From 0011ebac7a5c2e59448a09982c3f77b792f4a2d5 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Tue, 19 Aug 2025 00:06:00 -0300 Subject: [PATCH 01/12] [CraftingInterpreters] Add hello worlds proposed in CH01 --- .../01-introduction/helloworld-c/.gitignore | 4 ++ .../01-introduction/helloworld-c/Makefile | 20 ++++++++ .../01-introduction/helloworld-c/list.c | 48 ++++++++++++++++++ .../01-introduction/helloworld-c/list.h | 14 ++++++ .../01-introduction/helloworld-c/main.c | 50 +++++++++++++++++++ .../helloworld-java/.gitignore | 38 ++++++++++++++ .../helloworld-java/.idea/.gitignore | 3 ++ .../helloworld-java/.idea/encodings.xml | 7 +++ .../helloworld-java/.idea/misc.xml | 14 ++++++ .../helloworld-java/.idea/vcs.xml | 7 +++ .../01-introduction/helloworld-java/pom.xml | 17 +++++++ .../src/main/java/org/example/Main.java | 7 +++ 12 files changed, 229 insertions(+) create mode 100644 books/crafting-interpreters/01-introduction/helloworld-c/.gitignore create mode 100644 books/crafting-interpreters/01-introduction/helloworld-c/Makefile create mode 100644 books/crafting-interpreters/01-introduction/helloworld-c/list.c create mode 100644 books/crafting-interpreters/01-introduction/helloworld-c/list.h create mode 100644 books/crafting-interpreters/01-introduction/helloworld-c/main.c create mode 100644 books/crafting-interpreters/01-introduction/helloworld-java/.gitignore create mode 100644 books/crafting-interpreters/01-introduction/helloworld-java/.idea/.gitignore create mode 100644 books/crafting-interpreters/01-introduction/helloworld-java/.idea/encodings.xml create mode 100644 books/crafting-interpreters/01-introduction/helloworld-java/.idea/misc.xml create mode 100644 books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml create mode 100644 books/crafting-interpreters/01-introduction/helloworld-java/pom.xml create mode 100644 books/crafting-interpreters/01-introduction/helloworld-java/src/main/java/org/example/Main.java diff --git a/books/crafting-interpreters/01-introduction/helloworld-c/.gitignore b/books/crafting-interpreters/01-introduction/helloworld-c/.gitignore new file mode 100644 index 0000000..0685ed2 --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-c/.gitignore @@ -0,0 +1,4 @@ +helloworld +helloworld.exe +*~ +*.o diff --git a/books/crafting-interpreters/01-introduction/helloworld-c/Makefile b/books/crafting-interpreters/01-introduction/helloworld-c/Makefile new file mode 100644 index 0000000..cea76f3 --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-c/Makefile @@ -0,0 +1,20 @@ +SRCS := $(wildcard *.c) +OBJS := $(patsubst %.c,%.o,$(SRCS)) +CC := gcc +CFLAGS := -Wall -g +OBJFLAG := -c +OUTFLAG := -o + +.PHONY: all clean + +all: helloworld + +helloworld: $(OBJS) + $(CC) $(CFLAGS) $(OUTFLAG) $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) $(OUTFLAG) $@ $(OBJFLAG) $^ + +clean: + rm *.o + diff --git a/books/crafting-interpreters/01-introduction/helloworld-c/list.c b/books/crafting-interpreters/01-introduction/helloworld-c/list.c new file mode 100644 index 0000000..df8931c --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-c/list.c @@ -0,0 +1,48 @@ +#include "list.h" +#include +#include + +void +list_insert(list_node_t **list, char *string) +{ + list_node_t *end = *list; + if(end == NULL) { + *list = end = malloc(sizeof(list_node_t)); + end->prev = end->next = NULL; + } else { + while(end->next != NULL) + end = end->next; + end->next = malloc(sizeof(list_node_t)); + end->next->prev = end; + end->next->next = NULL; + end = end->next; + } + + end->string = malloc(strlen(string) * sizeof(char)); + strcpy(end->string, string); +} + +list_node_t * +list_find(list_node_t *list, const char *string) +{ + if(list == NULL) return NULL; + list_node_t *itr = list; + do { + if(strcmp(string, itr->string) == 0) break; + itr = itr->next; + } while(itr != NULL); + return itr; +} + +void +list_delete(list_node_t **node) +{ + if(*node == NULL) return; + + list_node_t *aux = *node; + if(aux->prev != NULL) aux->prev->next = aux->next; + if(aux->next != NULL) aux->next->prev = aux->prev; + free(aux->string); + *node = aux->next; + free(aux); +} diff --git a/books/crafting-interpreters/01-introduction/helloworld-c/list.h b/books/crafting-interpreters/01-introduction/helloworld-c/list.h new file mode 100644 index 0000000..ac72085 --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-c/list.h @@ -0,0 +1,14 @@ +#ifndef LIST_H +#define LIST_H + +typedef struct LIST_NODE_T { + char *string; + struct LIST_NODE_T *prev; + struct LIST_NODE_T *next; +} list_node_t; + +void list_insert(list_node_t **list, char *string); +list_node_t *list_find(list_node_t *list, const char *string); +void list_delete(list_node_t **node); + +#endif diff --git a/books/crafting-interpreters/01-introduction/helloworld-c/main.c b/books/crafting-interpreters/01-introduction/helloworld-c/main.c new file mode 100644 index 0000000..36cea2d --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-c/main.c @@ -0,0 +1,50 @@ +#include +#include "list.h" + +int +main(void) +{ + printf("Hello, world!\n"); + + list_node_t *list = NULL; + list_insert(&list, "Hello"); + list_insert(&list, "Cruel"); + list_insert(&list, "World"); + + printf("List contents:\n"); + for(const list_node_t *itr = list; itr != NULL; itr = itr->next) { + printf(" - %s\n", itr->string); + } + putchar('\n'); + + printf("Find word 'Cruel': "); + list_node_t *match = list_find(list, "Cruel"); + printf("%s\n", (match != NULL) ? "Found" : "Not found"); + + printf("Remove word 'Cruel'... "); + list_delete(&match); + printf("Done\n"); + + printf("Find word 'World'... "); + match = list_find(list, "World"); + printf("%s\n", (match != NULL) ? "Found" : "Not found"); + + printf("List contents backwards:\n"); + for(const list_node_t *itr = match; itr != NULL; itr = itr->prev) { + printf(" - %s\n", itr->string); + } + putchar('\n'); + + printf("Clear list... "); + int i = 0; + while(list != NULL) { + list_delete(&list); + printf("%d... ", ++i); + } + printf("Done\n"); + + printf("Check if list was cleared... %s\n", + (list == NULL) ? "Yes" : "No"); + + return 0; +} diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/.gitignore b/books/crafting-interpreters/01-introduction/helloworld-java/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-java/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/.idea/.gitignore b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/.idea/encodings.xml b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/.idea/misc.xml b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/misc.xml new file mode 100644 index 0000000..068a33d --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml new file mode 100644 index 0000000..ca9a474 --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/pom.xml b/books/crafting-interpreters/01-introduction/helloworld-java/pom.xml new file mode 100644 index 0000000..6e7215a --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-java/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + org.example + helloworld + 1.0-SNAPSHOT + + + 24 + 24 + UTF-8 + + + \ No newline at end of file diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/src/main/java/org/example/Main.java b/books/crafting-interpreters/01-introduction/helloworld-java/src/main/java/org/example/Main.java new file mode 100644 index 0000000..b6f0191 --- /dev/null +++ b/books/crafting-interpreters/01-introduction/helloworld-java/src/main/java/org/example/Main.java @@ -0,0 +1,7 @@ +package org.example; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello, world!"); + } +} \ No newline at end of file From 060a818a0a3cf64dd36114d7aa6f4f685e571440 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Tue, 19 Aug 2025 03:32:02 -0300 Subject: [PATCH 02/12] [CraftingInterpreters] Add notes on CH02 --- .../02-a-map-of-the-territory.org | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 books/crafting-interpreters/02-a-map-of-the-territory.org diff --git a/books/crafting-interpreters/02-a-map-of-the-territory.org b/books/crafting-interpreters/02-a-map-of-the-territory.org new file mode 100644 index 0000000..4ab3b0a --- /dev/null +++ b/books/crafting-interpreters/02-a-map-of-the-territory.org @@ -0,0 +1,265 @@ +#+TITLE: Chapter 02: A Map of the Territory + +* Parts of a language + +A language implementation starts with a text file as a stream of individual +characters. + +** Front-end of the language implementation + +*** Scanning / Lexing + +Also known as lexing, or lexical analysis. + +This part is known as scanner or lexer. + +This structure groups characters into "words", or tokens. There are characters +that can be ignored such as whitespace, and some tokens can even have a single +character. + +Example: + +#+begin_example +var average = (min+max)/2; +#+end_example + +Individual tokens: + +#+begin_example +var +average += +( +min ++ +max +) +/ +2 +; +#+end_example + +*** Parsing + +Construction of the language's grammar. + +The parser takes a flat sequence of tokens and builds a structure known as parse +tree or abstract syntax tree. Also known simply as syntax trees, ASTs, or just +trees. + +Building an AST also includes reporting syntax errors. + +*** Static analysis + +This is where we can analyze each of our expressions and identify their scope, +where variables are defined, etc. This process is called binding or resolution. + +This is also where we perform type checks when we have a statically typed +language. + +Scope, type checking, and other semantic details on this step can be annotated +here with a few techniques: + +- As attributes on the AST; +- As attributes on a lookup table where the names of variables and declarations + are keys for consulting -- this is known as a symbol table; +- Transform the AST into a new data structure that better expresses the + semantics of the code (intermediate representations; see next) + +** "Middle-end" of the language implementation + +*** Intermediate representations + +Act as an interface between the front-end, which is language-specific, and the +back-end, which is more coupled with final architecture. + +This middle-end of IRs are more coupled with semantics, so while they are lower +level than the language itself, they are still not coupled with implementation +for specific architectures. + +This allows some mobility so that we may interchange front and back ends for +languages with little to no effort: write a front-end for Pascal or C that +translates to the IR; then write back-ends that translate the IR to any +architecture that you want. + +*** Optimization + +Since IRs are tightly coupled with semantics, this is also the stage where we +can replace sections of code with others that have the same semantics, but +behave better. + +This is a big topic on itself and a huge amount of work is usually put here. + +Some topics of research for the future: + +- Constant folding; +- Constant propagation; +- Common subexpression elimination; +- Loop invariant code motion; +- Global value numbering; +- Strength reduction; +- Scalar replacement of aggregates; +- Dead code elimination; +- Loop unrolling... + +** Back-end of the language implementation + +*** Code generation + +Also known as "code gen". Generates primitive code, usually something like +assembly for the target CPU. + +Here we have the option to perform codegen for an actual existing CPU, or for a +virtual machine. + +Code generated for a virtual machine is known as bytecode, and the historical +reason for the name is that each instruction is usually a single byte long. + +*** Virtual machine + +If the produced code is bytecode, then it is the job of a virtual machine (VM), +a program that implements a theoretical chip for the virtual architecture, to +translate and run the bytecode on the actual CPU. + +While this technique may have an impact on performance, it is also a good way to +have portable code, since having the language run on many platforms implies only +on porting the virtual machine to other architectures. + +The second interpreter for Lox will run with a VM. + +*** Runtime + +When running the code, either natively or on a VM, we may need some basic +services provided by the language to the program that is running. For example, +you might need a garbage collector or something that helps managing memory while +the application is running. + +The runtime may accompany the compiled binary, or in the case of a VM, it may be +directly on the VM itself. + +* Shortcuts and alternate routes + +** Single-pass compilers + +Compilers that interleave parsing, analysis and code generation so that they +produce output directly in the parser, for example without allocating syntax +trees. + +While restrictive to the language's design, this was a useful technique when +memory was a scarce resource (see how Pascal requires variable initialization at +the beginning of a scope, or how C does not normally support calling functions +before their declaration; this is because their first compilers used a +single-pass tecnique). + +** Tree-walk interpreters + +Interpreters that begin executing code right after the code is parsed into an +AST, maybe after some bit of static analysis is applied. Code is interpreted by +traversing the syntax tree and evaluating branches, one at a time. + +This kind of implementation is common on student projects (also, this is +basically what is done on SICP's metacircular evaluator, and also on Majestic +Lisp's first implementation!), but the performance is not good on such +implementations. + +People tend to use the word "interpreter" to designate tree-walk interpreters. + +** Transpilers + +This technique replaces the canonical low-level codegen for the back-end with +codegen for other high-level target language, therefore producing strings with +valid syntax for another language; from here on, the target language's tools +take over. + +This technique is called a source-to-source compiler, or a transcompiler; often +also called a transpiler. + +** Just-in-time compilation + +Instead of compiling the IRs ahead-of-time to target implementations, compilers +with this technique take the IR to the end-user's computer and, when it is time +to run the application, the IR is loaded and then compiled to the native +architecture. + +This technique is used because we still want the performance of native code, but +we don't want to provide multiple binaries of the same application for each +architecture. So compile it on the user's machine when the program is needed! + +This is what Microsoft does with Common Language Runtime (CLR), and also most +JavaScript interpreters do this too. + +Worth mentioning: The HotSpot Java Virtual Machine also adds profiling hooks to +the generated code to analyze regions that are performance-critical and the kind +of data that they are processing, then automatically performs optimization and +recompiles those regions of code as needed. + +* Compilers and interpreters + +Compilation and interpretation is not a dichotomy. + +Compilation is an implementation technique that involves translating the source +code to some other form, usually low level. So generating assembly, generating +bytecode, and transpiling, are all kinds of compilation. + +Compilation also does not involve running code; this is a job for the end user +in this case. + +Saying that an implementation "is an interpreter" means that it takes source +code and executes it immediately; the code runs programs from source. + +A counterintuitive example: The Go compiler. Running =go build= generates a +binary file and does not run it, so it is just compilation. However, =go run= +compiles the code and then runs it, so the code is being interpreted. So the Go +compiler: + +- Is a compiler, since it generates a binary that will still be run by the user; +- Is an interpreter, since it supports running the code from source; +- HAS a compiler, since using it as an interpreter still compiles the code. + +* Challenges + +** Pick an open source language; look at the code; find scanner and parser; is it handwritten or generated? + +I've picked up Loko Scheme, more specifically a mirror from GitHub: +https://github.com/guenchi/loko. This is a Scheme implementation that compiles +to native code. I have once used it to write a bare-bones kernel for x86_64. + +The parser is handwritten, and it seems like it exploits Scheme's own runtime to +perform scanning, then annotates the tokens for static analysis (See +loko/runtime/reader.sls, line 487). + +The exploitable reader of Lisp is probably one of the main reasons why this is +simple enough to be handwritten. + +** JIT is fast for dynamically-typed languages, what reasons are there not to use JIT? + +I had a hunch on this one, but I prefered to google it up anyway. + +Here's a mix of my hunches and of what I found: + +1. You're introducing an extra layer of abstraction, so the code needs to be + compiled before being run. This can have some overhead, even though it might + only be done once. So it seems fine for long-running processes, but not for + small, short-lived applications. +2. It is still an abstraction over architecture, so it is not ideal for + bare-metal applications, for the same reasons that VMs aren't. +3. Again, it produces an overhead for the end-user, so it may cause performance + problems for the end user at startup. If an opinion may be excused here: + this is probably why React applications suck so much in terms of performance + on browsers. +4. For the same reason, a precompiled, ahead-of-time binary may use sofisticated + optimization tecniques that require a lot of time, since the compilation will + be done at the developer's end. So AOT compilation may produce better code. + +** Lisp implementations that compile to C also contain an interpreter. Why? + +Because Lisp is an awesome language. + +But more than that, this is because Lisp dialect implementations tend to be +compilers AND interpreters; the code is generated, compiled and subsequently +run in those cases. + +Implementations such as SBCL for example compile the code to machine language +before execution. The =DISASSEMBLE= function even allows one to inspect machine +code generated from a function, so you can also perform optimizations and +rewrite your functions at runtime to be less generic and more performant. From f8a909f1e03ff1494f7db6ef1117a049bb7efb17 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Tue, 19 Aug 2025 12:08:59 -0300 Subject: [PATCH 03/12] [CraftingInterpreters] Reorganize notes --- .../ch02-notes.org} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename books/crafting-interpreters/{02-a-map-of-the-territory.org => 02-a-map-of-the-territory/ch02-notes.org} (100%) diff --git a/books/crafting-interpreters/02-a-map-of-the-territory.org b/books/crafting-interpreters/02-a-map-of-the-territory/ch02-notes.org similarity index 100% rename from books/crafting-interpreters/02-a-map-of-the-territory.org rename to books/crafting-interpreters/02-a-map-of-the-territory/ch02-notes.org From f5f5af43bd3da3947e726b50455ef4b503f9e1e6 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Wed, 20 Aug 2025 22:05:19 -0300 Subject: [PATCH 04/12] [CraftingInterpreters] Add chapter 3 --- .../helloworld-java/.idea/vcs.xml | 1 - .../03-the-lox-language/church-numerals.lox | 50 ++ .../03-the-lox-language/fibonacci.lox | 10 + .../03-the-lox-language/helloworld.lox | 1 + .../03-the-lox-language/notes.org | 528 ++++++++++++++++++ 5 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 books/crafting-interpreters/03-the-lox-language/church-numerals.lox create mode 100644 books/crafting-interpreters/03-the-lox-language/fibonacci.lox create mode 100644 books/crafting-interpreters/03-the-lox-language/helloworld.lox create mode 100644 books/crafting-interpreters/03-the-lox-language/notes.org diff --git a/books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml index ca9a474..4fce1d8 100644 --- a/books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml +++ b/books/crafting-interpreters/01-introduction/helloworld-java/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/books/crafting-interpreters/03-the-lox-language/church-numerals.lox b/books/crafting-interpreters/03-the-lox-language/church-numerals.lox new file mode 100644 index 0000000..8e97e66 --- /dev/null +++ b/books/crafting-interpreters/03-the-lox-language/church-numerals.lox @@ -0,0 +1,50 @@ +fun zero(f, x) { + return x; +} + +fun one(f, x) { + return f(x); +} + +fun two(f, x) { + return f(f(x)); +} + +fun three(f, x) { + return f(f(f(x))); +} + +fun four(f, x) { + return f(f(f(f(x)))); +} + +fun five(f, x) { + return f(f(f(f(f(x))))); +} + +// ----- + +fun succ(n, f, x) { + return f(n(f, x)); +} + +fun add(m, n, f, x) { + return m(f, n(f, x)); +} + +// ----- + +fun plus_one(n) { + return n + 1; +} + +// ----- + +print zero(plus_one, 0); +print one(plus_one, 0); +print two(plus_one, 0); +print three(plus_one, 0); +print four(plus_one, 0); +print five(plus_one, 0); +print succ(five, plus_one, 0); +print add(five, four, plus_one, 0); diff --git a/books/crafting-interpreters/03-the-lox-language/fibonacci.lox b/books/crafting-interpreters/03-the-lox-language/fibonacci.lox new file mode 100644 index 0000000..e2d5980 --- /dev/null +++ b/books/crafting-interpreters/03-the-lox-language/fibonacci.lox @@ -0,0 +1,10 @@ +fun fibonacci(n) { + if((n == 0) or (n == 1)) { + return 1; + } + return fibonacci(n - 1) + fibonacci(n - 2); +} + +for(var i = 0; i < 5; i = i + 1) { + print fibonacci(i); +} diff --git a/books/crafting-interpreters/03-the-lox-language/helloworld.lox b/books/crafting-interpreters/03-the-lox-language/helloworld.lox new file mode 100644 index 0000000..1032681 --- /dev/null +++ b/books/crafting-interpreters/03-the-lox-language/helloworld.lox @@ -0,0 +1 @@ +print "Hello, world!"; diff --git a/books/crafting-interpreters/03-the-lox-language/notes.org b/books/crafting-interpreters/03-the-lox-language/notes.org new file mode 100644 index 0000000..f637b02 --- /dev/null +++ b/books/crafting-interpreters/03-the-lox-language/notes.org @@ -0,0 +1,528 @@ +#+TITLE: The Lox Language + +* Hello, Lox + +#+begin_src lox :tangle helloworld.lox +print "Hello, world!"; +#+end_src + +* A high-level language + +** Dynamic typing + +Lox is dynamically-typed, which means that variables can have values attributed +to them of different types at each time. + +Type error resolution is also done at runtime instead of at static analysis. + +** Automatic memory management + +There are two fundamental types of automatic memory management: reference +counting and tracing garbage collection. + +Reference counting tends to be easier but over time, the limitations of this +technique can become troublesome (reader's note: see Rust and its reference +counting across scopes when concurrency is in play). + +Garbage collection tends to have a fearsome reputation but can be fun, even +though debugging garbage collectors can be hard. + +Lox uses a garbage collector. + +* Data types + +** Booleans + +#+begin_src lox +true; +false; +#+end_src + +** Numbers + +Lox is going to follow the javascript way and have only double-precision +floating points as a numeric type. + +#+begin_src lox +1234; // Integer +12.34; // Decimal +#+end_src + +** Strings + +Literals enclosed in double quotes. Can be tricky; will probably support +unicode. The idea of a character is open-ended: can be ASCII, unicode, a code +point, a "grapheme cluster". Strings may also have fixed length or variable +length... + +#+begin_src lox +"I am a string"; +""; +"123"; +#+end_src + +** Nil + +Represents no value. + +There are strong arguments to ban the "null value" from languages, and this +might be very effective on statically-typed languages, but on dynamically-typed +languages, not having the "null value" seems to be more annoying. + +#+begin_src lox +nil; +#+end_src + +* Expressions + +** Arithmetic + +Basic arithmetic operators. + +#+begin_src lox +add + me; +subtract - me; +multiply * me; +divide / me; +#+end_src + +- These are binary operators. +- Subexpressions are known as operands. +- Operators are infix. + +One of these operators may also be a prefix operator: + +#+begin_src lox +-negateMe; +#+end_src + +Operator behavior are basically the same one would find in C. + +** Comparison and equality + +These operations always return a boolean result. + +#+begin_src lox +less < than; +lessThan <= orEqual; +greater > than; +greaterThan >= orEqual; + +1 == 2; // false +"cat" != "dog"; // true + +// Comparisons are type-tolerant +314 == "pi"; // false + +// Values of different types are never equivalent. +// We have no implicit conversions +123 == "123"; // false +#+end_src + +** Logical operators + +Negation. + +#+begin_src lox +!true; // false +!false; // true +#+end_src + +Conjunction. + +#+begin_src lox +true and false; // false +true and true; // true +#+end_src + +Disjunction. + +#+begin_src lox +false or false; // false +true or false; // true +#+end_src + +Conjunction and disjunction operate sort of like control flows because they +short-circuit: if the first operand is enough to yield the entire expression's +result, then the second operand won't even be evaluated. + +** Precedence and grouping + +Precedence and groping is inherited from C and will be discussed further when +parsing. Whenever precedence fails, use parens to group stuff. + +* Statements + +While an expression's main job is to produce a value, a statement's job is to +produce an effect. + +Examples of statements: + +#+begin_src lox +// Evaluates a single expression and displays the result +print "Hello, world"; + +"some expression"; // Semicolon at end promotes expression to an "expression statement" + +// Statements can be packed within a block +{ + print "One statement"; + print "Two statements"; +} +#+end_src + +blocks also affect scoping. + +* Variables + +Variables are declared by using =var= statements. Omitting an initializer +defaults a variable to =nil=. + +#+begin_src lox +var imAVariable = "here is my value"; +var iAmNil; + +// Accessing variables +print imAVariable; // "here is my value" +imAVariable = "test"; +print imAVariable; // "test" +#+end_src + +Scoping rules will be detailed once programmed, but are much like C and Java. + +* Control flow + +=if=, =else=, =while= and =for=, basically. + +#+begin_src lox +if(condition) { + print "yes"; +} else { + print "no"; +} + +var a = 1; +while(a < 10) { + print a; + a = a + 1; +} + +for(var a = 1; a < 10; a = a + 1) { + print a; +} +#+end_src + +* Functions + +Function calls work just like in C. + +#+begin_src lox +makeBreakfast(bacon, eggs, toast); +makeBreakfast(); +#+end_src + +Function definition: + +#+begin_src lox +fun printSum(a, b) { + print a + b; +} +#+end_src + +Some clarification on nomenclatures: + +- An *argument* is a value passed to a function, so function calls have argument + lists. Also called "actual parameters". +- A *parameter* is a variable that holds the value of the argument inside the + body of the function. So function declarations have parameter lists. Also + called "formal parameters" or simply "formals". + +Bodies of functions are blocks and so, values can be returned with a =return= +statement. + +#+begin_src lox +fun returnSum(a, b) { + return a + b; +} +#+end_src + +** Declaration vs definition + +Some statically typed languages like C make a distinction between function +declaration and function definition. + +A declaration binds the function's type to its name, but does not define a body. + +A definition declares the function and also fills in the body so that the +function can be compiled. + +Lox is dynamically typed, so this distinction isn't meaningful to it. + +* Closures + +Functions in Lox are first-class citizens. + +#+begin_src lox +fun addPair(a, b) { + return a + b; +} + +fun identity(a) { + return a; +} + +print identity(addPair)(1, 2); // Prints 3. +#+end_src + +Since functions can be used as block scopes, a function may capture the scope of +another, thus creating closures: + +#+begin_src lox +fun retFunction() { + var outside = "outside"; + fun inner() { + print outside; + } + return inner; +} + +var fn = retFunction(); +fn(); // Prints "outside" +#+end_src + +This nomenclature was created by Peter J. Landin since it involves a data +structure that closes over and holds the variables it needs. + +* Classes + +Lox has classes. + +** Why might any language want to be object oriented? + +Because it can still be useful and having objects comes in handy. Plus we can +avoid the need to prefix all function names that would collide with similar +functions of different types, since methods are scoped to the object. + +** Why is Lox object oriented? + +Because most books don't cover it well and it won't hurt looking at how it is +implemented after all. + +** Classes or prototypes + +Class-based OO is the classic way of implementing object orientation in C++, +Java and C#, and usually operate with instances and classes. In those, there may +be a static dispatch (which looks up methods at compile time), or dynamic +dispatch (which looks up methods at runtime on the actual instance of the +object). + +Prototype-based OO is implemented in JavaScript and merges the concepts of +classes and objects: There are only objects that may contain state and methods; +they also can directly inherit from ("delegate to") each other. + +So prototypal languages seem more fundamental than class languages, therefore +being simpler to implement too, and have a higher flexibility on expressing +patterns. But this flexibility often leads to... reinventing classes. + +Lox is going for a class-based approach. + +** Classes in Lox + +#+begin_src lox +class Breakfast { + cook() { + print "Eggs a-fryin'!"; + } + + serve(who) { + print "Enjoy your breakfast, " + who + "."; + } +} +#+end_src + +Classes are first-class in Lox. So creating classes means that an object with +the class name is also created. Therefore classes can be manipulated as such: + +#+begin_src lox +var someVariable = Breakfast; +someFunction(Breakfast); +#+end_src + +Calling a class like a function produces an instance of itself. + +#+begin_src lox +var breakfast = Breakfast(); +print breakfast; // "Breakfast instance" +#+end_src + +** Instantiation and initialization + +Fields are supposed to be added freely to instances, unfortunately. To create a +field, perform an assignment; if it doesn't exist, it will be created. + +#+begin_src lox +breakfast.meat = "sausage"; +breakfast.bread = "sourdough"; +#+end_src + +To access a field or a method within a class definition, use the =this= keyword. + +#+begin_src lox +class Breakfast { + serve(who) { + print "Enjoy your " + this.meat + " and " + + this.bread + ", " + who + "."; + } + // ... +} +#+end_src + +To initialize an object, just define an =init= method. It will be automatically +called when the class is instantiated: + +#+begin_src lox +class Breakfast { + init(meat, bread) { + this.meat = meat; + this.bread = bread; + } + // ... +} + +var baconAndToast = Breakfast("bacon", "toast"); +baconAndToast.serve("Dear Reader"); +// "Enjoy your bacon and toast, Dear Reader." +#+end_src + +** Inheritance + +Lox supports single inheritance. + +It also inherits constructors. + +#+begin_src lox +class Brunch < Breakfast { + init(meat, bread, drink) { + super.init(meat, bread); + this.drink = drink; + } + + drink() { + print "How about a " + this.drink + "?"; + } +} + +var benedict = Brunch("ham", "English muffin", "Bloody Mary"); +benedict.serve("Noble Reader"); +#+end_src + +Here, Brunch is a derived class or subclass, and Breakfast is a base class or +superclass. Hence the use of the keyword =super= to call the base class's +constructor. + +* The Standard Library + +Will contain a small set of functions and stuff just to get things done. The +language is not supposed to be used for real problems anyway. + +* Challenges + +** Sample Lox programs + +#+begin_src lox :tangle fibonacci.lox +fun fibonacci(n) { + if((n == 0) or (n == 1)) { + return 1; + } + return fibonacci(n - 1) + fibonacci(n - 2); +} + +for(var i = 0; i < 5; i = i + 1) { + print fibonacci(i); +} +#+end_src + +There seems to be an edge case involving concatenating numbers into strings. I +might not be aware of the standard library right now, but seems like a +limitation. + +#+begin_src lox :tangle church-numerals.lox +fun zero(f, x) { + return x; +} + +fun one(f, x) { + return f(x); +} + +fun two(f, x) { + return f(f(x)); +} + +fun three(f, x) { + return f(f(f(x))); +} + +fun four(f, x) { + return f(f(f(f(x)))); +} + +fun five(f, x) { + return f(f(f(f(f(x))))); +} + +// ----- + +fun succ(n, f, x) { + return f(n(f, x)); +} + +fun add(m, n, f, x) { + return m(f, n(f, x)); +} + +// ----- + +fun plus_one(n) { + return n + 1; +} + +// ----- + +print zero(plus_one, 0); +print one(plus_one, 0); +print two(plus_one, 0); +print three(plus_one, 0); +print four(plus_one, 0); +print five(plus_one, 0); +print succ(five, plus_one, 0); +print add(five, four, plus_one, 0); +#+end_src + +** Questions about syntax and semantics + +First thing that comes to mind are anonymous functions: Can we declare them? +Since they're first class citizens, it would do a lot for us to have them, +especially if we're going for functional approaches. + +Are classes going to have something similar to destructors? Doesn't feel +necessary but could be a feature. Maybe not, though. + +** Since Lox is tiny, how many more features does it need? + +Lox is not really my cup of tea, so I don't think it's missing much because I +don't see a real-world scenario for it where something else (like Lua or Scheme +or even Majestic) would excel instead. + +But I feel like there should be a way to concatenate strings and numbers, for +example. Though this may be covered by the standard library, it would be nicer +if there were a plus operator overload. + +And the anonymous function thing. + +* Design note: Expressions and statements + +Nothing to add, just wanna say that I like the idea that everything is an +expression. I actually find it to be more useful and perhaps easier to implement +than having statements mixed in the bag. + From 2d7484ecedfa3759b4672c3894117396cc5c3496 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Fri, 22 Aug 2025 01:56:39 -0300 Subject: [PATCH 05/12] [CraftingInterpreters] Add chapter 4 --- books/crafting-interpreters/.gitignore | 1 + .../04-scanning/notes.org | 287 ++++++++++++++++++ books/crafting-interpreters/index.org | 28 ++ books/crafting-interpreters/jlox/.gitignore | 38 +++ .../jlox/.idea/.gitignore | 3 + .../jlox/.idea/dictionaries/project.xml | 3 + .../jlox/.idea/encodings.xml | 7 + .../inspectionProfiles/Project_Default.xml | 12 + .../crafting-interpreters/jlox/.idea/misc.xml | 14 + .../crafting-interpreters/jlox/.idea/vcs.xml | 6 + .../jlox/examples/test.lox | 13 + books/crafting-interpreters/jlox/pom.xml | 17 ++ .../com/craftinginterpreters/lox/Lox.java | 60 ++++ .../com/craftinginterpreters/lox/Scanner.java | 214 +++++++++++++ .../com/craftinginterpreters/lox/Token.java | 24 ++ .../craftinginterpreters/lox/TokenType.java | 20 ++ 16 files changed, 747 insertions(+) create mode 100644 books/crafting-interpreters/.gitignore create mode 100644 books/crafting-interpreters/04-scanning/notes.org create mode 100644 books/crafting-interpreters/index.org create mode 100644 books/crafting-interpreters/jlox/.gitignore create mode 100644 books/crafting-interpreters/jlox/.idea/.gitignore create mode 100644 books/crafting-interpreters/jlox/.idea/dictionaries/project.xml create mode 100644 books/crafting-interpreters/jlox/.idea/encodings.xml create mode 100644 books/crafting-interpreters/jlox/.idea/inspectionProfiles/Project_Default.xml create mode 100644 books/crafting-interpreters/jlox/.idea/misc.xml create mode 100644 books/crafting-interpreters/jlox/.idea/vcs.xml create mode 100644 books/crafting-interpreters/jlox/examples/test.lox create mode 100644 books/crafting-interpreters/jlox/pom.xml create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Scanner.java create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Token.java create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/TokenType.java diff --git a/books/crafting-interpreters/.gitignore b/books/crafting-interpreters/.gitignore new file mode 100644 index 0000000..a8ac5b0 --- /dev/null +++ b/books/crafting-interpreters/.gitignore @@ -0,0 +1 @@ +**/ltximg/ diff --git a/books/crafting-interpreters/04-scanning/notes.org b/books/crafting-interpreters/04-scanning/notes.org new file mode 100644 index 0000000..dae4c80 --- /dev/null +++ b/books/crafting-interpreters/04-scanning/notes.org @@ -0,0 +1,287 @@ +#+TITLE: Scanning + +Most of the things in this chapter are related to coding, so from now on, I will +simply start writing notes on specific subtopics. + +* The Interpreter Framework + +** Error handling + +The method =error= is declared here with no private/public access +modifier. Recall from Java that having no access modifier also modifies how that +element is visible. Here is a transcript of a table from stackoverflow. + +| Modifier | Class | Package | Subclass | World | +|-----------+-------+---------+----------+-------| +| public | Y | Y | Y | Y | +| protected | Y | Y | Y | N | +| (Default) | Y | Y | N | N | +| private | Y | N | N | N | + +What this means is that methods and attributes are package-private by default, +meaning that they can be seen over the package, but cannot be seen by a class +that inherits the discussed base class, nor by other classes on other packages. + +* Lexemes and Tokens + +Static analysis is basically scanning through the list of characters, grouping +them together onto the smallest sequences that still represent something. + +Each of these sequences is called a *lexeme*. Notice that they are just the raw +substrings. + +For example. + +#+begin_src lox +var language = "lox"; +#+end_src + +Individual lexemes: + +#+begin_example +var +language += +"lox" +; +#+end_example + +If we bundle together a *lexeme* with some other data (related to semantics, for +example), we call the bundle a *token*. + +This bundle can have extra useful data such as: + +- Token type (for example, keywords); +- Literal value (for example, number or string values...); +- Location information (for example, line, column, length, etc. jlox only uses lines); +- etc... + +** Location information + +One interesting strategy: Some languages store the offset of the beginning of +the file to the beginning of the lexeme, plus their length. So displaying error +messages related to tokens will be slow. However, these only need to be done +when there are errors, and most tokens won't end up in error messages. + +Plus, offset and length are already information that will be needed when +capturing the lexeme, so you're already good to go with respect to storing this +kind of data. + +* Regular languages and expressions + +The scanner is basically a loop that gulps characters from first to last, +consuming characters that are a part of a lexeme, and then emits a token at the +end of a lexeme. + +It is possible to use regexes to match each kind of lexeme. + +The rules that determine how the language groups characters into lexemes are +called its *lexical grammar*. Lox's grammar is a simple language, enough to be a +*regular language*, which means that the lexemes can be recognized using regular +expressions after all. + +Lex and Flex are tools to handle grammars with regular expressions like +that. You give them regexes, they give you a scanner. + +But here we'll handcraft it. + +This closely relates to Chomsky's grammars too! The theory can be seen at The +Dragon Book (Compilers: Principles, Techniques and Tools). + + +* Longer lexemes + +Function =peek= performs an operation called *lookahead*, which means that we +are looking at the next character to help us decide what to do. + +=advance= is a *consumer*, so it just consumes the current character. + +=match= is a combination of both: it performs a *lookahead* and, if the +character is the one expected, it gets consumed. + +Notice also how *comments are lexemes*, but since they are not meaningful, they +are ignored. + +** Number literals + +In Lox, all numbers are internally represented as double-precision floating +points, but integer and decimal literals are supported. + +Leading or trailing decimal points are not allowed. + +Oh! And the minus sign will not be a part of the number literal. They will be +the minus operator applied to a number. Personally I think this is shooting +yourself on the foot and there is an aside note discussing a case where this +could be terribly wrong, such as =print -123.abs();=, if we were to allow +calling methods for number literals like that. + +But what the heck, I'm not here to bash on language design. I'll leave my design +choices to Majestic Lisp. + +* Reserved words and identifiers + +Concept: *Maximal munch*. + +Whenever two lexical grammar rules can both match a chunk of code that the +scanner is looking at, whichever one matches the most characters wins. + +This is the base for not confusing tokens such as the keyword =or= and, say, a +variable name like =orchid=. + +Because of that, we can't determine if a *reserved word* was used until we stop +at what might be the end of a lexeme; and this is also why we call those words +"reserved". + +* Challenges + +** Lexical grammars of Python and Haskell are not regular. Why? And what does it mean? + +So the explanation relates to Chomsky's grammars and I was lucky to learn about +them on Computer Theory classes, so I won't extend much here. + +According to Wikipedia, a grammar is regular if it is right-regular or +left-regular. The production rules therefore allow words to "sprout" to the left +or to the right. + +But if we mix these two, we may end up with a context-free grammar like this +language: + +$L = \{a^{n} b^{n} \| n > 0 \}$ + +Note that we can't just build our language from left or from right +there. Something there depends on context. + +This seems to be the case for Python and Haskell, and I can clearly see that +from Python especially because Python needs to have CONTEXT for its statements +and expressions, since they are bound by WHITESPACE INDENTATION. + +That whitespace indentation right there gives us the impression that we need to +stack and keep track of whatever level of indentation we are right now. Well, +stacking leads us in computer theory to pushdown automatons -- PDAs, or +"autômatos de pilha", as I learned in Portuguese --. This makes Python +context-sensitive. And being context-sensitive means it cannot be just a +finite-state machine! + +I don't know enough Haskell to explain that from a Haskell standpoint though. I +believe whitespace matters too? + +** Space affects how code is parsed in CoffeeScript, Ruby and the C preprocessor, Where and what effect does it have in each? + +*** CoffeeScript + +Apparently, parentheses around a function call is optional, so you can then pass +a parameter to a function by using white space. I found this example that seems +to illustrate that -- you need a space after =map= because the anonymous +function is its parameter. Here's two semantically equivalent expressions. + +#+begin_src coffee +# Function call with explicit arguments between parens +nums = [1..10].map((i) -> i*2) + +# Function call omitting parens +nums = [1..10].map (i) -> i*2 +#+end_src + +*** Ruby + +Ruby has a similar issue -- methods can be called without parentheses! + +So whitespace does matter in these cases. + +#+begin_src ruby +puts 2.between?(1, 3) # Outputs "true" +puts 2.between? 1, 3 # Outputs "true" + +puts 2.between? (1, 3) # Errors with the , token +#+end_src + +*** C preprocessor + +The C preprocessor only takes macros and rewrite them as a string of tokens and +whitespace, so this step happens before anything is semantically checked. + +The C preprocessor is not standardized so it is mostly subject to whatever the +hell the programmers want to do. In GCC, for example, escaped newlines are +deleted, hence we have another case of whitespace that matters. + +More info: https://gcc.gnu.org/onlinedocs/gcc-3.0.4/cppinternals_4.html + +** Why would you want to write a scanner that does not discard comments and whitespace? + +Because it may be the case that the comments provide some sort of documentation +that may be semantically valid as metadata. + +Even more than that; if for some reason you have annotations on your language +and you decide to put them inside comments, this might also be a good cause to +parse comments too. They may even be a sublanguage of sorts, in this case. + +** Add C-style multiline comments to Lox + +Here's how I did it. Everything is at =Scanner.java=. + +First of all, in =Scanner.scanToken=, I modified the base rule for ='/'= as +such: + +#+begin_src java + case '/': + if(match('/')) { + // A comment goes until EOL + while (peek() != '\n' && !isAtEnd()) advance(); + } else if(match('*')) { + multilineComment(); + } else { + addToken(SLASH); + } + break; +#+end_src + +Then I declared the method =Scanner.multilineComment=: + +#+begin_src java + private void multilineComment() { + int start_line = line; + while(!isAtEnd()) { + switch(peek()) { + case '\n': + line++; + break; + case '*': + if (peekNext() == '/') { + advance(); + advance(); + return; + } + break; + } + advance(); + } + Lox.error(start_line, "Unclosed multiline comment"); + } +#+end_src + +About NESTING, I will not do that. If I do that, we're going to reach a similar +case discussed on the previous questions, in which I'll end up with a comment +that needs to keep track of its delimiters left and right, which is a job for a +stack of sorts. + +This means that our grammar will not be regular anymore, therefore being a job +for a pushdown automaton ("autômato de pilha" in Portuguese...), and therefore +our language will not be expressible as a regular grammar anymore. + +This will probably imply changing the entire algorithm there. So yeah... No +way. :) + +* Design note: Implicit semicolons + +I just wanna point out that this read was very insightful and the JavaScript +part made me laugh once more at how much this language is a mess. + +My own opinion is that having statement terminators is chic for infix languages, +but that's because I'm a fan of C in many aspects. I like for example how Rust +basically copied how semicolons work in C, C++ and Java, and then slapped scopes +as expressions and the last statement of a scope as its return value. + +They could have left the ternary operator there, though. + +But if I have the option, I will simply build a Lisp dialect like I already am. + diff --git a/books/crafting-interpreters/index.org b/books/crafting-interpreters/index.org new file mode 100644 index 0000000..dea5525 --- /dev/null +++ b/books/crafting-interpreters/index.org @@ -0,0 +1,28 @@ +#+TITLE: Crafting Interpreters: Notes on the book + +Below you will find my notes while reading this book and building the Lox +language interpreters in Java and C. + +* Code + +- [[./jlox/][jlox source]] +- clox source (soon) + +* Chapter notes + +1. Introduction\\ + No notes, just Hello Worlds and a linked list implementation in C. +2. [[./02-a-map-of-the-territory/ch02-notes.org][A Map of the Territory]] +3. [[./03-the-lox-language/notes.org][The Lox Language]]\\ + Tangles some test files in Lox language. +4. [[./04-scanning/notes.org][Scanning]] + +* Tooling + +All Java code was built using IntelliJ IDEA Community Edition because I'm not +stupid enough to pretend that configuring Emacs for Java doesn't suck, and I +don't wanna bloat my Emacs config also. + +All C code, on the other hand, is glued together using Makefiles and such +because C is portable and fits like a glove in Emacs. + diff --git a/books/crafting-interpreters/jlox/.gitignore b/books/crafting-interpreters/jlox/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/books/crafting-interpreters/jlox/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/.gitignore b/books/crafting-interpreters/jlox/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/books/crafting-interpreters/jlox/.idea/dictionaries/project.xml b/books/crafting-interpreters/jlox/.idea/dictionaries/project.xml new file mode 100644 index 0000000..4787784 --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/dictionaries/project.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/encodings.xml b/books/crafting-interpreters/jlox/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/inspectionProfiles/Project_Default.xml b/books/crafting-interpreters/jlox/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1eefce7 --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/misc.xml b/books/crafting-interpreters/jlox/.idea/misc.xml new file mode 100644 index 0000000..bf22f3a --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/vcs.xml b/books/crafting-interpreters/jlox/.idea/vcs.xml new file mode 100644 index 0000000..c2365ab --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/examples/test.lox b/books/crafting-interpreters/jlox/examples/test.lox new file mode 100644 index 0000000..a8da0ee --- /dev/null +++ b/books/crafting-interpreters/jlox/examples/test.lox @@ -0,0 +1,13 @@ +/* This is a + multiline comment +*/ + +var foo = 2; + +// This is another comment + +/* This is yet + another + multiline + comment +*/ diff --git a/books/crafting-interpreters/jlox/pom.xml b/books/crafting-interpreters/jlox/pom.xml new file mode 100644 index 0000000..a87ee98 --- /dev/null +++ b/books/crafting-interpreters/jlox/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + com.craftinginterpreters + lox + 1.0-SNAPSHOT + + + 24 + 24 + UTF-8 + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java new file mode 100644 index 0000000..3c356e9 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java @@ -0,0 +1,60 @@ +package com.craftinginterpreters.lox; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class Lox { + static boolean hadError = false; + + public static void main(String[] args) throws IOException { + if(args.length > 1) { + System.out.println("Usage: jlox [script]"); + System.exit(64); // Taken from sysexits.h + } else if(args.length == 1) { + runFile(args[0]); + } else { + runPrompt(); + } + } + + private static void runFile(String path) throws IOException { + byte[] bytes = Files.readAllBytes(Paths.get(path)); + run(new String(bytes, Charset.defaultCharset())); + if(hadError) System.exit(65); + } + + private static void runPrompt() throws IOException { + InputStreamReader input = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(input); + + while(true) { + System.out.print("> "); + String line = reader.readLine(); + if(line == null) break; + run(line); + } + } + + private static void run(String source) { + Scanner scanner = new Scanner(source); + List tokens = scanner.scanTokens(); + + for(Token token : tokens) { + System.out.println(token); + } + } + + static void error(int line, String message) { + report(line, "", message); + } + + private static void report(int line, String where, String message) { + System.err.println("[line " + line + "] Error " + where + ": " + message); + hadError = true; + } +} \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Scanner.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Scanner.java new file mode 100644 index 0000000..6f8e3e0 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Scanner.java @@ -0,0 +1,214 @@ +package com.craftinginterpreters.lox; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// To avoid having lots and logs of TokenType.* everywhere. +// Not considered good practice, but we'll just use it anyway. +import static com.craftinginterpreters.lox.TokenType.*; + +public class Scanner { + private final String source; + private final List tokens = new ArrayList<>(); + private static final Map keywords; + private int start = 0; + private int current = 0; + private int line = 1; + + static { + keywords = new HashMap<>(); + keywords.put("and", AND); + keywords.put("class", CLASS); + keywords.put("else", ELSE); + keywords.put("false", FALSE); + keywords.put("for", FOR); + keywords.put("fun", FUN); + keywords.put("if", IF); + keywords.put("nil", NIL); + keywords.put("or", OR); + keywords.put("print", PRINT); + keywords.put("return", RETURN); + keywords.put("super", SUPER); + keywords.put("this", THIS); + keywords.put("true", TRUE); + keywords.put("var", VAR); + keywords.put("while", WHILE); + } + + public Scanner(String source) { + this.source = source; + } + + List scanTokens() { + while(!isAtEnd()) { + // We're at the beginning of the next lexeme. + start = current; + scanToken(); + } + + tokens.add(new Token(EOF, "", null, line)); + return tokens; + } + + private boolean isAtEnd() { + return current >= source.length(); + } + + private void scanToken() { + char c = advance(); + switch(c) { + case '(': addToken(LEFT_PAREN); break; + case ')': addToken(RIGHT_PAREN); break; + case '{': addToken(LEFT_BRACE); break; + case '}': addToken(RIGHT_BRACE); break; + case ',': addToken(COMMA); break; + case '.': addToken(DOT); break; + case '-': addToken(MINUS); break; + case '+': addToken(PLUS); break; + case ';': addToken(SEMICOLON); break; + case '*': addToken(STAR); break; + case '!': + addToken(match('=') ? BANG_EQUAL : BANG); + break; + case '=': + addToken(match('=') ? EQUAL_EQUAL : EQUAL); + break; + case '<': + addToken(match('=') ? LESS_EQUAL : EQUAL); + break; + case '>': + addToken(match('=') ? GREATER_EQUAL : EQUAL); + break; + case '/': + if(match('/')) { + // A comment goes until EOL + while (peek() != '\n' && !isAtEnd()) advance(); + } else if(match('*')) { + multilineComment(); + } else { + addToken(SLASH); + } + break; + case ' ': + case '\r': + case '\t': + break; // Ignore whitespace + case '\n': + line++; + break; + case '"': string(); break; + + default: + if(isDigit(c)) { + number(); + } else if(isAlpha(c)) { + identifier(); + } else { + Lox.error(line, "Unexpected character."); + } + break; + } + } + + private char advance() { + return source.charAt(current++); + } + + private void addToken(TokenType type) { + addToken(type, null); + } + + private void addToken(TokenType type, Object literal) { + String text = source.substring(start, current); + tokens.add(new Token(type, text, literal, line)); + } + + private boolean match(char expected) { + if(isAtEnd()) return false; + if(source.charAt(current) != expected) return false; + current++; + return true; + } + + private char peek() { + if(isAtEnd()) return '\0'; + return source.charAt(current); + } + + private void string() { + while(peek() != '"' && !isAtEnd()) { + if(peek() == '\n') line++; + advance(); + } + + if(isAtEnd()) { + Lox.error(line, "Unterminated string"); + return; + } + + advance(); // Consume the closing '"' + + // Generate token with trimmed quotes + String value = source.substring(start + 1, current - 1); + addToken(STRING, value); + } + + private boolean isDigit(char c) { + return (c >= '0') && (c <= '9'); + } + + private void number() { + while(isDigit(peek())) advance(); + + if(peek() == '.' && isDigit(peekNext())) { + do advance(); + while (isDigit(peek())); + } + addToken(NUMBER, Double.parseDouble(source.substring(start, current))); + } + + private char peekNext() { + if(current + 1 >= source.length()) return '\0'; + return source.charAt(current + 1); + } + + private void identifier() { + while(isAlphaNumeric(peek())) advance(); + String text = source.substring(start, current); + TokenType type = keywords.get(text); + if(type == null) type = IDENTIFIER; + addToken(type); + } + + private boolean isAlpha(char c) { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_'; + } + + private boolean isAlphaNumeric(char c) { + return isAlpha(c) || isDigit(c); + } + + private void multilineComment() { + int start_line = line; + while(!isAtEnd()) { + switch(peek()) { + case '\n': + line++; + break; + case '*': + if (peekNext() == '/') { + advance(); + advance(); + return; + } + break; + } + advance(); + } + Lox.error(start_line, "Unclosed multiline comment"); + } +} diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Token.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Token.java new file mode 100644 index 0000000..eefe886 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Token.java @@ -0,0 +1,24 @@ +package com.craftinginterpreters.lox; + +class Token { + final TokenType type; + final String lexeme; + final Object literal; + final int line; + + public Token(TokenType type, String lexeme, Object literal, int line) { + this.type = type; + this.lexeme = lexeme; + this.literal = literal; + this.line = line; + } + + @Override + public String toString() { + return "Token{" + + "type=" + type + + ", lexeme='" + lexeme + '\'' + + ", literal=" + literal + + '}'; + } +} diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/TokenType.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/TokenType.java new file mode 100644 index 0000000..8d58a5b --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/TokenType.java @@ -0,0 +1,20 @@ +package com.craftinginterpreters.lox; + +enum TokenType { + // Single-character tokens + LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, + COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, + + // One or two character tokens + BANG, BANG_EQUAL, EQUAL, EQUAL_EQUAL, + GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, + + // Literals + IDENTIFIER, STRING, NUMBER, + + // Keywords + AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, + PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, + + EOF +} \ No newline at end of file From 56b3e4d9815df451192b1ddc96d4436be024409b Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Fri, 22 Aug 2025 23:34:37 -0300 Subject: [PATCH 06/12] Rename some notes files --- .../03-the-lox-language/{notes.org => ch03-notes.org} | 0 .../04-scanning/{notes.org => ch04-notes.org} | 0 books/crafting-interpreters/index.org | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename books/crafting-interpreters/03-the-lox-language/{notes.org => ch03-notes.org} (100%) rename books/crafting-interpreters/04-scanning/{notes.org => ch04-notes.org} (100%) diff --git a/books/crafting-interpreters/03-the-lox-language/notes.org b/books/crafting-interpreters/03-the-lox-language/ch03-notes.org similarity index 100% rename from books/crafting-interpreters/03-the-lox-language/notes.org rename to books/crafting-interpreters/03-the-lox-language/ch03-notes.org diff --git a/books/crafting-interpreters/04-scanning/notes.org b/books/crafting-interpreters/04-scanning/ch04-notes.org similarity index 100% rename from books/crafting-interpreters/04-scanning/notes.org rename to books/crafting-interpreters/04-scanning/ch04-notes.org diff --git a/books/crafting-interpreters/index.org b/books/crafting-interpreters/index.org index dea5525..44c6860 100644 --- a/books/crafting-interpreters/index.org +++ b/books/crafting-interpreters/index.org @@ -13,9 +13,9 @@ language interpreters in Java and C. 1. Introduction\\ No notes, just Hello Worlds and a linked list implementation in C. 2. [[./02-a-map-of-the-territory/ch02-notes.org][A Map of the Territory]] -3. [[./03-the-lox-language/notes.org][The Lox Language]]\\ +3. [[./03-the-lox-language/ch03-notes.org][The Lox Language]]\\ Tangles some test files in Lox language. -4. [[./04-scanning/notes.org][Scanning]] +4. [[./04-scanning/ch04-notes.org][Scanning]] * Tooling From 5b0936b1d5cfb1997e4b1f783b73fe9e581a3bef Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Mon, 25 Aug 2025 23:48:42 -0300 Subject: [PATCH 07/12] [CraftingInterpreters] Add notes and exercises from chapter 5 --- .../05-representing-code/ch05-notes.org | 423 ++++++++++++++++++ .../{index.org => README.org} | 1 + .../.idea/runConfigurations/Generate_AST.xml | 10 + .../jlox/.idea/runConfigurations/Run_REPL.xml | 9 + .../.idea/runConfigurations/Run_Test_File.xml | 10 + .../runConfigurations/Test_Pretty_Printer.xml | 9 + .../runConfigurations/Test_RPN_Printer.xml | 9 + .../craftinginterpreters/lox/AstPrinter.java | 55 +++ .../lox/AstRpnPrinter.java | 56 +++ .../com/craftinginterpreters/lox/Expr.java | 71 +++ .../tool/GenerateAst.java | 96 ++++ 11 files changed, 749 insertions(+) create mode 100644 books/crafting-interpreters/05-representing-code/ch05-notes.org rename books/crafting-interpreters/{index.org => README.org} (93%) create mode 100644 books/crafting-interpreters/jlox/.idea/runConfigurations/Generate_AST.xml create mode 100644 books/crafting-interpreters/jlox/.idea/runConfigurations/Run_REPL.xml create mode 100644 books/crafting-interpreters/jlox/.idea/runConfigurations/Run_Test_File.xml create mode 100644 books/crafting-interpreters/jlox/.idea/runConfigurations/Test_Pretty_Printer.xml create mode 100644 books/crafting-interpreters/jlox/.idea/runConfigurations/Test_RPN_Printer.xml create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstRpnPrinter.java create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java diff --git a/books/crafting-interpreters/05-representing-code/ch05-notes.org b/books/crafting-interpreters/05-representing-code/ch05-notes.org new file mode 100644 index 0000000..473ff39 --- /dev/null +++ b/books/crafting-interpreters/05-representing-code/ch05-notes.org @@ -0,0 +1,423 @@ +#+TITLE: Representing Code +#+startup: latexpreview + +The goal here is to program a representation for code. In other words, we take +the tokens and structure them in such a way that we can extract semantics from +it. + +For example, arithmetic expressions can be represented in such a way that +precedence is expressed using a tree, from leaf nodes up toward the root +(post-order traversal). + +Trees are not the only way to do that; we could also generate bytecode, but +we'll use trees in this specific case. + +* Context-free grammars + +In Vieira (2006), a context-free grammar is defined (pp. 156-157) as: + +A grammar $(V, \Sigma, R, P)$, in which every rule has a form $X \rightarrow +\omega$, where $X \in V$ and $\omega \in (V \cup \Sigma)^{*}$. + +Also, $V$ is the language definition's "variables" (nonterminals), while +$\Sigma$ is the alphabet. We also have $(V \cup \Sigma)^{*}$ as the Kleene +closure defining the possible words that can be defined using the rules for the +language's syntax -- consider also that generally we have the symbol $\lambda$ +as the empty word for these grammars. + +For example, consider a grammar $G$ for a language $L(G)$ such that: + +- $V = \{P\}$; +- $\Sigma = \{0, 1\}$; +- $P$ is the initial form of the grammar; +- $R$ contains the single rule: + - $P \rightarrow 0P0\ |\ 1P1\ |\ 0\ |\ 1\ |\ \lambda$ + +Now we can clearly see that we can generate palindrome sentences of zeroes and +ones, with an arbitrary equal amount of those. So our language can be +represented as: + +$L(G) = \{\omega \in \{0, 1\}^{*}\ |\ \omega = \omega^{R}\}$ + +Here are a few words that can be generated from that grammar. + +#+begin_example + (an empty word) +0 +1 +00 +11 +0000 +0110 +1111 +1001 +... +#+end_example + +In the context of our language, the alphabet is our *tokens*, and our set of +rules are our grammar rules for building programs. A valid program in our +language is a program that follows the syntax rules of our grammar. + +Suddenly, theory and practice are not so distant after all, right? + +| Terminology | Lexical grammar | Syntactic grammar | Symbol | +|---------------------+-----------------+-------------------+--------------| +| Alphabet | Characters | Tokens | $\Sigma$ | +| Word | Lexeme/token | Expression | $\omega \in L(G)$ | +| Implemented by... | Scanner | Parser | | + +If we restrict heads to a single symbol, we're defining a context-free +grammar. + +There are more powerful formalisms. For example, unrestricted grammars allow a +sequence of symbols in the head as well as the body (think of how rules expand). + +** Rules for grammars + +Some more nomenclature: + +- *Terminal*: A letter from the grammar's alphabet. +- *Nonterminal*: Reference to another rule in the grammar. + +It is possible to have multiple rules with the same name! When that happens, you +are allowed to pick up the rule that best fits your case. + +John Backus et all created the *Backus-Naur form* (*BNF*) to describe ALGOL 58's +grammar, and so now everyone uses a flavor of BNF tweaked as its own. + +** A grammar for Lox expressions + +For now we are going to worry about: + +- Literals; +- Unary expressions; +- Binary expressions; +- Parentheses. + +Which are enough for expressions such as: + +#+begin_src lox +1 - (2 * 3) < 4 == false +#+end_src + +And the grammar can be defined as follows. Notice that the capitalized names are +terminals that are a single lexeme but the representation may vary. + +#+begin_example +expression -> literal | unary | binary | grouping ; +literal -> NUMBER | STRING | "true" | "false" | "nil" ; +grouping -> "(" expression ")" ; +unary -> ( "-" | "!" ) expression ; +binary -> expression operator expression ; +operator -> "==" | "!=" | "<" | "<=" | ">" | ">=" + | "+" | "-" | "*" | "/" ; +#+end_example + +The grammar is said to be ambiguous, but I could not immediately find what is +its ambiguity. + + +* Working with trees + +** The expression problem + +Ok, this is worth some notes. + +We have declared an abstract class =Expr= and several subclasses related to our +grammar such as =Unary=, =Binary=, etc. That are static subclasses, yet they +still inherit =Expr=. + +So right now we have some operations to do with these types and we need to +relate them to the types. + +Object orientation says that each class could have an implementation for each +operation, so we would implement methods like =interpret=, =resolve=, =analyze= +for each of these classes. But if we create a new rule, we would have to +implement them all over again. + +Functional languages, on the other hand, would have these functions as +standalone and perform pattern matching on these types. But if we create a new +type, we would still have to implement behavior for that type on each function. + +This is called the "expression problem". + +If we had been working with Common Lisp (CLOS specifically) or Julia, we could +solve this with multimethods and dynamic dispatch at the cost of not performing +static type checking (because dispatch happens at runtime) or separate +compilation. + +So we will just slap a design pattern that solves this problem... + +** The Visitor pattern + +First things first, "visitor" is not about visiting nodes; and it is not +necessarily related to traversing trees either. + +Even though we will use classes that are tree-like, that's just a coincidence. + +*What it allows us to do is define all of the behavior for a new operation on a + set of types in a single place, without having to touch the types themselves.* + +So it brings together things from both OO and functional: we dispatch on types, +but at the same time we define behavior at one time and it should work for +everyone involved. And this is done through a layer of indirection. + +Here's the book's example. Suppose we have pastries defined with inheritance. + + +#+begin_src java +abstract class Pastry { + // ... +} + +class Beignet extends Pastry { + // ... +} + +class Cruller extends Pastry { + // ... +} +#+end_src + +Define an interface for the visitors. Each operation is a visitor; distinct +methods were defined here just to evidence that these are statically compiled, +the opposite of overrides (if we were overloading a single visit method, it +would still be statically-compiled). + +#+begin_src java +interface PastryVisitor { + void visitBeignet(Beignet beignet); + void visitCruller(Cruller cruller); +} +#+end_src + +Then implement the =accept= method for each visited class: + +#+begin_src java +abstract class Pastry { + abstract void accept(PastryVisitor visitor); +} + +class Beignet extends Pastry { + @override + void accept(PastryVisitor visitor) { + visitor.visitBeignet(this); + } +} + +class Cruller extends Pastry { + @override + void accept(PastryVisitor visitor) { + visitor.visitCruller(this); + } +} +#+end_src + + +So now, to perform an operation on any kind of Pastry, we call the Pastry's +=accept= method, and through inheritance it will call the proper implementation +for that operation within the class implementing =PastryVisitor=. + +So now we get a way to implement our verbs as proper classes while still +retaining static analysis. + +We could also pass a context on the method =accept=, but we don't really need +that here. + + +* Challenges + +** Producing a gramar without syntax sugar + +Original grammar: + +#+begin_example +expr -> expr ( "(" ( expr ("," expr)* )? ")" | "." IDENTIFIER )+ + | IDENTIFIER + | NUMBER +#+end_example + +Without syntax sugar: + +#+begin_example +expr -> expr +expr -> expr grouping +expr -> IDENTIFIER +expr -> NUMBER + +grouping -> "(" commaseparated ")" + +commaseparated -> expr +commaseparated -> expr "," commaseparated +#+end_example + +This grammar produces an identifier, a number, or a nested, comma-separated, +paren-delimited list of expressions, where each of these expressions may also be +another comma-separated list. + +** Complimentary pattern for functional languages + +The idea here is to have object orientation on a functional language, then! + +I'm gonna do it using Common Lisp because I am not insane.. + +Here's a sketch of the syntax I am aiming for. + +#+begin_src lisp :tangle no +(dclass foo + (bar) + ((say-hello () + (format t (foo-get-bar this))) + (set-name (name) + (foo-set-bar this name)) + (say-goodbye () + (format t "Goodbye!")))) +#+end_src + +We have a few ideas. + +- We define fields within the first list after class name. It is a list of + fields which will comprise the object's state, simply put. +- Each field will have a getter named =CLASSNAME-get-FIELDNAME= and a setter + =CLASSNAME-set-FIELDNAME= (e.g. =foo-get-bar=, =foo-set-bar=). +- We define methods just after the fields list. Each of the methods may be + declared as having no parameters, but they implicitly receive =this= as first + parameter; that parameter is an instance to the current object. These methods + are called with =CLASSNAME-METHODNAME= (e.g. =foo-say-goodbye=) + +How are we going to represent the fields? Well... there is such a thing as +alists and plists in Lisp, and I'm going to use alists because I do want to +change state, otherwise I would have to abstain from using constructs such as +=setf=. + +We are going to represent our objects as alists. =assoc= is going to be our main +vector for retrieving a field. Using =assoc= with =setf= will give us ad-hoc +mutability. + +The rest are macros, which is what Lisp does best. + + +#+begin_src lisp +(defmacro dclass (classname + (&rest dfields) + (&body dmethods)) + `(progn + ;; Generate constructor + (DEFUN ,(intern (format nil "~a-INSTANTIATE" classname)) ,dfields + (LIST ,@(loop for field in dfields + collect `(cons (quote ,field) ,field)))) + ;; Generate getters and setters + ,@(loop for field in dfields + collect + `(DEFUN ,(intern (format nil "~a-GET-~a" classname field)) (THIS) + (CDR (ASSOC (QUOTE ,field) THIS))) + collect + `(DEFUN ,(intern (format nil "~a-SET-~a" classname field)) (THIS VALUE) + (SETF (CDR (ASSOC (QUOTE ,field) THIS)) + VALUE))) + ;; Generate methods + ,@(loop for (method-name method-lambda-list . method-body) in dmethods + collect + `(DEFUN ,(intern (format nil "~a-~a" classname method-name)) + (THIS ,@method-lambda-list) + ,@method-body)))) +#+end_src + +#+RESULTS: +: DCLASS + +And here is an evaluation of the example: + +#+begin_src lisp :results output +(dclass foo + (name age) + ((say-hello () + (format t "Hello, ~a!~%" (foo-get-name this))) + (multiply-age (mult) + (foo-set-age this (* (foo-get-age this) + mult)) + (format t "Age is now ~a~%" (foo-get-age this))) + (say-goodbye () + (format t "Goodbye!~%")))) + +(defparameter *test* (foo-instantiate "Lucas" 31)) + +(foo-say-hello *test*) +(foo-multiply-age *test* 2) +(foo-say-goodbye *test*) +#+end_src + +#+RESULTS: +: Hello, Lucas! +: Age is now 62 +: Goodbye! + +** Writing a pretty printer with reverse polish notation + +Here's my attempt. + +#+begin_src java +package com.craftinginterpreters.lox; + +/// This printer is temporary and may be removed soon + +public class AstRpnPrinter implements Expr.Visitor { + String print(Expr expr) { + return expr.accept(this); + } + + @Override + public String visitBinaryExpr(Expr.Binary expr) { + return expr.left.accept(this) + + " " + + expr.right.accept(this) + + " " + + expr.operator.lexeme; + } + + @Override + public String visitGroupingExpr(Expr.Grouping expr) { + return expr.expression.accept(this); + } + + @Override + public String visitLiteralExpr(Expr.Literal expr) { + if(expr.value == null) return "nil"; + return expr.value.toString(); + } + + @Override + public String visitUnaryExpr(Expr.Unary expr) { + return expr.right.accept(this) + " " + expr.operator.lexeme; + } + + public static void main(String[] args) { + Expr expression = new Expr.Binary( + new Expr.Grouping( + new Expr.Binary( + new Expr.Literal(1), + new Token(TokenType.PLUS, "+", null, 1), + new Expr.Literal(2) + ) + ), + new Token(TokenType.STAR, "*", null, 1), + new Expr.Grouping( + new Expr.Binary( + new Expr.Literal(4), + new Token(TokenType.MINUS, "-", null, 1), + new Expr.Literal(3) + ) + ) + ); + System.out.println(new AstRpnPrinter().print(expression)); + } +} +#+end_src + +Output: + +#+begin_example +1 2 + 4 3 - * +#+end_example + +As expected. diff --git a/books/crafting-interpreters/index.org b/books/crafting-interpreters/README.org similarity index 93% rename from books/crafting-interpreters/index.org rename to books/crafting-interpreters/README.org index 44c6860..7415edf 100644 --- a/books/crafting-interpreters/index.org +++ b/books/crafting-interpreters/README.org @@ -16,6 +16,7 @@ language interpreters in Java and C. 3. [[./03-the-lox-language/ch03-notes.org][The Lox Language]]\\ Tangles some test files in Lox language. 4. [[./04-scanning/ch04-notes.org][Scanning]] +5. [[./05-representing-code/ch05-notes.org][Representing Code]] * Tooling diff --git a/books/crafting-interpreters/jlox/.idea/runConfigurations/Generate_AST.xml b/books/crafting-interpreters/jlox/.idea/runConfigurations/Generate_AST.xml new file mode 100644 index 0000000..5725eae --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/runConfigurations/Generate_AST.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/runConfigurations/Run_REPL.xml b/books/crafting-interpreters/jlox/.idea/runConfigurations/Run_REPL.xml new file mode 100644 index 0000000..76feb5a --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/runConfigurations/Run_REPL.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/runConfigurations/Run_Test_File.xml b/books/crafting-interpreters/jlox/.idea/runConfigurations/Run_Test_File.xml new file mode 100644 index 0000000..4be7baf --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/runConfigurations/Run_Test_File.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/runConfigurations/Test_Pretty_Printer.xml b/books/crafting-interpreters/jlox/.idea/runConfigurations/Test_Pretty_Printer.xml new file mode 100644 index 0000000..25a5882 --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/runConfigurations/Test_Pretty_Printer.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/.idea/runConfigurations/Test_RPN_Printer.xml b/books/crafting-interpreters/jlox/.idea/runConfigurations/Test_RPN_Printer.xml new file mode 100644 index 0000000..7aa2971 --- /dev/null +++ b/books/crafting-interpreters/jlox/.idea/runConfigurations/Test_RPN_Printer.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java new file mode 100644 index 0000000..89a8336 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java @@ -0,0 +1,55 @@ +package com.craftinginterpreters.lox; + +/// This printer is temporary. +/// WARNING: This file is removed from IntelliJ IDEA's compilation configuration by default + +class AstPrinter implements Expr.Visitor { + + String print(Expr expr) { + return expr.accept(this); + } + + @Override + public String visitBinaryExpr(Expr.Binary expr) { + return parenthesize(expr.operator.lexeme, expr.left, expr.right); + } + + @Override + public String visitGroupingExpr(Expr.Grouping expr) { + return parenthesize("group", expr.expression); + } + + @Override + public String visitLiteralExpr(Expr.Literal expr) { + if(expr.value == null) return "nil"; + return expr.value.toString(); + } + + @Override + public String visitUnaryExpr(Expr.Unary expr) { + return parenthesize(expr.operator.lexeme, expr.right); + } + + private String parenthesize(String name, Expr... exprs) { + StringBuilder builder = new StringBuilder(); + builder.append("(") + .append(name); + for(Expr expr : exprs) { + builder.append(" "); + builder.append(expr.accept(this)); + } + builder.append(")"); + return builder.toString(); + } + + public static void main(String[] args) { + Expr expression = new Expr.Binary( + new Expr.Unary( + new Token(TokenType.MINUS, "-", null, 1), + new Expr.Literal(123)), + new Token(TokenType.STAR, "*", null, 1), + new Expr.Grouping( + new Expr.Literal(45.67))); + System.out.println(new AstPrinter().print(expression)); + } +} diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstRpnPrinter.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstRpnPrinter.java new file mode 100644 index 0000000..c70dd37 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/AstRpnPrinter.java @@ -0,0 +1,56 @@ +package com.craftinginterpreters.lox; + +/// This printer is temporary. +/// WARNING: This file is removed from IntelliJ IDEA's compilation configuration by default + +public class AstRpnPrinter implements Expr.Visitor { + String print(Expr expr) { + return expr.accept(this); + } + + @Override + public String visitBinaryExpr(Expr.Binary expr) { + return expr.left.accept(this) + + " " + + expr.right.accept(this) + + " " + + expr.operator.lexeme; + } + + @Override + public String visitGroupingExpr(Expr.Grouping expr) { + return expr.expression.accept(this); + } + + @Override + public String visitLiteralExpr(Expr.Literal expr) { + if(expr.value == null) return "nil"; + return expr.value.toString(); + } + + @Override + public String visitUnaryExpr(Expr.Unary expr) { + return expr.right.accept(this) + " " + expr.operator.lexeme; + } + + public static void main(String[] args) { + Expr expression = new Expr.Binary( + new Expr.Grouping( + new Expr.Binary( + new Expr.Literal(1), + new Token(TokenType.PLUS, "+", null, 1), + new Expr.Literal(2) + ) + ), + new Token(TokenType.STAR, "*", null, 1), + new Expr.Grouping( + new Expr.Binary( + new Expr.Literal(4), + new Token(TokenType.MINUS, "-", null, 1), + new Expr.Literal(3) + ) + ) + ); + System.out.println(new AstRpnPrinter().print(expression)); + } +} diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java new file mode 100644 index 0000000..2665ba3 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java @@ -0,0 +1,71 @@ +package com.craftinginterpreters.lox; + +import java.util.List; + +/// NOTE: This is generated with GenerateAst. +/// Do not edit this file manually. + +abstract class Expr { + interface Visitor { + R visitBinaryExpr(Binary expr); + R visitGroupingExpr(Grouping expr); + R visitLiteralExpr(Literal expr); + R visitUnaryExpr(Unary expr); + } + static class Binary extends Expr { + final Expr left; + final Token operator; + final Expr right; + + Binary(Expr left, Token operator, Expr right) { + this.left = left; + this.operator = operator; + this.right = right; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitBinaryExpr(this); + } + } + static class Grouping extends Expr { + final Expr expression; + + Grouping(Expr expression) { + this.expression = expression; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitGroupingExpr(this); + } + } + static class Literal extends Expr { + final Object value; + + Literal(Object value) { + this.value = value; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitLiteralExpr(this); + } + } + static class Unary extends Expr { + final Token operator; + final Expr right; + + Unary(Token operator, Expr right) { + this.operator = operator; + this.right = right; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitUnaryExpr(this); + } + } + + abstract R accept(Visitor visitor); +} diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java new file mode 100644 index 0000000..5094047 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java @@ -0,0 +1,96 @@ +package com.craftinginterpreters.tool; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +public class GenerateAst { + public static void main(String[] args) throws IOException { + if(args.length != 1) { + System.err.println("Usage: generate_ast "); + System.exit(64); + } + String outputDir = args[0]; + defineAst(outputDir, "Expr", Arrays.asList( + "Binary : Expr left, Token operator, Expr right", + "Grouping : Expr expression", + "Literal : Object value", + "Unary : Token operator, Expr right" + )); + } + + private static void defineAst(String outputDir, String baseName, List types) throws IOException { + String path = outputDir + "/" + baseName + ".java"; + PrintWriter writer = new PrintWriter(path, StandardCharsets.UTF_8); + + writer.println("package com.craftinginterpreters.lox;"); + writer.println(); + writer.println("import java.util.List;"); + writer.println(); + + writer.println("/// NOTE: This is generated with GenerateAst."); + writer.println("/// Do not edit this file manually."); + writer.println(); + + writer.println("abstract class " + baseName + " {"); + + defineVisitor(writer, baseName, types); + + for(String type : types) { + String className = type.split(":")[0].trim(); + String fields = type.split(":")[1].trim(); + defineType(writer, baseName, className, fields); + } + + // Base accept method + writer.println(); + writer.println(" abstract R accept(Visitor visitor);"); + + writer.println("}"); + writer.close(); + } + + private static void defineType(PrintWriter writer, String baseName, String className, String fieldList) { + writer.println(" static class " + className + " extends " + baseName + " {"); + String[] fields = fieldList.split(", "); + + // fields + for(String field : fields) { + writer.println(" final " + field.trim() + ";"); + } + writer.println(); + + // ctor + writer.println(" " + className + "(" + fieldList + ") {"); + + // store params in fields + for(String field : fields) { + String name = field.split(" ")[1]; + writer.println(" this." + name + " = " + name + ";"); + } + writer.println(" }"); + + // visitor pattern + writer.println(); + writer.println(" @Override"); + writer.println(" R accept(Visitor visitor) {"); + writer.println(" return visitor.visit" + className + baseName + "(this);"); + writer.println(" }"); + + writer.println(" }"); + } + + private static void defineVisitor(PrintWriter writer, String baseName, List types) { + writer.println(" interface Visitor {"); + + for(String type : types) { + String typeName = type.split(":")[0].trim(); + writer.println(" R visit" + typeName + baseName + "(" + + typeName + " " + baseName.toLowerCase() + ");"); + } + + writer.println(" }"); + } +} From 1a204f807f1a2718b8932b875acfc8ef07411541 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Fri, 29 Aug 2025 03:03:27 -0300 Subject: [PATCH 08/12] [CraftingInterpreters] Add Chapter 06 up to section 6.3 --- .../06-parsing-expressions/ch06-notes.org | 132 +++++++++++++++ books/crafting-interpreters/README.org | 3 +- .../com/craftinginterpreters/lox/Parser.java | 157 ++++++++++++++++++ 3 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 books/crafting-interpreters/06-parsing-expressions/ch06-notes.org create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java diff --git a/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org b/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org new file mode 100644 index 0000000..37fdddd --- /dev/null +++ b/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org @@ -0,0 +1,132 @@ +#+title: Parsing Expressions +#+startup: latexpreview entitiespretty + +The goal here is to write a parser with decent error handling, coherent internal +structure and the ability to process a sophisticated syntax. + +It looks like it is simpler than we may thing. Also, taking a look at the Dragon +Book might be nice as complimentary material, because theory isn't covered in +depth here. + +* Ambiguity and the parsing game + +Replicating Lox's grammar: + +#+begin_example +expression -> literal | unary | binary | grouping ; +literal -> NUMBER | STRING | "true" | "false" | "nil" ; +grouping -> "(" expression ")" ; +unary -> ( "-" | "!" ) expression ; +binary -> expression operator expression ; +operator -> "==" | "!=" | "<" | "<=" | ">" | ">=" + | "+" | "-" | "*" | "/" ; +#+end_example + +For an expression such as + +#+begin_example +6 / 3 - 1 +#+end_example + +If we had no order of precedence, we might as well have two different ways to +generate this string: we start by picking the =binary= rule within +expression. And either our right expression is =3 - 1=, or our left expression +is =6 / 3=. This generates two different syntax trees! + +So we need to have rules of *precedence* and *associativity*. + +- Precedence relates to groups of different operators, determining which should + be evaluated first. Operators with high precedence (evaluated first) are + "bound tighter". There are also operators with "no precedence", so mixing them + without explicit grouping will be a syntax error. +- Associativity relates to groups of equal operators, determining direction of + associativity. they may be "left-associative", "right-associative" or + "non-associative" (using it more than once on the same sequence being an + error). E.g. minus is left-associative; assignment is right-associative. + +Here is Lox's associativity table: + +| Name | Operators | Associates | +|-------------+-----------+------------| +| Equality | ~==~ ~!=~ | Left | +| Comparisons | > >= < <= | Left | +| Term | - + | Left | +| Factor | / * | Left | +| Unary | - ! | Right | + +Some parser generators let you add some precedence metadata to some tokens so +you are allowed to keep the grammar ambiguous. But what we can also do here is +stratify our grammar so we can have separate rules for the operators, and then +have them ordered. + +So from lower to higher order of precedence: + +#+begin_example +expression -> equality ; +equality -> comparison ( ( "!=" | "==" ) comparison )* ; +comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ; +term -> factor ( ( "-" | "+" ) factor )* ; +factor -> unary ( ( "/" | "*" ) unary )* ; +unary -> ( "!" | "-" ) unary + | primary ; +primary -> NUMBER | STRING | "true" | "false" | "nil" + | "(" expression ")" ; +#+end_example + +So any rule matches itself and anything else below it: =unary= matches both +=unary= and =primary=; while =term= matches =term=, =factor=, =unary=, +=primary=... And so on. + +Rules such as =factor= could be defined as *left-recursive*, but the kind of +parser we're using has trouble with left-recursion like that. We would end up +with an infinite loop since we wouldn't have an initial "anchor" of sorts for +our expression, a way to terminate it. + +Notice how =factor= is left-associative but it makes no difference for +multiplication and division. That idea of definition is extended to the upper +rules. + +And by the way, being left-associative means that the leftmost expressions are +or are closer to a branch's leaves; see the implementation for =equality= and +the diagram on the book (pp. 87). + +* Recursive Descent Parsing + +There are many parsing techniques, such as LL(k), LR(1), LALR, parser +combinators, Earley parsers, the shunting yard algorithm, and someone told me +about Pratt parsers the other day. + +We are going to use recursive descent parsing. This the simplest way to build +parsers, and won't require us to use parser generators (such as Yacc, Bison or +ANTLR). + +Even though they are simple, they are also fast, robust, and support +sophisticated error handling. GCC, V8 and Roslyn (C# bootstrapped compiler) all +use recursive descent. + +It is a *top-down parser*, since it starts at the most general rule down to the +simplest token terminators. In contrast, LR parsers start from the primary +expressions, composing them into larger structures. + +It is basically a literal translation of each parser rule into imperative code, +where each rule would be a function. + +One note: Recursive descent basically walks down our grammar, so the primary +expressions are at the bottom. But conversely, rule *precedence* is higher at +the LOWEST levels; see how *factor* comes after *term*? That's because +multiplication and division have higher precedence than sum and subtraction. + +Here is a rough translation of the grammar's syntax into code. + +| Grammar notation | Code representation | Meaning | +|------------------+-----------------------------------+----------------------------| +| Terminal | Code to match and consume a token | Single token | +| Nonterminal | Call to that rule's function | Rule | +| \vert | ~if~ or ~switch~ statement | One rule or other | +| * or + | ~while~ or ~for~ loop | Zero or more / One or more | +| ? | ~if~ statement | One or none | + +Recursive descent parsing is also under the category of *predictive parsers*, +since it looks ahead at the tokens to decide what to do next. + + diff --git a/books/crafting-interpreters/README.org b/books/crafting-interpreters/README.org index 7415edf..6cd3c65 100644 --- a/books/crafting-interpreters/README.org +++ b/books/crafting-interpreters/README.org @@ -17,7 +17,8 @@ language interpreters in Java and C. Tangles some test files in Lox language. 4. [[./04-scanning/ch04-notes.org][Scanning]] 5. [[./05-representing-code/ch05-notes.org][Representing Code]] - +6. [[./06-parsing-expressions/ch06-notes.org][Parsing Expressions]] + * Tooling All Java code was built using IntelliJ IDEA Community Edition because I'm not diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java new file mode 100644 index 0000000..1cbdc5f --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java @@ -0,0 +1,157 @@ +package com.craftinginterpreters.lox; + +import java.util.List; +import static com.craftinginterpreters.lox.TokenType.*; + +/// Recursive-descent parser for Lox. +class Parser { + private final List tokens; + private int current = 0; + + /* Constructors */ + Parser(List tokens) { + this.tokens = tokens; + } + + /* Helper functions */ + /// Matches a set of tokens. If any of them is found, + /// it is consumed and `true` is returned. + private Boolean match(TokenType... types) { + for(TokenType type : types) { + if(check(type)) { + advance(); + return true; + } + } + return false; + } + + /// Checks for a current token. Never consumes. + private Boolean check(TokenType type) { + if(isAtEnd()) return false; + return peek().type == type; + } + + /// Consumes the current token and returns it. + private Token advance() { + if(!isAtEnd()) current++; + return previous(); + } + + /// Checks if we ran out of tokens to parse. + private Boolean isAtEnd() { + return peek().type == EOF; + } + + /// Returns the current token that we have yet to consume. + private Token peek() { + return tokens.get(current); + } + + /// Returns the most recently consumed token. + private Token previous() { + return tokens.get(current - 1); + } + + /* Grammar rules */ + + /// Grammar rule for expressions in general. + /// ``` + /// expression -> equality ; + /// ``` + private Expr expression() { + return equality(); + } + + /// Grammar rule for equalities or higher precedence. + /// ``` + /// equality -> comparison ( ( "!=" | "==" ) comparison )* ; + /// ``` + private Expr equality() { + Expr expr = comparison(); + while(match(BANG_EQUAL, EQUAL_EQUAL)) { + Token operator = previous(); + Expr right = comparison(); + expr = new Expr.Binary(expr, operator, right); + } + return expr; + } + + /// Grammar rule for comparisons or higher precedence. + /// ``` + /// comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ; + /// ``` + private Expr comparison() { + Expr expr = term(); + while(match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) { + Token operator = previous(); + Expr right = term(); + expr = new Expr.Binary(expr, operator, right); + } + return expr; + } + + /// Grammar rule for addition and subtraction, or higher precedence. + /// ``` + /// term -> factor ( ( "-" | "+" ) factor )* ; + /// ``` + private Expr term() { + Expr expr = factor(); + while(match(MINUS, PLUS)) { + Token operator = previous(); + Expr right = factor(); + expr = new Expr.Binary(expr, operator, right); + } + return expr; + } + + /// Grammar rule for multiplication and division, or higher precedence. + /// ``` + /// factor -> unary ( ( "/" | "*" ) unary )* ; + /// ``` + private Expr factor() { + Expr expr = unary(); + while(match(SLASH, STAR)) { + Token operator = previous(); + Expr right = unary(); + expr = new Expr.Binary(expr, operator, right); + } + return expr; + } + + /// Grammar rule for unary operations, or higher precedence. + /// ``` + /// unary -> ( "!" | "-" ) unary + /// | primary ; + /// ``` + private Expr unary() { + if(match(BANG, MINUS)) { + Token operator = previous(); + Expr right = unary(); + return new Expr.Unary(operator, right); + } + return primary(); + } + + /// Grammar rule for primary expressions, the highest level + /// of precedence. + /// ``` + /// primary -> NUMBER | STRING | "true" | "false" | "nil" + /// | "(" expression ")" ; + /// ``` + private Expr primary() { + if(match(FALSE)) return new Expr.Literal(false); + if(match(TRUE)) return new Expr.Literal(true); + if(match(NIL)) return new Expr.Literal(null); + + if(match(NUMBER, STRING)) { + return new Expr.Literal(previous().literal); + } + + if(match(LEFT_PAREN)) { + Expr expr = expression(); + consume(RIGHT_PAREN, "Expected ')' after expression."); + return new Expr.Grouping(expr); + } + } +} From edf7d713e998afd488ea6f3525157107af982fa3 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Sat, 30 Aug 2025 19:36:46 -0300 Subject: [PATCH 09/12] [CraftingInterpreters] Finish implementation of CH06 (no exercises) --- .../06-parsing-expressions/ch06-notes.org | 68 +++++++++++++++++++ .../com/craftinginterpreters/lox/Lox.java | 17 ++++- .../com/craftinginterpreters/lox/Parser.java | 54 ++++++++++++++- 3 files changed, 135 insertions(+), 4 deletions(-) diff --git a/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org b/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org index 37fdddd..d6b2937 100644 --- a/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org +++ b/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org @@ -130,3 +130,71 @@ Recursive descent parsing is also under the category of *predictive parsers*, since it looks ahead at the tokens to decide what to do next. +* Syntax errors + +Parsers must do two things: produce a syntax tree from valid code, but also +detect errors and tell the user about mistakes, when given an invalid sequence +of tokens. And that last part must not be underestimated. + +When a parser runs into a syntax error, it must detect and report the error, +while also avoiding crashing or hanging. + +This is because, while the code may be invalid, it is still a valid input to the +compiler. + +A decent parser sould also be fast, report as many distinct errors as there are, +and minimize cascaded errors. + +The way that parsers respond to errors is called *error recovery*. + +** Panic mode error recovery + +Basically, it is a technique which, once a parser reaches an error, it enters +panic mode immediately. This means that at least one token doesn't make sense +given the grammar. + +After reaching panic mode, it does *synchronization*, which means getting its +state, and then align the sequence of forthcoming tokens such that the next +token *DOES* match the rule being parsed. + +In other words, we select some rule that will mark the synchronization point, +then the parser fixes its parsing state, jumping back out of nested calls, until +it gets back to that rule which is the synchronization point. Then, it discards +tokens until it reaches one that can appear at that point. + +Additional syntax errors in those syntax errors aren't reported, but this also +discards falsely-reported cascaded errors that may come from the discarded +tokens as well. + +The traditional place to do these synchronization points is between +statements. Since we don't have statements yet, we won't synchronize now, but +we'll get things ready for when the time comes. + +*** Error production error recovery + +Another way to handle errors: *error production*. + +Basically augmenting the syntax with rules that match errors. The parser parses +it, but reports it as an error instead of adding it to the syntax tree. + +This is especially useful to help users fix mistakes when writing erroneous +code, so more mature parsers will make use of it. + +** Synchronizing a recursive descent parser + +The key here is that we end up using Java's own call stack to keep track of what +the parser is doing; so each rule being parsed is a call frame on the stack. To +reset the state, we clear all those frames. Which means... raising an exception +that is handled through a try-catch block at our synchronization point! + +And to synchronize and move the parser state to the next statement, we just need +to keep track of the next semicolon, which might be ending the current +statement. Notice that this isn't perfect synchronization; we might be ending +prematurely a =for= statement, for example. But this is OK because any errors +that come after the first one is just a "best effort" scenario. + +Oh, and of course, certain keywords (=class=, =for=, =fun=, =if=, =print=, +=return=, =var=, =while=) are also considered boundaries for statements. If we +reached them, we stop advancing our tokens. + + diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java index 3c356e9..2005b22 100644 --- a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java @@ -43,16 +43,27 @@ private static void runPrompt() throws IOException { private static void run(String source) { Scanner scanner = new Scanner(source); List tokens = scanner.scanTokens(); + Parser parser = new Parser(tokens); + Expr expression = parser.parse(); - for(Token token : tokens) { - System.out.println(token); - } + if(hadError) return; + + System.out.println(new AstPrinter().print(expression)); + System.out.println(new AstRpnPrinter().print(expression)); } static void error(int line, String message) { report(line, "", message); } + static void error(Token token, String message) { + if(token.type == TokenType.EOF) { + report(token.line, " at end", message); + return; + } + report(token.line, " at '" + token.lexeme + "'", message); + } + private static void report(int line, String where, String message) { System.err.println("[line " + line + "] Error " + where + ": " + message); hadError = true; diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java index 1cbdc5f..d953a4c 100644 --- a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java @@ -5,14 +5,30 @@ /// Recursive-descent parser for Lox. class Parser { + /// Simple sentinel class for unwinding the parser. + private static class ParseError extends RuntimeException {} + private final List tokens; private int current = 0; - /* Constructors */ + /* Constructors and entry point */ + + /// Constructor for parser. Parser(List tokens) { this.tokens = tokens; } + /// Parse the given list of tokens stored within parser. + /// Returns an abstract syntax tree on success, and `null` on error. + /// TODO: Revisit this when adding statements. + Expr parse() { + try { + return expression(); + } catch(ParseError error) { + return null; + } + } + /* Helper functions */ /// Matches a set of tokens. If any of them is found, /// it is consumed and `true` is returned. @@ -53,6 +69,40 @@ private Token previous() { return tokens.get(current - 1); } + /// Checks if the next token is of the expected type. + /// If so, consumes. If not, report an error. + private Token consume(TokenType type, String message) { + if(check(type)) return advance(); + throw error(peek(), message); + } + + /// Returns a parse error when a wrong token is found. + private ParseError error(Token token, String message) { + Lox.error(token, message); + return new ParseError(); + } + + /// Synchronizes the parser state by advancing to the next statement. + /// Used after a syntax error is encountered to help unwinding the state. + private void synchronize() { + advance(); + while(!isAtEnd()) { + if(previous().type == SEMICOLON) return; + switch(peek().type) { + case CLASS: + case FOR: + case FUN: + case IF: + case PRINT: + case RETURN: + case VAR: + case WHILE: + return; + } + advance(); + } + } + /* Grammar rules */ /// Grammar rule for expressions in general. @@ -153,5 +203,7 @@ private Expr primary() { consume(RIGHT_PAREN, "Expected ')' after expression."); return new Expr.Grouping(expr); } + + throw error(peek(), "Expected expression."); } } From 58e1e7ba217a087343bfa4adce2aaed31429e8ab Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Mon, 1 Sep 2025 21:44:33 -0300 Subject: [PATCH 10/12] [CraftingInterpreters] Lazy pertial answers for CH06 challenges --- .../06-parsing-expressions/ch06-notes.org | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org b/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org index d6b2937..1b59d8c 100644 --- a/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org +++ b/books/crafting-interpreters/06-parsing-expressions/ch06-notes.org @@ -198,3 +198,43 @@ Oh, and of course, certain keywords (=class=, =for=, =fun=, =if=, =print=, reached them, we stop advancing our tokens. +* Challenges + +I'm lazy, so I don't wanna do these right now. I will just write down what I +think should be done. + +** Comma expressions + +Don't wanna do this right now, but I think that maybe I could add a rule at the +expression level, leveraging =equality= there. + +** Ternary operator + +Don't wanna do this either. + +But there shouldn't be any level of precedence between =?= and =:= since they're +part of the same rule, and it should be *right-associative* so it can represent +an if-elseif-elseif-else by chaining ternaries. Otherwise we would have a really +bad case of [[http://www.phpsadness.com/sad/30][PHP ternary]]. + +** Error productions to handle binary operators at left + +That's easy but I don't wanna also + + +* Designo note: Logic versus history + +This part comments on how bitwise operators have lower precedence than equality +on C and how it makes things weird. + +Then it talks about language design from a viewpoint of someone who already +knows other languages: It really helps newcomers to learn about your language if +you use concepts that make the most sense, and reuse concepts that already +exist, and that goes for some operator precedences too. + +It helps the with the language getting known and being easier to pick up. + +"It's often better to make the most of what users already know". + +Still, new stuff is important for a new language also. + From a460805a4705a3e088d5872fbcb5570b289dab80 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Sat, 13 Sep 2025 18:23:05 -0300 Subject: [PATCH 11/12] [CraftingInterpreters] Add Chapter 7 notes --- .../07-evaluating-expressions/ch07-notes.org | 143 ++++++++++++++++++ books/crafting-interpreters/README.org | 1 + .../jlox/examples/test.lox | 3 +- .../craftinginterpreters/lox/Interpreter.java | 136 +++++++++++++++++ .../com/craftinginterpreters/lox/Lox.java | 14 +- .../lox/RuntimeError.java | 10 ++ 6 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 books/crafting-interpreters/07-evaluating-expressions/ch07-notes.org create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java create mode 100644 books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/RuntimeError.java diff --git a/books/crafting-interpreters/07-evaluating-expressions/ch07-notes.org b/books/crafting-interpreters/07-evaluating-expressions/ch07-notes.org new file mode 100644 index 0000000..890c7da --- /dev/null +++ b/books/crafting-interpreters/07-evaluating-expressions/ch07-notes.org @@ -0,0 +1,143 @@ +#+title: Evaluating Expressions +#+startup: latexpreview entitiespretty + +* Representing values + +Here, the user will see the objects as Lox values, but we'll use underlying +values from the language used during implementation -- namely, Java. We'll also +use the `java.lang.Object` class to help us do some reflection on runtime and +dispatch operations depending on types, by using the =instanceof= +operator. Plus, we can use Java's own boxed types for representation: + +| Lox | Corresponding Java type | +|-----------+-------------------------| +| Any value | =Object= | +| =nil= | =null= | +| Boolean | =Boolean= | +| Number | =Double= | +| String | =String= | + +More work will be needed for other stuff such as functions, classes and +instances, but that's for later. + +Oh, and also -- Memory management and garbage collection is the JVM's job for +this implementation. + +* Evaluating Expressions + +We won't be using the Gang of Four's interpreter design pattern, which would +stuff behavior onto the syntax tree objects so they interpret themselves. + +Instead, we'll just abuse our Visitor pattern that already exists. + +We also do a *post-order traversal* here, since we can't evaluate expression +nodes until their children are already evaluated. + +** Evaluating literals + +Literals are almost values, but not quite; literals appear on the source code, +and are from the parser's domain. Values, on the other hand, belong to the +interpreter, being a concept of it, and also part of the runtime. + +In our case, we took advantage of the scanning process and already produced +values for literals on that step. + +** Evaluating parentheses + +Some languages don't define a node for a parenthesized expression with a child +node to the expression itself, like we do. + +But here, we're doing this because we'll need it later to work with assignments. + +** Evaluating unary expressions + +Since we don't really know if =expr.right= is a number, we have to cast to +=double= at runtime when defining the "minus" operator. And that is the kernel +of what makes Lox dynamically typed: this resolution happens at runtime. + +** Truthiness and falsiness + +Even though we don't do implicit conversions, we introduce truthiness and +falsiness to non-boolean values. + +Lox follows Ruby's rule: =false= and =nil= are falsey. Everything else is +truthy. + +** Evaluating binary operators + +Notice that we're introducing left-to-right evaluation and that this evaluation +order can introduce side effects. This is on purpose. + +The PLUS operator here is overloaded for both arithmetic and string +concatenation. This overload can make the language more approachable to +newcomers. + +Equality operators support operands of any type, even mixed ones. So the +equality logic is hoisted separately, just like truthiness. + +Equality leverages =java.lang.Object.equal()= to check for equality, so values +such as =NaN= (that generally appear for =(0 / 0)=) will not appear here, since +this method does not follow IEEE 754 entirely for doubles. + +In the end, apart from that, we just handle situations where =a= is equal to +=nil= so we don't get an exception when calling =.equal()=. + +* Runtime errors + +Right now the casts we introduced ensure that, when an operand is of invalid +type, a =ClassCastException= is thrown, and that will unwind the stack for us. + +But that's not desirable since the usage of the JVM should be just an +implementation detail, not explicit to the user. + +So we need to capture that error and basically give a meaningful message while +still keeping the REPL running, instead of, say, killing everything at point. + +* Challenges + +** Would you extend Lox to support comparing other types? + +Absolutely NOT, but I think it would be useful to have a way to explicitly cast +some values to others. For example, casting a boolean to integer could be very +useful on certain situations, and if we had some sort of foreign function +interface with C, maybe it would be interesting to cast objects to obtain +pointers, but that would also imply a lot of care, true integer values, maybe +even a proper pointer type so we can avoid many of the pitfalls on +this... perharps even proper classes defined only for that. + +** Support string and number concatenation + +All I had to do on =visitBinaryExpr= was add this case prior to throwing the +=RuntimeError=: + +#+begin_src java +// in visitBinaryExpr, at end of the PLUS case: +if((left instanceof String && right instanceof Double) + || (left instanceof Double && right instanceof String)){ + return stringify(left) + stringify(right); +} +#+end_src + +** About division by zero + +Right now division by zero complies with IEEE 754 and returns =NaN= on =(0 / 0)= +case, and =Infinity= otherwise. + +Personally I think that it should throw an error, so we need to compare the +dividend with zero. Unfortunately, that's not really that obvious for Doubles. + +What I'm going to do here is use a very small value (let's call it \epsilon), which +will be =2 * Double.MIN_VALUE=. Of course, this means that zero should be within +a certain threshold of an acceptable value, related to how Doubles can represent +very small numbers. + +Here's what I didn in =visitBinaryExpr=: + +#+begin_src java +case SLASH: + checkNumberOperands(expr.operator, left, right); + if(Math.abs((double)right) < 2 * Double.MIN_VALUE) { + throw new RuntimeError(expr.operator, "Division by zero"); + } + return (double)left / (double)right; +#+end_src diff --git a/books/crafting-interpreters/README.org b/books/crafting-interpreters/README.org index 6cd3c65..5780c4b 100644 --- a/books/crafting-interpreters/README.org +++ b/books/crafting-interpreters/README.org @@ -18,6 +18,7 @@ language interpreters in Java and C. 4. [[./04-scanning/ch04-notes.org][Scanning]] 5. [[./05-representing-code/ch05-notes.org][Representing Code]] 6. [[./06-parsing-expressions/ch06-notes.org][Parsing Expressions]] +7. [[./07-evaluating-expressions/ch07-notes.org][Evaluating Expressions]] * Tooling diff --git a/books/crafting-interpreters/jlox/examples/test.lox b/books/crafting-interpreters/jlox/examples/test.lox index a8da0ee..65980ed 100644 --- a/books/crafting-interpreters/jlox/examples/test.lox +++ b/books/crafting-interpreters/jlox/examples/test.lox @@ -2,7 +2,6 @@ multiline comment */ -var foo = 2; // This is another comment @@ -11,3 +10,5 @@ var foo = 2; multiline comment */ + +(1 + 2) == 3 \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java new file mode 100644 index 0000000..cb5dc56 --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java @@ -0,0 +1,136 @@ +package com.craftinginterpreters.lox; + +class Interpreter implements Expr.Visitor { + + /* Visitor methods */ + + @Override + public Object visitBinaryExpr(Expr.Binary expr) { + Object left = evaluate(expr.left); + Object right = evaluate(expr.right); + + switch(expr.operator.type) { + case GREATER: + checkNumberOperands(expr.operator, left, right); + return (double)left > (double)right; + case GREATER_EQUAL: + checkNumberOperands(expr.operator, left, right); + return (double)left >= (double)right; + case LESS: + checkNumberOperands(expr.operator, left, right); + return (double)left < (double)right; + case LESS_EQUAL: + checkNumberOperands(expr.operator, left, right); + return (double)left <= (double)right; + case BANG_EQUAL: + return !isEqual(left, right); + case EQUAL_EQUAL: + return isEqual(left, right); + case MINUS: + checkNumberOperands(expr.operator, left, right); + return (double)left - (double)right; + case SLASH: + checkNumberOperands(expr.operator, left, right); + if(Math.abs((double)right) < 2 * Double.MIN_VALUE) { + throw new RuntimeError(expr.operator, "Division by zero"); + } + return (double)left / (double)right; + case STAR: + checkNumberOperands(expr.operator, left, right); + return (double)left * (double)right; + case PLUS: + if(left instanceof Double && right instanceof Double) { + return (double)left + (double)right; + } + if(left instanceof String && right instanceof String) { + return left + (String)right; + } + // String and number concatenation + if((left instanceof String && right instanceof Double) + || (left instanceof Double && right instanceof String)){ + return stringify(left) + stringify(right); + } + throw new RuntimeError(expr.operator, "Operands must be two numbers or two strings."); + } + + // Unreachable + return null; + } + + @Override + public Object visitGroupingExpr(Expr.Grouping expr) { + return evaluate(expr.expression); + } + + @Override + public Object visitLiteralExpr(Expr.Literal expr) { + return expr.value; + } + + @Override + public Object visitUnaryExpr(Expr.Unary expr) { + Object right = evaluate(expr.right); + + switch(expr.operator.type) { + case MINUS: + checkNumberOperand(expr.operator, right); + return -(double)right; + case BANG: + return !isTruthy(right); + } + + // Unreachable + return null; + } + + /* Helper methods */ + + private Object evaluate(Expr expr) { + return expr.accept(this); + } + + private boolean isTruthy(Object object) { + if(object == null) return false; + if(object instanceof Boolean) return (boolean)object; + return true; + } + + private boolean isEqual(Object a, Object b) { + if(a == null && b == null) return true; + if(a == null) return false; + return a.equals(b); + } + + private void checkNumberOperand(Token operator, Object operand) { + if(operand instanceof Double) return; + throw new RuntimeError(operator, "Operand must be a number."); + } + + private void checkNumberOperands(Token operator, Object left, Object right) { + if(left instanceof Double && right instanceof Double) return; + throw new RuntimeError(operator, "Operands must be numbers."); + } + + private String stringify(Object object) { + if(object == null) return "nil"; + if(object instanceof Double) { + String text = object.toString(); + if(text.endsWith(".0")) { + text = text.substring(0, text.length() - 2); + } + return text; + } + return object.toString(); + } + + /* Interpreter's public API */ + + void interpret(Expr expression) { + try { + Object value = evaluate(expression); + System.out.println(stringify(value)); + } catch(RuntimeError error) { + Lox.runtimeError(error); + } + } +} diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java index 2005b22..229ed2f 100644 --- a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java @@ -9,7 +9,9 @@ import java.util.List; public class Lox { + private static final Interpreter interpreter = new Interpreter(); static boolean hadError = false; + static boolean hadRuntimeError = false; public static void main(String[] args) throws IOException { if(args.length > 1) { @@ -26,6 +28,7 @@ private static void runFile(String path) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(path)); run(new String(bytes, Charset.defaultCharset())); if(hadError) System.exit(65); + if(hadRuntimeError) System.exit(70); } private static void runPrompt() throws IOException { @@ -48,8 +51,9 @@ private static void run(String source) { if(hadError) return; - System.out.println(new AstPrinter().print(expression)); - System.out.println(new AstRpnPrinter().print(expression)); + //System.out.println(new AstPrinter().print(expression)); + //System.out.println(new AstRpnPrinter().print(expression)); + interpreter.interpret(expression); } static void error(int line, String message) { @@ -64,6 +68,12 @@ static void error(Token token, String message) { report(token.line, " at '" + token.lexeme + "'", message); } + static void runtimeError(RuntimeError error) { + System.err.println(error.getMessage() + + "\n[line " + error.token.line + "]"); + hadRuntimeError = true; + } + private static void report(int line, String where, String message) { System.err.println("[line " + line + "] Error " + where + ": " + message); hadError = true; diff --git a/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/RuntimeError.java b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/RuntimeError.java new file mode 100644 index 0000000..017865b --- /dev/null +++ b/books/crafting-interpreters/jlox/src/main/java/com/craftinginterpreters/lox/RuntimeError.java @@ -0,0 +1,10 @@ +package com.craftinginterpreters.lox; + +class RuntimeError extends RuntimeException { + final Token token; + + RuntimeError(Token token, String message) { + super(message); + this.token = token; + } +} From 66a56fdf59d3f3c70c2672faa02abe7527a74999 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Thu, 25 Dec 2025 23:45:15 -0300 Subject: [PATCH 12/12] [CraftingInterpreters] Start Chapter 8 --- .../08-statements-and-state/ch08-notes.org | 13 +++++++++++++ books/crafting-interpreters/README.org | 1 + .../crafting-interpreters/jlox/.idea/encodings.xml | 1 + books/crafting-interpreters/jlox/pom.xml | 12 ++++++++++++ 4 files changed, 27 insertions(+) create mode 100644 books/crafting-interpreters/08-statements-and-state/ch08-notes.org diff --git a/books/crafting-interpreters/08-statements-and-state/ch08-notes.org b/books/crafting-interpreters/08-statements-and-state/ch08-notes.org new file mode 100644 index 0000000..46e5c9a --- /dev/null +++ b/books/crafting-interpreters/08-statements-and-state/ch08-notes.org @@ -0,0 +1,13 @@ +#+TITLE: Statements and State + +Statements are supposed to produce side-effects. + +Assignments on most languages produce no value and therefore are never +expressions. + +I do recall, though, that when I was creating Majestic, I did specify +definitions as if they were expressions, since they return values -- the symbol +that gives name to the value, that is. + +Anyway, here we'll add assignment, prints, blocks and local scope. + diff --git a/books/crafting-interpreters/README.org b/books/crafting-interpreters/README.org index 5780c4b..8e31086 100644 --- a/books/crafting-interpreters/README.org +++ b/books/crafting-interpreters/README.org @@ -19,6 +19,7 @@ language interpreters in Java and C. 5. [[./05-representing-code/ch05-notes.org][Representing Code]] 6. [[./06-parsing-expressions/ch06-notes.org][Parsing Expressions]] 7. [[./07-evaluating-expressions/ch07-notes.org][Evaluating Expressions]] +8. [[./08-statements-and-state/ch08-notes.org][Statements and State]] * Tooling diff --git a/books/crafting-interpreters/jlox/.idea/encodings.xml b/books/crafting-interpreters/jlox/.idea/encodings.xml index aa00ffa..d44d19f 100644 --- a/books/crafting-interpreters/jlox/.idea/encodings.xml +++ b/books/crafting-interpreters/jlox/.idea/encodings.xml @@ -2,6 +2,7 @@ + \ No newline at end of file diff --git a/books/crafting-interpreters/jlox/pom.xml b/books/crafting-interpreters/jlox/pom.xml index a87ee98..cdff11b 100644 --- a/books/crafting-interpreters/jlox/pom.xml +++ b/books/crafting-interpreters/jlox/pom.xml @@ -14,4 +14,16 @@ UTF-8 + + + + src/main/java/com/craftinginterpreters/lox + + AstPrinter.java + AstRpnPrinter.java + + + + + \ No newline at end of file