Compare commits

20 Commits
Author SHA1 Message Date
glm94 73067fcc1f Added statements to the Parser, currently just the Print and Expression statements. 8.1 isn't quite done yet, maybe half way there. 2022-03-23 19:43:46 +00:00
glm94 bf59a98628 Updated the Makefile, set debug option for the compiler, and fixed all the warnings generated by gcc with O2 optimization option for release builds. 2022-03-18 17:01:15 +00:00
glm94 1e6bbb87d4 Cleaned up the Makefile, added the framework for checking for runtime errrors in the Interpreter and fixed a bug where I didn't close out the 'va_list' in the Match function in the Parser. 2022-03-17 18:58:23 +00:00
glm94 34557c92c3 Setup the Makefile to allow me to produce a release build and to create some directories if they're not there already. 2022-03-16 18:23:11 +00:00
glm94 b3693f17df Updated the project structure, and modified the Makefile to hopefully be future proof so I can freely make sub-folders in the src/ folder and not have to worry about breaking the Makefile. 2022-03-16 18:07:37 +00:00
glm94 6daaaae7c2 Fixed a bug when the scanner parses a number and creates the token. 2022-03-16 00:00:17 -05:00
glm94 b072921304 Touched up the Scanner. Refactored the Interpreter some more, this time it's able to be run on simple expressions. String concatenation can be performed by the Interpreter however, numbers and Booleans are not being stored correctly in the Object structures. 2022-03-15 20:50:08 +00:00
glm94 d4dd459389 Fixed a bug with string parsing in the Scanner. 2022-03-15 18:37:25 +00:00
glm94 1ded68ddee Fixed a bug in the IsTruthy function. The VisitBinaryExpression now processes more operations. 2022-03-14 19:20:03 +00:00
glm94 3044f23bcc Creating an object of type Boolean should work correctly, as long as the Boolean value being passed is done so as a double*. 2022-03-14 16:52:47 +00:00
glm94 f666585f5f More refactoring trying to get this cleaned up while trying to keep it easy(ish) to extend for later chapters. I'll never take namespaces for granted again... 2022-03-09 21:13:52 +00:00
glm94 e802c57a93 Trying a more object oriented approach and created an 'Object' struct to have metadata for the values the interpreter works with. 2022-03-08 22:08:06 +00:00
glm94 2d587e75b1 Extemely broken, but all the code up to 7.3 should be here, now the refactoring begins to make it C code and not Java code. 2022-03-07 19:22:39 +00:00
glm94 d39d20aa6e Added a reminder in the Parser in the event of bad syntax. 2022-03-07 18:13:35 +00:00
glm94 5ea8079090 Updated the token to more reflect what was implemented in the book. Maybe not the right way to handle it but perhaps this setup will come in handy in later chapters. 2022-03-03 19:39:12 +00:00
glm94 caf4f02428 Minor cleanup. 2022-03-03 17:12:01 +00:00
glm94 d039a26c16 Fixed some bugs with the Scanner and Parser. 2022-03-02 22:30:53 -06:00
glm94 7bf7304bd4 Attempting to print the expression tree. I think the printing code is fine but the Parser is bugged and not building the tree correctly. 2022-03-02 21:43:22 +00:00
glm94 221d575c16 Further refactoring done. The Parser runs without crashing, however it's output is still to be checked and verified. 2022-03-02 21:24:58 +00:00
glm94 e20ccb8cfb The Scanner has been refactored and some responsibilities were pulled from it. Confirmed working, the Parser still needs to be touched on however. 2022-03-02 20:52:40 +00:00
18 changed files with 1169 additions and 550 deletions
+1
View File
@@ -1,3 +1,4 @@
clox
*.lox
obj/
bin/
+30 -6
View File
@@ -1,13 +1,37 @@
CC = gcc
SOURCEFILES := $(wildcard *.c)
CFLAGS=-g -Wall -DDEBUG
SRCDIR=src
OBJDIR=obj
SRCS=$(wildcard $(SRCDIR)/*.c)
# Substitute all .c with .o from SRCS
OBJS=$(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SRCS))
SOURCE := $(wildcard *.c)
BINDIR=bin
BIN=$(BINDIR)/clox
$(SOURCE:.c=.o): $(SOURCE)
@mkdir -p obj
$(CC) $^ -o obj/clox
all: $(BIN)
release: CFLAGS=-Wall -O2
release: clean
release: $(BIN)
$(BIN): $(OBJS) $(BINDIR)
$(CC) $(CFLAGS) $(OBJS) -o $@
$(OBJDIR)/%.o: $(SRCDIR)/%.c $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(BINDIR):
mkdir $@
$(OBJDIR):
mkdir $@
.PHONY: clean
.PHONY: test
clean:
rm -r obj
rm -rf $(BINDIR)/* $(OBJDIR)/*
test:
$(BIN) test.lox
-6
View File
@@ -1,6 +0,0 @@
#include "expr.h"
void VisitBinary(struct binary);
void VisitGrouping(struct grouping);
void VisitLiteral(struct literal);
void VisitUnary(struct unary);
-190
View File
@@ -1,190 +0,0 @@
#include "parser.h"
#include "expr.h"
#include "scanner.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
Expr* Expression(void);
Expr* Equality(void);
Expr* Comparison(void);
Expr* Term(void);
Expr* Factor(void);
Expr* Unary(void);
Expr* Primary(void);
int Match(int, ...);
int Check(TokenType);
int IsAtEnd(void);
Token* Peek(void);
Token* Previous(void);
Token* Advance(void);
Token* CreateToken(char*, TokenType);
const TokenList* tokens;
int Current = 0;
//Simply expands the equality rule
Expr* Expression() {
return Equality();
}
Expr* Equality() {
Expr* expr = Comparison();
while(Match(2, Bang_Equal, Equal_Equal)) {
Token* operator = Previous();
Expr* right = Comparison();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Comparison() {
Expr* expr = Term();
while(Match(4, Greater, Greater_Equal, Less, Less_Equal)) {
Token* operator = Previous();
Expr* right = Term();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Term() {
Expr* expr = Factor();
while(Match(2, Minus, Plus)) {
Token* operator = Previous();
Expr* right = Factor();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Factor() {
Expr* expr = Unary();
while(Match(2, Slash, Star)) {
Token* operator = Previous();
Expr* right = Unary();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Unary() {
if (Match(2, Bang, Minus)) {
Token* operator = Previous();
Expr* right = Unary();
Expr* expr = calloc(1, sizeof(Expr));
expr->type = UNARY;
expr->expression.Unary.op = operator;
expr->expression.Unary.right = right;
return expr;
}
return Primary();
}
Expr* Primary() {
Expr* expr = calloc(1, sizeof(Expr));
expr->type = LITERAL;
if (Match(1, FALSE)) {
expr->expression.Literal.type = CreateToken("false", FALSE);
return expr;
}
if (Match(1, TRUE)) {
expr->expression.Literal.type = CreateToken("true", TRUE);
return expr;
}
if (Match(1, NIL)) {
expr->expression.Literal.type = CreateToken("nil", NIL);
return expr;
}
if (Match(2, Number, String)) {
expr->expression.Literal.type = Previous();
return expr;
}
if (Match(1, LParen)) {
free(expr);
expr = Expression();
//Consume(RParen, "Expect ')' after expression.");
expr->type = GROUPING;
return expr;
}
return expr;
}
int Match(int count, ...) {
va_list list;
va_start(list, count);
for(int i = 0; i < count; i++) {
if(Check(va_arg(list, TokenType))) {
Advance();
return 1;
}
}
return 0;
}
int Check(TokenType type) {
if (IsAtEnd()) return 0;
return Peek()->type == type;
}
int IsAtEnd() {
return Peek()->type == EndOF;
}
Token* Peek() {
return tokens->tokens[Current];
}
Token* Previous() {
return tokens->tokens[Current - 1];
}
Token* Advance() {
if (!IsAtEnd()) Current++;
return Previous();
}
Token* CreateToken(char* lexeme, TokenType type) {
Token* token = calloc(1, sizeof(Token));
token->type = type;
token->lexeme = lexeme;
token->length = strlen(lexeme);
return token;
}
-6
View File
@@ -1,6 +0,0 @@
#ifndef PARSER_H
#define PARSER_H
#endif
-308
View File
@@ -1,308 +0,0 @@
#include "scanner.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef struct {
char* keyword;
TokenType type;
} KeywordPair;
KeywordPair keywords[KEYWORD_COUNT] = {
{ "and", AND },
{ "class", CLASS },
{ "else", ELSE },
{ "false", FALSE },
{ "for", FOR },
{ "fun", FUN },
{ "if", IF },
{ "nil", NIL },
{ "or", OR },
{ "print", PRINT },
{ "return", RETURN },
{ "super", SUPER },
{ "this", THIS },
{ "true", TRUE },
{ "var", VAR },
{ "while", WHILE }
};
const char* source_code;
//Start and Current hold the offsets that index into the string source_code.
int start; //Points to the first character in the lexeme being scanned.
int current; //points to the character currently being considered.
int length;
int line = 1;
const char* SAdvance(void);
int SIsAtEnd(void);
void ScanToken(TokenList*);
TokenList* CreateList(void);
int AddTokenToList(TokenType, const char*, int, TokenList*);
int SMatch(char);
char SPeek(void);
char PeekNext(void);
void ParseString(TokenList *);
void ParseNumber(TokenList *);
void ParseIdentifier(TokenList *);
int IsAlpha(char c);
KeywordPair* Get(char*);
TokenList* ScanTokens(const char* source) {
if (!source) return NULL;
source_code = source;
length = strlen(source);
if (length == 0) return NULL;
TokenList* tokens = CreateList();
while(!SIsAtEnd()) {
start = current;
ScanToken(tokens);
}
//Add EOF token and return list once that's set up.
AddTokenToList(EndOF, NULL, 0, tokens);
return tokens;
}
TokenList* CreateList() {
TokenList* list = calloc(1, sizeof(TokenList));
if (!list) {
fprintf(stderr, "Failed to calloc TokenList.\n");
return NULL;
}
list->tokens = calloc(DEFAULT_TOKENLIST_SIZE, sizeof(Token*));
if (!list->tokens) {
free(list);
fprintf(stderr, "Failed to calloc tokens.\n");
return NULL;
}
list->capacity = DEFAULT_TOKENLIST_SIZE;
list->size = 0;
return list;
}
void DestroyTokenList(TokenList* list) {
if (!list) return;
for(int i = 0; i < list->size; i++) {
//free(list->tokens[i]->lexeme); This shouldn't be needed since the lexeme is a pointer into the source code.
free(list->tokens[i]);
}
free(list->tokens);
free(list);
}
int AddTokenToList(TokenType type, const char* lexeme, int length, TokenList* tokens) {
if (!tokens) return 0;
Token* token = calloc(1, sizeof(Token));
if (!token) {
fprintf(stderr, "Failed to calloc memory for new Token.\n");
return 0;
}
token->lexeme = lexeme;
token->type = type;
token->length = length; //Set the length of the lexeme (which is just a pointer into the complete source listing).
token->line = line;
if ((tokens->size + 1) > tokens->capacity) {
void* new_ptr = realloc(tokens->tokens, sizeof(Token*) * tokens->capacity * 2);
if (!new_ptr) {
fprintf(stderr, "Failed to realloc TokenList to size %d.\n", tokens->capacity * 2);
return 0;
}
tokens->tokens = new_ptr;
tokens->capacity = tokens->capacity * 2;
}
tokens->tokens[tokens->size] = token;
tokens->size++;
return 1;
}
void ScanToken(TokenList* tokens) {
const char* c = SAdvance();
switch (*c) {
case '(':
AddTokenToList(LParen, c, 1, tokens);
break;
case ')':
AddTokenToList(RParen, c, 1, tokens);
break;
case '{':
AddTokenToList(LBrace, c, 1, tokens);
break;
case '}':
AddTokenToList(RBrace, c, 1, tokens);
break;
case ',':
AddTokenToList(Comma, c, 1, tokens);
break;
case '.':
AddTokenToList(Dot, c, 1, tokens);
break;
case '-':
AddTokenToList(Minus, c, 1, tokens);
break;
case '+':
AddTokenToList(Plus, c, 1, tokens);
break;
case ';':
AddTokenToList(Semicolon, c, 1, tokens);
break;
case '*':
AddTokenToList(Star, c, 1, tokens);
break;
case '!':
if (SMatch('=')) AddTokenToList(Bang_Equal, "!=", 2, tokens);
else AddTokenToList(Bang, c, 1, tokens);
break;
case '=':
if (SMatch('=')) AddTokenToList(Equal_Equal, "==", 2, tokens);
else AddTokenToList(Equal, c, 1, tokens);
break;
case '<':
if (SMatch('=')) AddTokenToList(Less_Equal, "<=", 2, tokens);
else AddTokenToList(Less, c, 1, tokens);
break;
case '>':
if (SMatch('=')) AddTokenToList(Greater_Equal, ">=", 2, tokens);
else AddTokenToList(Greater, c, 1, tokens);
break;
case '/':
if (SMatch('/')) {
while(SPeek() != '\n' && !SIsAtEnd()) SAdvance();
}
else {
AddTokenToList(Slash, c, 1, tokens);
}
break;
case '"':
ParseString(tokens);
break;
case ' ':
case '\r':
case '\t':
break; //Ignore whitespace
case '\n':
line++;
break;
default:
if (isdigit(*c)) {
ParseNumber(tokens);
break;
} else if (IsAlpha(*c)) {
ParseIdentifier(tokens);
break;
}
fprintf(stderr, "Unexcpedted character %c\n", *c);
break;
}
}
int SIsAtEnd() {
return current >= length;
}
const char* SAdvance() {
return &source_code[current++];
}
int SMatch(char expected) {
if (SIsAtEnd()) return 0;
if (source_code[current] != expected) return 0;
current++;
return 1;
}
char SPeek() {
if (SIsAtEnd()) return '\0';
return source_code[current];
}
void ParseString(TokenList *list) {
start = current; //The start is currently pointing to the first double quote so we need to move it
//to the next (first character) of the string literal.
while(SPeek() != '"' && !SIsAtEnd()) {
if (SPeek() == '\n') line++;
SAdvance();
}
if (SIsAtEnd()) {
fprintf(stderr, "Unterminated string.\n");
return;
}
AddTokenToList(String, &source_code[start], current - start, list);
SAdvance(); // The closing ".
}
void ParseNumber(TokenList* list) {
while(isdigit(SPeek())) SAdvance();
if (SPeek() == '.' && isdigit(PeekNext())) {
SAdvance();
while(isdigit(SPeek())) SAdvance();
}
AddTokenToList(Number, &source_code[start], current - start, list);
}
char PeekNext() {
if (current + 1 >= length) return '\0';
return source_code[current + 1];
}
void ParseIdentifier(TokenList * list) {
while(IsAlpha(SPeek())) SAdvance();
char* lexeme = calloc(sizeof(char*), (current - start + 1));
snprintf(lexeme, current - start + 1, "%s", &source_code[start]);
KeywordPair* result = Get(lexeme);
if (result) AddTokenToList(result->type, &source_code[start], current - start, list);
else AddTokenToList(Identifier, &source_code[start], current - start, list);
free(lexeme);
}
int IsAlpha(char c) {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c == '_');
}
KeywordPair* Get(char* text) {
if (!text) return NULL;
for (int i = 0; i < KEYWORD_COUNT; i++)
if (strcmp(text, keywords[i].keyword) == 0) return &keywords[i];
return NULL;
}
+6 -10
View File
@@ -2,6 +2,7 @@
#define EXPRESSION_H
#include "scanner.h"
#include <stdarg.h>
typedef enum {
EXPRESSION,
@@ -24,11 +25,6 @@ struct grouping {
struct Expr* expression;
};
struct literal {
Token* type;
void* object;
};
struct unary {
Token* op;
struct Expr* right;
@@ -39,14 +35,14 @@ struct Expr {
union ex {
struct binary Binary;
struct grouping Grouping;
struct literal Literal;
Token* Literal;
struct unary Unary;
} expression;
};
void VisitBinary(struct binary);
void VisitGrouping(struct grouping);
void VisitLiteral(struct literal);
void VisitUnary(struct unary);
// void VisitBinary(struct binary);
// void VisitGrouping(struct grouping);
// void VisitLiteral(Token);
// void VisitUnary(struct unary);
#endif
+312
View File
@@ -0,0 +1,312 @@
#include "interpreter.h"
#include "expr.h"
#include "token.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Object* Evaluate(Expr*);
Object* CreateObject(const void*, TokenType);
Object* VisitLiteralExpression(Expr*);
Object* VisitGroupingExpression(Expr*);
Object* VisitBinaryExpression(Expr*);
int IsTruthy(Object*);
int IsEqual(Object*, Object*);
int ConcatStringObject(Object*, const Object*);
void CheckNumberOperands(TokenType, int, ...);
void PrintObject(Object*);
void PrintObject(Object* o) {
switch(o->instance) {
case INS_BOOLEAN:
printf("BOOL: %d\n", o->value.boolean);
break;
case INS_DOUBLE:
printf("DOUBLE: %f\n", o->value.number);
break;
case INS_STRING:
printf("STRING: %s\n", o->value.string);
break;
default:
printf("NULL\n");
}
}
void Interpret(Expr* exp) {
Object* c = Evaluate(exp);
switch(c->instance) {
case INS_BOOLEAN:
printf("Bool %d\n", c->value.boolean);
break;
case INS_DOUBLE:
printf("Number %f\n", c->value.number);
break;
case INS_STRING:
printf("String '%s'\n", c->value.string);
break;
default:
printf("Default\n");
}
printf("Freeing Object...\n");
FreeObject(c);
}
Object* VisitLiteralExpression(Expr* expr) {
return CreateObject(expr->expression.Literal->literal, expr->expression.Literal->type);
}
//NOTE: We rely on this helper method which simply sends the expression back into
//the interpreters visitor implementation
Object* VisitGroupingExpression(Expr* expression) {
return Evaluate(expression->expression.Grouping.expression);
}
Object* VisitBinaryExpression(Expr* expression) {
Object* left = Evaluate(expression->expression.Binary.left);
Object* right = Evaluate(expression->expression.Binary.right);
double computed_value;
switch(expression->expression.Binary.op->type) {
case Greater:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
computed_value = left->value.number > right->value.number;
FreeObject(left);
FreeObject(right);
return CreateObject(&computed_value, TRUE);
//return left > right;
case Greater_Equal:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
computed_value = left->value.number >= right->value.number;
FreeObject(left);
FreeObject(right);
return CreateObject(&computed_value, TRUE);
//return left >= right;
case Less:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
computed_value = left->value.number < right->value.number;
FreeObject(left);
FreeObject(right);
return CreateObject(&computed_value, TRUE);
//return left < right;
case Less_Equal:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
computed_value = left->value.number <= right->value.number;
FreeObject(left);
FreeObject(right);
return CreateObject(&computed_value, TRUE);
//return left <= right;
case Bang_Equal:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
computed_value = !IsEqual(left, right);
FreeObject(left);
FreeObject(right);
return CreateObject(&computed_value, TRUE);
//return !IsEqual(left, right);
case Equal_Equal:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
computed_value = IsEqual(left, right);
FreeObject(left);
FreeObject(right);
return CreateObject(&computed_value, TRUE);
//return IsEqual(left, right);
case Minus:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
left->value.number -= right->value.number;
FreeObject(right);
return left;
//return left - right;
case Slash:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
left->value.number /= right->value.number;
FreeObject(right);
return left;
//return *((double*)left) / *((double*)right);
case Star:
CheckNumberOperands(expression->expression.Binary.op->type, 2, left, right);
left->value.number *= right->value.number;
FreeObject(right);
return left;
//return *((double*)left) * *((double*)right);
case Plus:
if (left->instance == INS_DOUBLE && right->instance == INS_DOUBLE) {
left->value.number += right->value.number;
FreeObject(right);
return left;
}
if (left->instance == INS_STRING && right->instance == INS_STRING) {
if (ConcatStringObject(left, right)) {
FreeObject(right);
return left;
}
}
break;
default:
FreeObject(left);
FreeObject(right);
return NULL; //Should be unreachable.
}
FreeObject(left);
FreeObject(right);
// Unreachable
return NULL;
}
Object* VisitUnaryExpression(Expr* expression) {
Object* right = Evaluate(expression->expression.Unary.right);
Object* c;
double computed_value;
switch (expression->expression.Unary.op->type) {
case Minus:
CheckNumberOperands(expression->expression.Unary.op->type, 1, right);//This needs to "throw" if the function fails.
computed_value = -right->value.number;
FreeObject(right);
c = CreateObject(&computed_value, Number);
return c;
case Bang:
computed_value = !IsTruthy(right);
FreeObject(right);
c = CreateObject(&computed_value, TRUE); //TRUE or FALSE, doesn't matter here since it becomes INS_BOOLEAN in the end.
return c;
default:
FreeObject(right);
return NULL; //Should be unreachable.
}
FreeObject(right);
return NULL; //Should be unreachable.
}
Object* Evaluate(Expr* expression) {
switch (expression->type) {
case EXPRESSION:
fprintf(stderr, "EXPRESSION case seen for expression.\n");
break;
case UNARY:
return VisitUnaryExpression(expression);
case GROUPING:
return VisitGroupingExpression(expression->expression.Grouping.expression);
case BINARY:
return VisitBinaryExpression(expression);
case LITERAL:
return VisitLiteralExpression(expression);
default:
fprintf(stderr, "Default case seen for expression.\n");
break;
}
//return expr.accept(this);
return NULL;
}
Object* CreateObject(const void* value, TokenType type) {
Object* object = calloc(1, sizeof(Object));
if (!object) {
fprintf(stderr, "Failed to calloc object. %s.\n", strerror(errno));
return NULL;
}
switch(type) {
case NIL:
object->instance = INS_NULL;
break;
case Number:
//printf("NUM: %p, %f\n", value, *(double*) value);
object->instance = INS_DOUBLE;
object->value.number = *(double*) value;
break;
case TRUE:
case FALSE:
object->instance = INS_BOOLEAN;
object->value.boolean = *(double*) value;
break;
default:
if (value) {
unsigned int length = strlen(value);
object->instance = INS_STRING;
object->value.string = calloc(length + 1, sizeof(char));
if (!object->value.string) {
fprintf(stderr, "Faild to calloc %u bytes for a new string Object. %s.\n", length + 1, strerror(errno));
return NULL;
}
memcpy(object->value.string, value, length);
} else object->instance = INS_NULL; //TODO: this is most likely an error, but we'll ignore that for now.
}
return object;
}
void FreeObject(Object* object) {
if (object->instance == INS_STRING) free(object->value.string);
free(object);
}
int ConcatStringObject(Object* a, const Object* b) {
if (!a || !b) return 0;
if (a->instance != INS_STRING || b->instance != INS_STRING) return 0;
int a_length = strlen(a->value.string);
int b_length = strlen(b->value.string);
char* c;
c = realloc(a->value.string, a_length + b_length + 1);
if (!c) {
fprintf(stderr, "Failed to realloc for object concat. %s.\n", strerror(errno));
return 0;
}
a->value.string = c;
a->value.string[a_length + b_length] = '\0';
strncat(a->value.string, b->value.string, b_length);
return 1;
}
int IsTruthy(Object* object) {
if (!object || object->instance == INS_NULL) return 0;
if (object->instance == INS_BOOLEAN) return object->value.boolean;
return 1;
}
int IsEqual(Object* a, Object* b) {
if (a->instance == INS_NULL && b->instance == INS_NULL) return 1;
if (a->instance == INS_NULL) return 0;
if (b->instance == INS_DOUBLE && b->instance == INS_DOUBLE) {
return a->value.number == b->value.number;
}
if (a->instance == INS_STRING && b->instance == INS_STRING) {
return strcmp(a->value.string, b->value.string) == 0;
}
return 0;
}
void CheckNumberOperands(TokenType operator, int operandCount, ...) {
va_list list;
va_start(list, operandCount);
for(int i = 0; i < operandCount; i++) {
Object* operand = va_arg(list, Object*);
if (operand->instance != INS_DOUBLE) {
va_end(list);
return; //TODO: "throw" runtime error "Operand must be a number."
}
}
va_end(list);
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef INTERPRETER_H
#define INTERPRETER_H
#include "expr.h"
#include "token.h"
typedef enum {
INS_STRING,
INS_DOUBLE,
INS_BOOLEAN,
INS_NULL
} ObjectInstanceType;
typedef struct Object {
ObjectInstanceType instance;
union {
char* string;
double number;
int boolean;
} value;
} Object;
void Interpret(Expr*);
void FreeObject(Object*);
#endif
+13 -10
View File
@@ -1,4 +1,7 @@
#include "scanner.h"
#include "parser.h"
#include "expr.h"
#include "interpreter.h"
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
@@ -21,19 +24,13 @@ int main(int argc, char** argv) {
}
void Print(TokenList* list) {
char lexeme[100];
for(int i = 0; i < list->size; i++) {
if (list->tokens[i]->type == EndOF) {
printf("[Line %d] EOF\n", list->tokens[i]->line);\
return;
}
printf("[Line %d] ", list->tokens[i]->line);
for(int j = 0; j < list->tokens[i]->length; j++) {
printf("%c", list->tokens[i]->lexeme[j]);
}
printf("\n");
printf("[Line %d] %s\n", list->tokens[i]->line, list->tokens[i]->lexeme);
}
}
@@ -41,10 +38,16 @@ void RunFile(const char* path) {
printf("Running '%s'\n", path);
char* contents = GetFileContents(path);
TokenList* tokens = ScanTokens(contents);
Print(tokens);
DestroyTokenList(tokens);
free(contents);
printf("TOKENS:\n");
Print(tokens);
printf("EXPRESSION TREE:\n");
Expr* tree = GenerateExpressionTree(tokens);
PrintExpressionTree(tree);
printf("\n");
Interpret(tree);
FreeExpressionTree(tree);
DestroyTokenList(tokens);
}
char* GetFileContents(const char* path) {
+288
View File
@@ -0,0 +1,288 @@
#include "parser.h"
#include "expr.h"
#include "statement.h"
#include "token.h"
#include <stdio.h>
Expr* Expression(void);
Expr* Equality(void);
Expr* Comparison(void);
Expr* Term(void);
Expr* Factor(void);
Expr* Unary(void);
Expr* Primary(void);
Stmt* Statement(void);
Stmt* PrintStatement(void);
Stmt* ExpressionStatement(void);
int Match(int, ...);
int Check(TokenType);
int ParserAtEnd(void);
Token* ParserPeek(void);
Token* Previous(void);
Token* AdvanceParser(void);
void SynchronizeParser(void);
const TokenList* ListOfTokens;
int Current = 0;
Expr* GenerateExpressionTree(const TokenList* list) {
ListOfTokens = list;
return Expression();
}
//Simply expands the equality rule
Expr* Expression() {
return Equality();
}
Stmt* Statement(void) {
if (Match(1, PRINT)) return PrintStatement();
return ExpressionStatement();
}
Stmt* PrintStatement(void) {
Expr* value = Expression();
if (!Match(1, Semicolon)) fprintf(stderr, "Expected ';' after value\n");
AdvanceParser();
return CreateStatement(value, STMT_Print);
}
Stmt* ExpressionStatement(void) {
Expr* expr = Expression();
if (!Match(1, Semicolon)) fprintf(stderr, "Expected ';' after expression\n");
AdvanceParser();
return CreateStatement(expr, STMT_Expression);
}
Stmt* ExpressionStatement(void);
Expr* Equality() {
Expr* expr = Comparison();
while(Match(2, Bang_Equal, Equal_Equal)) {
Token* operator = Previous();
Expr* right = Comparison();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Comparison() {
Expr* expr = Term();
while(Match(4, Greater, Greater_Equal, Less, Less_Equal)) {
Token* operator = Previous();
Expr* right = Term();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Term() {
Expr* expr = Factor();
while(Match(2, Minus, Plus)) {
Token* operator = Previous();
Expr* right = Factor();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Factor() {
Expr* expr = Unary();
while(Match(2, Slash, Star)) {
Token* operator = Previous();
Expr* right = Unary();
Expr* temp = calloc(1, sizeof(Expr));
temp->type = BINARY;
temp->expression.Binary.left = expr;
temp->expression.Binary.op = operator;
temp->expression.Binary.right = right;
expr = temp;
}
return expr;
}
Expr* Unary() {
if (Match(2, Bang, Minus)) {
Token* operator = Previous();
Expr* right = Unary();
Expr* expr = calloc(1, sizeof(Expr));
expr->type = UNARY;
expr->expression.Unary.op = operator;
expr->expression.Unary.right = right;
return expr;
}
return Primary();
}
Expr* Primary() {
Expr* expr = calloc(1, sizeof(Expr));
expr->type = LITERAL;
if (Match(3, FALSE, TRUE, NIL)) {
expr->expression.Literal = ParserPeek();
return expr;
}
if (Match(2, Number, String)) {
expr->expression.Literal = Previous();
return expr;
}
free(expr);
if (Match(1, LParen)) {
expr = Expression();
if (!Check(RParen)) {
free(expr);
printf("Unbalanced\n"); //Todo: something or another...
return NULL;
}
//Consume(RParen, "Expect ')' after expression.");
Expr* temp = calloc(1, sizeof(Expr));
temp->type = GROUPING;
temp->expression.Grouping.expression = expr;
return temp;
}
fprintf(stderr, "Bad expression, this should be unreachable.\n");
return NULL;
}
int Match(int count, ...) {
va_list list;
va_start(list, count);
for(int i = 0; i < count; i++) {
if(Check(va_arg(list, TokenType))) {
va_end(list);
AdvanceParser();
return 1;
}
}
va_end(list);
return 0;
}
int Check(TokenType type) {
if (ParserAtEnd()) return 0;
return ParserPeek()->type == type;
}
int ParserAtEnd() {
return ParserPeek()->type == EndOF;
}
Token* ParserPeek() {
return ListOfTokens->tokens[Current];
}
Token* Previous() {
return ListOfTokens->tokens[Current - 1];
}
Token* AdvanceParser() {
if (!ParserAtEnd()) Current++;
return Previous();
}
//In the Java implementation this get's called in catch blocks.
//Obviously that's not going to fly in C, so I need some way to
//"unwind" the stack. Maybe synchronizing (setting the Current variable)
//will be enough, and simply return NULLs up the call stack.
//Naive but might work in the future for this.
void SynchronizeParser(void) {
AdvanceParser();
//Discard tokens until we find a statement boundary, or at least something that looks like one.
while(!ParserAtEnd()) {
if (Previous()->type == Semicolon) return;
switch(ParserPeek()->type) {
case CLASS:
case FOR:
case FUN:
case IF:
case PRINT:
case RETURN:
case VAR:
case WHILE:
return;
default:
break;
}
AdvanceParser();
}
}
void PrintExpressionTree(const Expr* tree) {
if (!tree) return;
if (tree->type == BINARY) {
printf("(");
PrintExpressionTree(tree->expression.Binary.left);
PrintExpressionTree(tree->expression.Binary.right);
printf("%s", tree->expression.Binary.op->lexeme);
printf(")");
}
else if (tree->type == UNARY) {
printf("(");
printf("%s", tree->expression.Unary.op->lexeme);
PrintExpressionTree(tree->expression.Unary.right);
printf(")");
}
else if (tree->type == GROUPING) {
PrintExpressionTree(tree->expression.Grouping.expression);
}
else if (tree->type == LITERAL) printf("%s", tree->expression.Literal->lexeme);
}
void FreeExpressionTree(Expr* tree) {
if (!tree) return;
if (tree->type == BINARY) {
FreeExpressionTree(tree->expression.Binary.left);
FreeExpressionTree(tree->expression.Binary.right);
}
else if (tree->type == UNARY) {
FreeExpressionTree(tree->expression.Unary.right);
}
free(tree);
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef PARSER_H
#define PARSER_H
#include "expr.h"
#include "scanner.h"
#include "token.h"
#include "statement.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
Expr* GenerateExpressionTree(const TokenList* list);
void PrintExpressionTree(const Expr*);
void FreeExpressionTree(Expr*);
#endif
+298
View File
@@ -0,0 +1,298 @@
#include "scanner.h"
#include "token.h"
#include <string.h>
const char* source_code;
//Start and Current hold the offsets that index into the string source_code.
int start; //Points to the first character in the lexeme being scanned.
int current; //points to the character currently being considered.
int length;
int line = 1;
const char* AdvanceScanner(void);
int ScannerAtEnd(void);
void ScanToken(TokenList*);
TokenList* CreateList(void);
int AddToTokenList(Token*, TokenList*);
int ScannerMatch(char);
char ScannerPeek(void);
char PeekNext(void);
void ParseString(TokenList *);
void ParseNumber(TokenList *);
void ParseIdentifier(TokenList *);
int IsAlpha(char c);
KeyValuePair* Get(const char*);
TokenList* ScanTokens(const char* source) {
if (!source) return NULL;
source_code = source;
length = strlen(source);
if (length == 0) return NULL;
TokenList* tokens = CreateList();
while(!ScannerAtEnd()) {
start = current;
ScanToken(tokens);
}
AddToTokenList(CreateToken(NULL, NULL, line, EndOF), tokens);
return tokens;
}
TokenList* CreateList() {
TokenList* list = calloc(1, sizeof(TokenList));
if (!list) {
fprintf(stderr, "Failed to calloc TokenList.\n");
return NULL;
}
list->tokens = calloc(DEFAULT_TOKENLIST_SIZE, sizeof(Token*));
if (!list->tokens) {
free(list);
fprintf(stderr, "Failed to calloc tokens.\n");
return NULL;
}
list->capacity = DEFAULT_TOKENLIST_SIZE;
list->size = 0;
return list;
}
void DestroyTokenList(TokenList* list) {
if (!list) return;
for(int i = 0; i < list->size; i++) {
FreeToken(list->tokens[i]);
}
free(list->tokens);
free(list);
}
int AddToTokenList(Token* token, TokenList* list) {
if (!list) return 0;
if (!token) return 0;
if ((list->size + 1) > list->capacity) {
void* new_ptr = realloc(list->tokens, sizeof(Token*) * list->capacity * 2);
if (!new_ptr) {
fprintf(stderr, "Failed to realloc TokenList to size %d.\n", list->capacity * 2);
return 0;
}
list->tokens = new_ptr;
list->capacity = list->capacity * 2;
}
list->tokens[list->size] = token;
list->size++;
return 1;
}
void ScanToken(TokenList* tokens) {
const char* c = AdvanceScanner();
switch (*c) {
case '(':
AddToTokenList(CreateToken(NULL, NULL, line, LParen), tokens);
break;
case ')':
AddToTokenList(CreateToken(NULL, NULL, line, RParen), tokens);
break;
case '{':
AddToTokenList(CreateToken(NULL, NULL, line, LBrace), tokens);
break;
case '}':
AddToTokenList(CreateToken(NULL, NULL, line, RBrace), tokens);
break;
case ',':
AddToTokenList(CreateToken(NULL, NULL, line, Comma), tokens);
break;
case '.':
AddToTokenList(CreateToken(NULL, NULL, line, Dot), tokens);
break;
case '-':
AddToTokenList(CreateToken(NULL, NULL, line, Minus), tokens);
break;
case '+':
AddToTokenList(CreateToken(NULL, NULL, line, Plus), tokens);
break;
case ';':
AddToTokenList(CreateToken(NULL, NULL, line, Semicolon), tokens);
break;
case '*':
AddToTokenList(CreateToken(NULL, NULL, line, Star), tokens);
break;
case '!':
if (ScannerMatch('=')) AddToTokenList(CreateToken(NULL, NULL, line, Bang_Equal), tokens);
else AddToTokenList(CreateToken(NULL, NULL, line, Bang), tokens);
break;
case '=':
if (ScannerMatch('=')) AddToTokenList(CreateToken(NULL, NULL, line, Equal_Equal), tokens);
else AddToTokenList(CreateToken(NULL, NULL, line, Equal), tokens);
break;
case '<':
if (ScannerMatch('=')) AddToTokenList(CreateToken(NULL, NULL, line, Less_Equal), tokens);
else AddToTokenList(CreateToken(NULL, NULL, line, Less), tokens);
break;
case '>':
if (ScannerMatch('=')) AddToTokenList(CreateToken(NULL, NULL, line, Greater_Equal), tokens);
else AddToTokenList(CreateToken(NULL, NULL, line, Greater), tokens);
break;
case '/':
if (ScannerMatch('/')) while(ScannerPeek() != '\n' && !ScannerAtEnd()) { AdvanceScanner(); }
else AddToTokenList(CreateToken(NULL, NULL, line, Slash), tokens);
break;
case '"':
ParseString(tokens);
break;
case ' ':
case '\r':
case '\t':
break; //Ignore whitespace
case '\n':
line++;
break;
default:
if (isdigit(*c)) {
ParseNumber(tokens);
break;
} else if (IsAlpha(*c)) {
ParseIdentifier(tokens);
break;
}
fprintf(stderr, "Unexcpedted character %c\n", *c);
break;
}
}
int ScannerAtEnd() {
return current >= length;
}
const char* AdvanceScanner() {
return &source_code[current++];
}
int ScannerMatch(char expected) {
if (ScannerAtEnd()) return 0;
if (source_code[current] != expected) return 0;
current++;
return 1;
}
char ScannerPeek() {
if (ScannerAtEnd()) return '\0';
return source_code[current];
}
void ParseString(TokenList *list) {
start = current; //The start is currently pointing to the first double quote so we need to move it
//to the next (first character) of the string literal.
while(ScannerPeek() != '"' && !ScannerAtEnd()) {
if (ScannerPeek() == '\n') line++;
AdvanceScanner();
}
if (ScannerAtEnd()) {
fprintf(stderr, "Unterminated string.\n");
return;
}
AdvanceScanner();
char* lexeme = calloc(current - start + 1, sizeof(char));
if (!lexeme) {
fprintf(stderr, "Failed to calloc for string lexeme. %s\n", strerror(errno));
return;
}
snprintf(lexeme, current - start, "%s", &source_code[start]);
AddToTokenList(CreateToken(lexeme, lexeme, line, String), list);
}
void ParseNumber(TokenList* list) {
while(isdigit(ScannerPeek())) AdvanceScanner();
if (ScannerPeek() == '.' && isdigit(PeekNext())) {
AdvanceScanner();
while(isdigit(ScannerPeek())) AdvanceScanner();
}
AdvanceScanner();
char* lexeme = calloc(current - start + 1, sizeof(char));
if (!lexeme) {
fprintf(stderr, "Failed to calloc for number lexeme. %s\n", strerror(errno));
return;
}
snprintf(lexeme, current - start, "%s", &source_code[start]);
double* value = calloc(1, sizeof(double));
if (!value) {
fprintf(stderr, "Failed to calloc for number value. %s\n", strerror(errno));
free(lexeme);
return;
}
*value = atof(lexeme);
AddToTokenList(CreateToken(lexeme, value, line, Number), list);
}
char PeekNext() {
if (current + 1 >= length) return '\0';
return source_code[current + 1];
}
void ParseIdentifier(TokenList * list) {
while(IsAlpha(ScannerPeek())) AdvanceScanner();
char* lexeme = calloc(current - start + 2, sizeof(char));
snprintf(lexeme, current - start + 1, "%s", &source_code[start]);
KeyValuePair* result = Get(lexeme);
if (result) {
free(lexeme);
AddToTokenList(CreateToken(NULL, NULL, line, result->type), list);
}
else AddToTokenList(CreateToken(lexeme, lexeme, line, Identifier), list);
}
int IsAlpha(char c) {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c == '_');
}
KeyValuePair* Get(const char* text) {
if (!text) return NULL;
for (int i = 0; i < TOKENTYPE_MAPPINGS_COUNT; i++)
if (strcmp(text, TokenTypeMappings[i].lexeme) == 0) return &TokenTypeMappings[i];
return NULL;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef SCANNER_H
#define SCANNER_H
#include "token.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define DEFAULT_TOKENLIST_SIZE 32
typedef struct {
Token** tokens;
int capacity;
int size;
} TokenList;
TokenList* ScanTokens(const char*);
void DestroyTokenList(TokenList*);
#endif
+21
View File
@@ -0,0 +1,21 @@
#include "statement.h"
Stmt* CreateStatement(Expr* expression, StatementType type) {
Stmt* stmt = calloc(1, sizeof(Stmt));
if (!stmt) {
fprintf(stderr, "Failed to calloc space for Statement. %s.\n", strerror(errno));
return NULL;
}
stmt->expression = expression;
stmt->type = type;
return stmt;
}
void FreeStatement(Stmt* stmt) {
if (!stmt) return;
free(stmt);
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef STATEMENT_H
#define STATEMENT_H
#include "expr.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
typedef enum {
STMT_Expression,
STMT_Print
} StatementType;
typedef struct stmt {
StatementType type;
Expr* expression;
} Stmt;
Stmt* CreateStatement(Expr*, StatementType);
void FreeStatement(Stmt*);
#endif
+93
View File
@@ -0,0 +1,93 @@
#include "token.h"
#include <stdlib.h>
#include <string.h>
KeyValuePair TokenTypeMappings[TOKENTYPE_MAPPINGS_COUNT] = {
{ "(", LParen}, { ")", RParen}, { ",", Comma }, { ".", Dot },
{ "-", Minus }, { "+", Plus }, { ";", Semicolon }, { "/", Slash}, { "*", Star },
{ "!", Bang }, { "!=", Bang_Equal }, { "=", Equal }, { "==", Equal_Equal },
{ ">", Greater }, { ">=", Greater_Equal }, { "<", Less }, { "<=", Less_Equal },
{ "and", AND }, { "class", CLASS }, { "else", ELSE },
{ "false", FALSE }, { "for", FOR }, { "fun", FUN },
{ "if", IF }, { "nil", NIL }, { "or", OR },
{ "print", PRINT }, { "return", RETURN }, { "super", SUPER },
{ "this", THIS }, { "true", TRUE }, { "var", VAR }, { "while", WHILE },
{ "", EndOF }
};
const char* GetLexemeMapping(TokenType);
Token* CreateToken(const char* lexeme, void* literal, int line, TokenType type) {
Token* token = calloc(1, sizeof(Token));
if (!token) {
fprintf(stderr, "Failed to calloc token. %s", strerror(errno));
return NULL;
}
if (lexeme) {
token->literal = literal;
token->lexeme = lexeme;
}
else {
const char* mapping_result = GetLexemeMapping(type);
if (!mapping_result) {
fprintf(stderr, "Failed to get the mapping for TokenType value %d.\n", type);
free(token);
return NULL;
}
if (type != NIL) token->literal = mapping_result;
token->lexeme = mapping_result;
}
token->line = line;
token->type = type;
return token;
}
const char* GetLexemeMapping(TokenType type) {
for (int i = 0; i < TOKENTYPE_MAPPINGS_COUNT; i++) {
if (TokenTypeMappings[i].type == type) return TokenTypeMappings[i].lexeme;
}
return NULL;
}
void FreeToken(Token* token) {
if (!token) return;
if (token->type == String || token->type == Number || token->type == Identifier) free((void *) token->lexeme);
free(token);
}
char* GetTokenStringValue(Token* token, int* length) {
length = 0;
if (!token) return NULL;
if (token->type != String) return NULL;
*length = strlen(token->lexeme);
if (length == 0) return NULL;
char* value = calloc(strlen(token->lexeme) + 1, sizeof(char));
strncpy(value, token->lexeme, *length);
return value;
}
int GetTokenNumberValue(Token* token, double* value) {
value = 0;
if (!token) return 0;
if (token->type != Number) return 0;
*value = *((double*)token->literal);
return 1;
}
+22 -14
View File
@@ -1,8 +1,12 @@
#ifndef SCANNER_H
#define SCANNER_H
#ifndef TOKEN_H
#define TOKEN_H
#define DEFAULT_TOKENLIST_SIZE 32
#define KEYWORD_COUNT 16
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define TOKENTYPE_MAPPINGS_COUNT 34
typedef enum {
//Single-character tokens
@@ -31,19 +35,23 @@ typedef enum {
} TokenType;
typedef struct {
TokenType type;
const char* lexeme;
int line;
int length;
} Token;
TokenType type;
} KeyValuePair;
typedef struct {
Token** tokens;
int capacity;
int size;
} TokenList;
TokenType type;
const char* lexeme;
const void* literal;
int line;
} Token;
TokenList* ScanTokens(const char*);
void DestroyTokenList(TokenList*);
extern KeyValuePair TokenTypeMappings[TOKENTYPE_MAPPINGS_COUNT];
//If the first parameter is NULL, the token creation will attempt to infer the lexeme from the TokenType.
Token* CreateToken(const char*, void*, int, TokenType);
char* GetTokenStringValue(Token*, int*);
int GetTokenNumberValue(Token*, double*);
void FreeToken(Token*);
#endif