diff --git a/code/compiler_scanner.l b/code/compiler/01/scanner.l similarity index 100% rename from code/compiler_scanner.l rename to code/compiler/01/scanner.l diff --git a/code/compiler_ast.hpp b/code/compiler/02/ast.hpp similarity index 100% rename from code/compiler_ast.hpp rename to code/compiler/02/ast.hpp diff --git a/code/compiler/02/compile.sh b/code/compiler/02/compile.sh new file mode 100755 index 0000000..2b2d0fe --- /dev/null +++ b/code/compiler/02/compile.sh @@ -0,0 +1,5 @@ +bison -o parser.cpp -d parser.y +flex -o scanner.cpp scanner.l +g++ -c -o scanner.o scanner.cpp +g++ -c -o parser.o parser.cpp +g++ main.cpp parser.o scanner.o diff --git a/code/compiler_main.cpp b/code/compiler/02/main.cpp similarity index 81% rename from code/compiler_main.cpp rename to code/compiler/02/main.cpp index 8f07c6b..ce3d541 100644 --- a/code/compiler_main.cpp +++ b/code/compiler/02/main.cpp @@ -1,5 +1,5 @@ -#include "compiler_ast.hpp" -#include "compiler_parser.hpp" +#include "ast.hpp" +#include "parser.hpp" void yy::parser::error(const std::string& msg) { std::cout << "An error occured: " << std::endl; diff --git a/code/compiler_parser.y b/code/compiler/02/parser.y similarity index 97% rename from code/compiler_parser.y rename to code/compiler/02/parser.y index 70950b4..986c8a5 100644 --- a/code/compiler_parser.y +++ b/code/compiler/02/parser.y @@ -1,8 +1,8 @@ %{ #include #include -#include "compiler_ast.hpp" -#include "compiler_parser.hpp" +#include "ast.hpp" +#include "parser.hpp" std::vector program; extern yy::parser::symbol_type yylex(); diff --git a/code/compiler_scanner_bison.l b/code/compiler/02/scanner.l similarity index 93% rename from code/compiler_scanner_bison.l rename to code/compiler/02/scanner.l index af90ca7..683deeb 100644 --- a/code/compiler_scanner_bison.l +++ b/code/compiler/02/scanner.l @@ -2,8 +2,8 @@ %{ #include -#include "compiler_ast.hpp" -#include "compiler_parser.hpp" +#include "ast.hpp" +#include "parser.hpp" #define YY_DECL yy::parser::symbol_type yylex() diff --git a/content/blog/01_compiler_tokenizing.md b/content/blog/01_compiler_tokenizing.md index c9a7616..5b1f123 100644 --- a/content/blog/01_compiler_tokenizing.md +++ b/content/blog/01_compiler_tokenizing.md @@ -201,7 +201,7 @@ So, our two regular expressions will be `[a-z][a-zA-Z]*` for the lowercase varia this, we create a new file, `scanner.l`, in which we write a mix of regular expressions and C++ code. Here's the whole thing: -{{< rawblock "compiler_scanner.l" >}} +{{< rawblock "compiler/01/scanner.l" >}} A flex file starts with options. I set the `noyywrap` option, which disables a particular feature of flex that we won't use, and which causes linker errors. Next up, diff --git a/content/blog/02_compiler_parsing.md b/content/blog/02_compiler_parsing.md index faba8b7..871a71c 100644 --- a/content/blog/02_compiler_parsing.md +++ b/content/blog/02_compiler_parsing.md @@ -225,14 +225,14 @@ Just like with tokenizing, there exists a piece of software that will generate a It's called Bison, and it is frequently used with Flex. Before we get to bison, though, we need to pay a debt we've already incurred - the implementation of our AST. Such a tree is language-specific, so Bison doesn't generate it for us. Here's what I came up with: -{{< codeblock "C++" "compiler_ast.hpp" >}} +{{< codeblock "C++" "compiler/02/ast.hpp" >}} We create a base class for an expression tree, which we call `ast`. Then, for each possible syntactic construct in our language (a number, a variable, a binary operation, a case expression) we create a subclass of `ast`. The `ast_case` subclass is the most complex, since it must contain a list of case expression branches, which are a combination of a `pattern` and another expression. Finally, we get to writing our Bison file, `parser.y`. Here's what I come up with: -{{< rawblock "compiler_parser.y" >}} +{{< rawblock "compiler/02/parser.y" >}} There's a few things to note here. First of all, the __parser__ is the "source of truth" regarding what tokens exist in our language. We have a list of `%token` declarations, each of which corresponds to a regular expression in our scanner. @@ -260,7 +260,7 @@ to return `yy::parser::symbol_type`. You can see it in our forward declaration o Now that we made these changes, it's time to hook up Flex to all this. Here's a new version of the Flex scanner, with all necessary modifications: -{{< rawblock "compiler_scanner_bison.l" >}} +{{< rawblock "compiler/02/scanner.l" >}} The key two ideas are that we overrode the default signature of `yylex` by changing the `YY_DECL` preprocessor variable, and used the `yy::parser::make_` functions @@ -268,16 +268,10 @@ to return the `symbol_type` rather than `int`. Finally, let's get a main function so that we can at least check for segmentation faults and other obvious mistakes: -{{< codeblock "C++" "compiler_main.cpp" >}} +{{< codeblock "C++" "compiler/02/main.cpp" >}} Now, we can compile and run the code: -``` -flex -o compiler_scanner.cpp compiler_scanner_bison.l -bison -o compiler_parser.cpp -d compiler_parser.y -g++ -c -o scanner.o compiler_scanner.cpp -g++ -c -o parser.o compiler_parser.cpp -g++ compiler_main.cpp parser.o scanner.o -``` +{{< codeblock "Bash" "compiler/02/compile.sh" >}} We used the `-d` option for Bison to generate the `compiler_parser.hpp` header file, which exports our token declarations and token creation functions, allowing us to use them in Flex.