Compare commits
11 commits
trunk
...
first-inte
| Author | SHA1 | Date | |
|---|---|---|---|
| 40b17625e2 | |||
| 9132dd2d66 | |||
| f36482afc7 | |||
| 2f32c928d6 | |||
| a44abe5e73 | |||
| 49329273d3 | |||
| 26bb0a8df1 | |||
| 9b280dca92 | |||
| 10d2f10c2f | |||
| b5426dcc3f | |||
| 16e8ec36e1 |
15 changed files with 787 additions and 0 deletions
3
first-interpreter/.gitignore
vendored
Normal file
3
first-interpreter/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
_build
|
||||||
|
interpreter
|
||||||
|
interpreter.dSYM
|
||||||
38
first-interpreter/Makefile
Normal file
38
first-interpreter/Makefile
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
ifndef VERBOSE
|
||||||
|
.SILENT:
|
||||||
|
endif
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -ggdb
|
||||||
|
BUILD_DIR = _build
|
||||||
|
|
||||||
|
.DEFAULT_TARGET = interpreter
|
||||||
|
|
||||||
|
interpreter: src/main.c $(BUILD_DIR)/run.o
|
||||||
|
$(CC) $(CFLAGS) $< -o $@ $(BUILD_DIR)/run.o $(BUILD_DIR)/ast.o $(BUILD_DIR)/lex.o $(BUILD_DIR)/parse.o $(BUILD_DIR)/execute.o
|
||||||
|
|
||||||
|
$(BUILD_DIR)/ast.o: src/ast.c src/ast.h
|
||||||
|
mkdir -p $(BUILD_DIR)/
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/lex.o: src/lex.c src/lex.h
|
||||||
|
mkdir -p $(BUILD_DIR)/
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/parse.o: src/parse.c src/parse.h $(BUILD_DIR)/ast.o $(BUILD_DIR)/lex.o
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/execute.o: src/execute.c src/execute.h $(BUILD_DIR)/ast.o
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD_DIR)/run.o: src/run.c src/run.h $(BUILD_DIR)/ast.o $(BUILD_DIR)/lex.o $(BUILD_DIR)/parse.o $(BUILD_DIR)/execute.o
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
.PHONY clean run:
|
||||||
|
clean:
|
||||||
|
rm -rf _build
|
||||||
|
rm -rf interpreter
|
||||||
|
rm -rf interpreter.dSYM
|
||||||
|
|
||||||
|
run: interpreter
|
||||||
|
./interpreter program.code
|
||||||
9
first-interpreter/program.code
Normal file
9
first-interpreter/program.code
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
sum = 0
|
||||||
|
counter = 10
|
||||||
|
|
||||||
|
while counter {
|
||||||
|
sum = add(sum, counter)
|
||||||
|
counter = add(counter, negate(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
print(sum)
|
||||||
37
first-interpreter/readme.md
Normal file
37
first-interpreter/readme.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# My first interpreter
|
||||||
|
|
||||||
|
Code for a very simple interpreter.
|
||||||
|
|
||||||
|
Accompanying blogpost: https://alloca.space/blog/my-first-interpreter.html
|
||||||
|
|
||||||
|
## Build and run
|
||||||
|
|
||||||
|
Requires: gcc, make
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Program
|
||||||
|
|
||||||
|
```go
|
||||||
|
sum = 0
|
||||||
|
counter = 10
|
||||||
|
|
||||||
|
while counter {
|
||||||
|
sum = add(sum, counter)
|
||||||
|
counter = add(counter, negate(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
print(sum)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `main.c` - entry point. Reads user input and calls the interpreter
|
||||||
|
- `run.c` - glue code. invokes the interpreter stages.
|
||||||
|
- `ast.h` - AST definitions
|
||||||
|
- `lex.h` - Tokens definitions
|
||||||
|
- `lex.c` - Stage 1 - Lexing: Text -> Tokens
|
||||||
|
- `parsing.c` - Stage 2 - Parsing: Tokens -> Ast
|
||||||
|
- `execute.c` - Stage 3 - Execute: Ast -> Output
|
||||||
57
first-interpreter/src/ast.c
Normal file
57
first-interpreter/src/ast.c
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#include "ast.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void print_expr(Expr expr) {
|
||||||
|
switch (expr.tag) {
|
||||||
|
case LITERAL:
|
||||||
|
printf("%d", expr.data.integer);
|
||||||
|
break;
|
||||||
|
case VARIABLE:
|
||||||
|
printf("%s", expr.data.variable);
|
||||||
|
break;
|
||||||
|
case FUNCTION: {
|
||||||
|
printf("%s(", expr.data.function.name);
|
||||||
|
for (unsigned i = 0; i < expr.data.function.args.length; ++i) {
|
||||||
|
print_expr(((Expr*)expr.data.function.args.elements)[i]);
|
||||||
|
if (i +1 < expr.data.function.args.length) {
|
||||||
|
printf(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf(")");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_stmt(Stmt stmt, unsigned indentation) {
|
||||||
|
for (unsigned i = 0; i < indentation; ++i) {
|
||||||
|
printf(" ");
|
||||||
|
}
|
||||||
|
switch (stmt.tag) {
|
||||||
|
case SET:
|
||||||
|
printf("%s = ", stmt.data.Set.name);
|
||||||
|
print_expr(stmt.data.Set.expr);
|
||||||
|
break;
|
||||||
|
case WHILE:
|
||||||
|
printf("while ");
|
||||||
|
print_expr(stmt.data.While.condition);
|
||||||
|
printf(" {\n");
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < stmt.data.While.block.length; ++i) {
|
||||||
|
print_stmt(((Stmt*)stmt.data.While.block.elements)[i], indentation + 1);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("}");
|
||||||
|
break;
|
||||||
|
case EXPR:
|
||||||
|
print_expr(stmt.data.Expr.expr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_ast(StmtArray stmts) {
|
||||||
|
for (unsigned i = 0; i < stmts.length; ++i) {
|
||||||
|
print_stmt(stmts.stmts[i], 0);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
43
first-interpreter/src/ast.h
Normal file
43
first-interpreter/src/ast.h
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef INTERPRETER_AST_H
|
||||||
|
#define INTERPRETER_AST_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void* elements;
|
||||||
|
unsigned length;
|
||||||
|
} Array;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
enum {
|
||||||
|
LITERAL,
|
||||||
|
VARIABLE,
|
||||||
|
FUNCTION,
|
||||||
|
} tag;
|
||||||
|
union {
|
||||||
|
int integer;
|
||||||
|
char* variable;
|
||||||
|
struct { char* name; Array args; } function;
|
||||||
|
} data;
|
||||||
|
} Expr;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
enum {
|
||||||
|
SET,
|
||||||
|
WHILE,
|
||||||
|
EXPR,
|
||||||
|
} tag;
|
||||||
|
union {
|
||||||
|
struct { char* name; Expr expr; } Set;
|
||||||
|
struct { Expr condition; Array block; } While;
|
||||||
|
struct { Expr expr; } Expr;
|
||||||
|
} data;
|
||||||
|
} Stmt;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Stmt* stmts;
|
||||||
|
unsigned length;
|
||||||
|
} StmtArray;
|
||||||
|
|
||||||
|
void print_expr(Expr);
|
||||||
|
void print_ast(StmtArray);
|
||||||
|
|
||||||
|
#endif
|
||||||
162
first-interpreter/src/execute.c
Normal file
162
first-interpreter/src/execute.c
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ast.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned* next_free_cell_index;
|
||||||
|
Array memory;
|
||||||
|
} Memory;
|
||||||
|
|
||||||
|
Memory new_memory() {
|
||||||
|
unsigned size = 1024;
|
||||||
|
unsigned* next_free_cell_index = (unsigned*)calloc(1, sizeof(unsigned));
|
||||||
|
int* mem = (int*)malloc(sizeof(int) * size);
|
||||||
|
return (Memory){
|
||||||
|
.next_free_cell_index = next_free_cell_index,
|
||||||
|
.memory = {
|
||||||
|
.length = size,
|
||||||
|
.elements = mem,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cell {
|
||||||
|
char* name;
|
||||||
|
int* memory_cell;
|
||||||
|
struct Cell* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct Cell* next;
|
||||||
|
} Scope;
|
||||||
|
|
||||||
|
Scope new_scope() {
|
||||||
|
return (Scope) { .next = NULL };
|
||||||
|
}
|
||||||
|
|
||||||
|
int* lookup(char* name, Scope scope) {
|
||||||
|
struct Cell* cell = scope.next;
|
||||||
|
while (cell != NULL) {
|
||||||
|
if (strcmp(cell->name, name) == 0) {
|
||||||
|
return cell->memory_cell;
|
||||||
|
} else {
|
||||||
|
cell = cell->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope insert(char* name, int value, Memory memory, Scope scope) {
|
||||||
|
if (*memory.next_free_cell_index + 1 < memory.memory.length) {
|
||||||
|
int* memory_cell = &((int*)(memory.memory.elements))[*memory.next_free_cell_index];
|
||||||
|
++*memory.next_free_cell_index;
|
||||||
|
*memory_cell = value;
|
||||||
|
struct Cell* cell = (struct Cell*)malloc(sizeof(struct Cell));
|
||||||
|
*cell = (struct Cell) {
|
||||||
|
.name = name,
|
||||||
|
.memory_cell = memory_cell,
|
||||||
|
.next = scope.next,
|
||||||
|
};
|
||||||
|
Scope new_scope = (Scope) {.next = cell};
|
||||||
|
return new_scope;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Error: out of memory.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int eval_expr(Expr expr, Memory memory, Scope scope) {
|
||||||
|
switch (expr.tag) {
|
||||||
|
default:
|
||||||
|
case LITERAL: {
|
||||||
|
return expr.data.integer;
|
||||||
|
}
|
||||||
|
case VARIABLE: {
|
||||||
|
int* result = lookup(expr.data.variable, scope);
|
||||||
|
if (result == NULL) {
|
||||||
|
fprintf(stderr, "Error: variable not found '%s'\n", expr.data.variable);
|
||||||
|
exit(1);
|
||||||
|
} else {
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case FUNCTION: {
|
||||||
|
if (strcmp(expr.data.function.name, "print") == 0) {
|
||||||
|
if (expr.data.function.args.length == 1) {
|
||||||
|
int arg = eval_expr(((Expr*)expr.data.function.args.elements)[0], memory, scope);
|
||||||
|
printf("%d\n", arg);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Error: print expects a single argument.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(expr.data.function.name, "add") == 0) {
|
||||||
|
if (expr.data.function.args.length == 2) {
|
||||||
|
int arg1 = eval_expr(((Expr*)expr.data.function.args.elements)[0], memory, scope);
|
||||||
|
int arg2 = eval_expr(((Expr*)expr.data.function.args.elements)[1], memory, scope);
|
||||||
|
return arg1 + arg2;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Error: negate expects a single argument.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(expr.data.function.name, "negate") == 0) {
|
||||||
|
if (expr.data.function.args.length == 1) {
|
||||||
|
int arg = eval_expr(((Expr*)expr.data.function.args.elements)[0], memory, scope);
|
||||||
|
return 0 - arg;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Error: negate expects a single argument.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope interpret_stmt(Stmt stmt, Memory memory, Scope scope) {
|
||||||
|
switch (stmt.tag) {
|
||||||
|
case SET: {
|
||||||
|
int result = eval_expr(stmt.data.Set.expr, memory, scope);
|
||||||
|
int* index = lookup(stmt.data.Set.name, scope);
|
||||||
|
if (index != NULL) {
|
||||||
|
*index = result;
|
||||||
|
} else {
|
||||||
|
return insert(stmt.data.Set.name, result, memory, scope);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WHILE: {
|
||||||
|
while (eval_expr(stmt.data.While.condition, memory, scope)) {
|
||||||
|
Scope new_scope = scope;
|
||||||
|
for (unsigned i = 0; i < stmt.data.While.block.length; ++i) {
|
||||||
|
Stmt current = ((Stmt*)stmt.data.While.block.elements)[i];
|
||||||
|
new_scope = interpret_stmt(current, memory, new_scope);
|
||||||
|
}
|
||||||
|
// free the part of the scope we are done with
|
||||||
|
struct Cell* next = new_scope.next;
|
||||||
|
while (next != NULL && next != scope.next) {
|
||||||
|
struct Cell* nextnext = next->next;
|
||||||
|
free(next);
|
||||||
|
next = nextnext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EXPR:
|
||||||
|
eval_expr(stmt.data.Expr.expr, memory, scope);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(StmtArray stmts) {
|
||||||
|
Memory memory = new_memory();
|
||||||
|
Scope scope = new_scope();
|
||||||
|
for (unsigned pc = 0; pc < stmts.length; ++pc) {
|
||||||
|
Stmt stmt = stmts.stmts[pc];
|
||||||
|
scope = interpret_stmt(stmt, memory, scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
first-interpreter/src/execute.h
Normal file
8
first-interpreter/src/execute.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef EXECUTE_H
|
||||||
|
#define EXECUTE_H
|
||||||
|
|
||||||
|
#include "ast.h"
|
||||||
|
|
||||||
|
void execute(StmtArray);
|
||||||
|
|
||||||
|
#endif
|
||||||
153
first-interpreter/src/lex.c
Normal file
153
first-interpreter/src/lex.c
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "lex.h"
|
||||||
|
|
||||||
|
int is_identifier_char(char);
|
||||||
|
int is_numeric(char);
|
||||||
|
|
||||||
|
TokenArray lex(char* txt, unsigned length) {
|
||||||
|
// The index for the input.
|
||||||
|
unsigned txt_index = 0;
|
||||||
|
|
||||||
|
// We'll put tokens we lexed successfully here.
|
||||||
|
Token* tokens = (Token*)malloc(length * sizeof(Token));
|
||||||
|
// Where to put the next token in tokens.
|
||||||
|
unsigned tokens_index = 0;
|
||||||
|
|
||||||
|
while (txt_index < length && txt[txt_index] != '\0') {
|
||||||
|
switch (txt[txt_index]) {
|
||||||
|
// Ignore spaces, tabs and newlines
|
||||||
|
case ' ': {
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '\t': {
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '\n': {
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// EQUALS
|
||||||
|
case '=': {
|
||||||
|
tokens[tokens_index] = (Token){ .tag = EQUALS, .data.integer = 0, };
|
||||||
|
++tokens_index;
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// COMMA
|
||||||
|
case ',': {
|
||||||
|
tokens[tokens_index] = (Token){ .tag = COMMA, .data.integer = 0, };
|
||||||
|
++tokens_index;
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// OPENPAREN
|
||||||
|
case '(': {
|
||||||
|
tokens[tokens_index] = (Token){ .tag = OPENPAREN, .data.integer = 0, };
|
||||||
|
++tokens_index;
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// CLOSEPAREN
|
||||||
|
case ')': {
|
||||||
|
tokens[tokens_index] = (Token){ .tag = CLOSEPAREN, .data.integer = 0, };
|
||||||
|
++tokens_index;
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// OPENCURLY
|
||||||
|
case '{': {
|
||||||
|
tokens[tokens_index] = (Token){ .tag = OPENCURLY, .data.integer = 0, };
|
||||||
|
++tokens_index;
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// CLOSECURLY
|
||||||
|
case '}': {
|
||||||
|
tokens[tokens_index] = (Token){ .tag = CLOSECURLY, .data.integer = 0, };
|
||||||
|
++tokens_index;
|
||||||
|
++txt_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Complex cases and errors.
|
||||||
|
default: {
|
||||||
|
// Identifiers.
|
||||||
|
if (is_identifier_char(txt[txt_index])) {
|
||||||
|
char* word = malloc(128);
|
||||||
|
unsigned word_index = 0;
|
||||||
|
while (txt_index < length && txt[txt_index] != '\0' && is_identifier_char(txt[txt_index]) && word_index < 128) {
|
||||||
|
word[word_index] = txt[txt_index];
|
||||||
|
++word_index;
|
||||||
|
++txt_index;
|
||||||
|
}
|
||||||
|
tokens[tokens_index] = (Token){ .tag = IDENTIFIER, .data.identifier = word, };
|
||||||
|
++tokens_index;
|
||||||
|
}
|
||||||
|
// Integers.
|
||||||
|
else if (is_numeric(txt[txt_index])) {
|
||||||
|
|
||||||
|
char word[9] = { '\0' };
|
||||||
|
unsigned word_index = 0;
|
||||||
|
while (txt_index < length && txt[txt_index] != '\0' && is_numeric(txt[txt_index]) && word_index < 9) {
|
||||||
|
word[word_index] = txt[txt_index];
|
||||||
|
++word_index;
|
||||||
|
++txt_index;
|
||||||
|
}
|
||||||
|
int integer = atoi(word);
|
||||||
|
tokens[tokens_index] = (Token){ .tag = INTEGER, .data.integer = integer, };
|
||||||
|
++tokens_index;
|
||||||
|
}
|
||||||
|
// Not a token.
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "unexpected character '%c'", txt[txt_index]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (TokenArray){ .tokens = tokens, .length = tokens_index };
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_identifier_char(char c) {
|
||||||
|
return c == '_' || ('a' <= c && c <= 'z');
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_numeric(char c) {
|
||||||
|
return ('0' <= c && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_TokenArray(TokenArray tokens) {
|
||||||
|
unsigned token_index = 0;
|
||||||
|
while (token_index < tokens.length) {
|
||||||
|
Token token = tokens.tokens[token_index];
|
||||||
|
++token_index;
|
||||||
|
switch (token.tag) {
|
||||||
|
case IDENTIFIER:
|
||||||
|
printf("IDENTIFIER '%s'\n", token.data.identifier);
|
||||||
|
break;
|
||||||
|
case INTEGER:
|
||||||
|
printf("NUMBER %d\n", token.data.integer);
|
||||||
|
break;
|
||||||
|
case OPENPAREN:
|
||||||
|
printf("OPENPAREN\n");
|
||||||
|
break;
|
||||||
|
case CLOSEPAREN:
|
||||||
|
printf("CLOSEPAREN\n");
|
||||||
|
break;
|
||||||
|
case OPENCURLY:
|
||||||
|
printf("OPENCURLY\n");
|
||||||
|
break;
|
||||||
|
case CLOSECURLY:
|
||||||
|
printf("CLOSECURLY\n");
|
||||||
|
break;
|
||||||
|
case EQUALS:
|
||||||
|
printf("EQUALS\n");
|
||||||
|
break;
|
||||||
|
case COMMA:
|
||||||
|
printf("COMMA\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
first-interpreter/src/lex.h
Normal file
32
first-interpreter/src/lex.h
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef LEXER_H
|
||||||
|
#define LEXER_H
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
IDENTIFIER,
|
||||||
|
INTEGER,
|
||||||
|
OPENPAREN,
|
||||||
|
CLOSEPAREN,
|
||||||
|
OPENCURLY,
|
||||||
|
CLOSECURLY,
|
||||||
|
EQUALS,
|
||||||
|
COMMA,
|
||||||
|
} TokenTag;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TokenTag tag;
|
||||||
|
union {
|
||||||
|
char* identifier;
|
||||||
|
int integer;
|
||||||
|
} data;
|
||||||
|
} Token;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Token* tokens;
|
||||||
|
unsigned length;
|
||||||
|
} TokenArray;
|
||||||
|
|
||||||
|
TokenArray lex(char*, unsigned);
|
||||||
|
|
||||||
|
void print_TokenArray(TokenArray);
|
||||||
|
|
||||||
|
#endif
|
||||||
29
first-interpreter/src/main.c
Normal file
29
first-interpreter/src/main.c
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "run.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
// Read file
|
||||||
|
if (argc != 2) {
|
||||||
|
puts("USAGE: interpreter FILE\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* file = fopen(argv[1], "r");
|
||||||
|
if (!file) {
|
||||||
|
printf("Error opening file: %s", argv[1]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
unsigned file_size = ftell(file);
|
||||||
|
rewind(file);
|
||||||
|
|
||||||
|
char* txt = alloca(file_size);
|
||||||
|
|
||||||
|
fread(txt, 1, file_size, file);
|
||||||
|
|
||||||
|
// Run
|
||||||
|
run(txt, file_size);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
177
first-interpreter/src/parse.c
Normal file
177
first-interpreter/src/parse.c
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
#include "parse.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int is_while(char* string) {
|
||||||
|
if (strcmp("while", string) == 0) { return 1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maybe_parse_token(TokenArray tokens, unsigned* tokens_index, TokenTag tag) {
|
||||||
|
if (*tokens_index < tokens.length) {
|
||||||
|
Token token = tokens.tokens[*tokens_index];
|
||||||
|
if (token.tag == tag) {
|
||||||
|
++(*tokens_index);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_token(TokenArray tokens, unsigned* tokens_index, TokenTag tag) {
|
||||||
|
if (!(maybe_parse_token(tokens, tokens_index, tag))) {
|
||||||
|
fprintf(stderr, "Parse error: unexpected token. Expected %d\n", tag);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* parse_identifier(TokenArray tokens, unsigned* tokens_index) {
|
||||||
|
if (*tokens_index < tokens.length) {
|
||||||
|
Token token = tokens.tokens[*tokens_index];
|
||||||
|
++(*tokens_index);
|
||||||
|
if (token.tag != IDENTIFIER) {
|
||||||
|
fprintf(stderr, "Parse error: got wrong token: %d, expected IDENTIFIER\n", token.tag);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return token.data.identifier;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Parse error: unexpected end of text.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr parse_expr(TokenArray tokens, unsigned* tokens_index);
|
||||||
|
Expr parse_function(TokenArray tokens, unsigned* tokens_index, char* name) {
|
||||||
|
Expr* exprs = (Expr*)malloc((tokens.length - *tokens_index) * sizeof(Expr));
|
||||||
|
unsigned expr_index = 0;
|
||||||
|
while (*tokens_index < tokens.length && tokens.tokens[*tokens_index].tag != CLOSEPAREN) {
|
||||||
|
exprs[expr_index] = parse_expr(tokens, tokens_index);
|
||||||
|
++expr_index;
|
||||||
|
if (!maybe_parse_token(tokens, tokens_index, COMMA)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse_token(tokens, tokens_index, CLOSEPAREN);
|
||||||
|
|
||||||
|
return (Expr) {
|
||||||
|
.tag = FUNCTION,
|
||||||
|
.data.function = {
|
||||||
|
.name = name,
|
||||||
|
.args = (Array) {
|
||||||
|
.elements = exprs,
|
||||||
|
.length = expr_index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr parse_expr(TokenArray tokens, unsigned* tokens_index) {
|
||||||
|
if (*tokens_index < tokens.length) {
|
||||||
|
Token token = tokens.tokens[*tokens_index];
|
||||||
|
++(*tokens_index);
|
||||||
|
switch (token.tag) {
|
||||||
|
case INTEGER: {
|
||||||
|
return (Expr) {
|
||||||
|
.tag = LITERAL,
|
||||||
|
.data.integer = token.data.integer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case IDENTIFIER: {
|
||||||
|
if (maybe_parse_token(tokens, tokens_index, OPENPAREN)) {
|
||||||
|
return parse_function(tokens, tokens_index, token.data.identifier);
|
||||||
|
} else {
|
||||||
|
return (Expr) {
|
||||||
|
.tag = VARIABLE,
|
||||||
|
.data.variable = token.data.identifier,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Parse error: got wrong token: %d, expected IDENTIFIER or INTEGER\n", token.tag);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Parse error: unexpected end of text.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtArray parse_block(TokenArray tokens, unsigned* tokens_index);
|
||||||
|
|
||||||
|
Stmt parse_stmt(TokenArray tokens, unsigned* tokens_index) {
|
||||||
|
char* identifier = parse_identifier(tokens, tokens_index);
|
||||||
|
|
||||||
|
// While
|
||||||
|
if (is_while(identifier)) {
|
||||||
|
Expr condition = parse_expr(tokens, tokens_index);
|
||||||
|
StmtArray block = parse_block(tokens, tokens_index);
|
||||||
|
|
||||||
|
return (Stmt) {
|
||||||
|
.tag = WHILE,
|
||||||
|
.data.While = {
|
||||||
|
.condition = condition,
|
||||||
|
.block = {
|
||||||
|
.elements = block.stmts,
|
||||||
|
.length = block.length,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Set
|
||||||
|
else if (maybe_parse_token(tokens, tokens_index, EQUALS)) {
|
||||||
|
Expr expr = parse_expr(tokens, tokens_index);
|
||||||
|
|
||||||
|
return (Stmt) {
|
||||||
|
.tag = SET,
|
||||||
|
.data.Set = {
|
||||||
|
.name = identifier,
|
||||||
|
.expr = expr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Expression
|
||||||
|
else if (maybe_parse_token(tokens, tokens_index, OPENPAREN)) {
|
||||||
|
Expr expr = parse_function(tokens, tokens_index, identifier);
|
||||||
|
|
||||||
|
return (Stmt) {
|
||||||
|
.tag = EXPR,
|
||||||
|
.data.Expr = {
|
||||||
|
.expr = expr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Error
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "Parse error: Unexpected token. Expected '(' or '='");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtArray parse_block(TokenArray tokens, unsigned* tokens_index) {
|
||||||
|
parse_token(tokens, tokens_index, OPENCURLY);
|
||||||
|
Stmt* stmts = (Stmt*)malloc((tokens.length - *tokens_index) * sizeof(Stmt));
|
||||||
|
unsigned stmt_index = 0;
|
||||||
|
while (*tokens_index < tokens.length && tokens.tokens[*tokens_index].tag != CLOSECURLY) {
|
||||||
|
stmts[stmt_index] = parse_stmt(tokens, tokens_index);
|
||||||
|
++stmt_index;
|
||||||
|
}
|
||||||
|
parse_token(tokens, tokens_index, CLOSECURLY);
|
||||||
|
return (StmtArray) {
|
||||||
|
.stmts = stmts,
|
||||||
|
.length = stmt_index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StmtArray parse(TokenArray tokens) {
|
||||||
|
unsigned tokens_index = 0;
|
||||||
|
unsigned stmt_index = 0;
|
||||||
|
Stmt* stmts = (Stmt*)malloc(tokens.length * sizeof(Stmt));
|
||||||
|
|
||||||
|
while (tokens_index < tokens.length) {
|
||||||
|
stmts[stmt_index] = parse_stmt(tokens, &tokens_index);
|
||||||
|
++stmt_index;
|
||||||
|
}
|
||||||
|
return (StmtArray){ .stmts = stmts, .length = stmt_index };
|
||||||
|
}
|
||||||
9
first-interpreter/src/parse.h
Normal file
9
first-interpreter/src/parse.h
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#ifndef PARSER_H
|
||||||
|
#define PARSER_H
|
||||||
|
|
||||||
|
#include "ast.h"
|
||||||
|
#include "lex.h"
|
||||||
|
|
||||||
|
StmtArray parse(TokenArray tokens);
|
||||||
|
|
||||||
|
#endif
|
||||||
24
first-interpreter/src/run.c
Normal file
24
first-interpreter/src/run.c
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "ast.h"
|
||||||
|
#include "lex.h"
|
||||||
|
#include "parse.h"
|
||||||
|
#include "execute.h"
|
||||||
|
|
||||||
|
void run(char* code, unsigned length) {
|
||||||
|
TokenArray tokens = lex(code, length);
|
||||||
|
|
||||||
|
puts("Tokens:\n");
|
||||||
|
print_TokenArray(tokens);
|
||||||
|
puts("\n");
|
||||||
|
|
||||||
|
StmtArray program = parse(tokens);
|
||||||
|
|
||||||
|
puts("\nProgram:\n");
|
||||||
|
print_ast(program);
|
||||||
|
puts("\n");
|
||||||
|
|
||||||
|
puts("\nRun:\n");
|
||||||
|
execute(program);
|
||||||
|
puts("\n");
|
||||||
|
}
|
||||||
6
first-interpreter/src/run.h
Normal file
6
first-interpreter/src/run.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef INTERPRETER_H
|
||||||
|
#define INTERPRETER_H
|
||||||
|
|
||||||
|
void run(char*, unsigned);
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Add table
Reference in a new issue