From d6281d2f5cab600feb6e5b4d93c90fc578f27a22 Mon Sep 17 00:00:00 2001 From: Josh K Date: Wed, 6 Jun 2018 02:09:17 -0400 Subject: [PATCH] Initial commit. My implementation of Clox, the programming language constructed from the book 'Crafting Interpreters' --- Makefile | 82 +++++++++++++++++++++++++++++++++++++ chunk.c | 65 +++++++++++++++++++++++++++++ chunk.h | 43 +++++++++++++++++++ common.h | 12 ++++++ compiler.c | 7 ++++ compiler.h | 9 ++++ debug.c | 90 ++++++++++++++++++++++++++++++++++++++++ debug.h | 11 +++++ main.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++ memory.c | 13 ++++++ memory.h | 16 ++++++++ value.c | 29 +++++++++++++ value.h | 20 +++++++++ vm.c | 86 ++++++++++++++++++++++++++++++++++++++ vm.h | 28 +++++++++++++ 15 files changed, 629 insertions(+) create mode 100644 Makefile create mode 100644 chunk.c create mode 100644 chunk.h create mode 100644 common.h create mode 100644 compiler.c create mode 100644 compiler.h create mode 100644 debug.c create mode 100644 debug.h create mode 100644 main.c create mode 100644 memory.c create mode 100644 memory.h create mode 100644 value.c create mode 100644 value.h create mode 100644 vm.c create mode 100644 vm.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d1c37ee --- /dev/null +++ b/Makefile @@ -0,0 +1,82 @@ +# Detect OS + +ifeq ($(OS),Windows_NT) + ISWIN := 1 +endif + +ifdef ISWIN + CC := i686-w64-mingw32-gcc +else + CC := gcc +endif + +RM ?= rm -f +RMDIR ?= rm -rf +MKDIR ?= mkdir -p + +WARFLAGS := -Wall -Wextra -pedantic + +CFLAGS_g := $(WARFLAGS) -std=gnu11 -O2 -msse2 -ffast-math -mfpmath=sse -DNDEBUG \ + -MMD + +LDFLAGS_g := -s + +# object output dir +OBJDIR_g := obj_g + +SRCS := $(wildcard *.c) +OBJS_g := $(SRCS:.c=.o) + +# include dirs +CFLAGS_g += + +# libs +ifdef ISWIN + LDFLAGS_g += +else + CFLAGS_g += + LDFLAGS_g += +endif + +# binary target +ifdef ISWIN + TARG_g := clox.exe +else + TARG_g := clox +endif + +game: $(TARG_g) + @echo "*** Target '$@': '$^' is up to date!" + +default: game + +.PHONY: default game clean + +# rewrite OBJS so it outputs object files to seperate dir + +OBJS_g := $(patsubst %,$(OBJDIR_g)/%,$(OBJS_g)) + +# include the dependency rules generated by -MMD + +-include $(OBJS_g:.o=.d) + +clean: + @echo "*** Removing target binary and object directory..." + @$(RM) $(TARG_g) + @$(RMDIR) $(OBJDIR_g) + @echo "*** Done." + +# Compile + +$(OBJDIR_g)/%.o: %.c + @$(MKDIR) $(@D) + @echo "*** Compiling '$<' ..." + @$(CC) $(CFLAGS_g) -c $< -o $@ + +# Link + +$(TARG_g): $(OBJS_g) + @$(MKDIR) $(@D) + @echo "\n*** Linking binary target '$@' ...\n" + @$(CC) $(OBJS_g) -o $@ $(LDFLAGS_g) + diff --git a/chunk.c b/chunk.c new file mode 100644 index 0000000..49de696 --- /dev/null +++ b/chunk.c @@ -0,0 +1,65 @@ +#include "chunk.h" +#include "memory.h" +#include "value.h" + +void initChunk( Chunk* chunk ) { + chunk->capacity = 0; + chunk->count = 0; + chunk->bDebug = true; + chunk->code = NULL; + chunk->lines = NULL; + initValueArray( &chunk->constants ); +} + +void freeChunk( Chunk* chunk ) { + FREE_ARRAY( uint8_t, chunk->code, chunk->capacity ); + FREE_ARRAY( uint16_t, chunk->lines, chunk->capacity ); + freeValueArray( &chunk->constants ); + initChunk( chunk ); +} + +void writeChunk( Chunk* chunk, uint8_t byte, uint16_t line ) { + if ( chunk->capacity < chunk->count + 1 ) { + int oldCap = chunk->capacity; + chunk->capacity = GROW_CAPACITY( oldCap ); + chunk->code = GROW_ARRAY( chunk->code, uint8_t, oldCap, chunk->capacity ); + if ( chunk->bDebug ) { + chunk->lines = GROW_ARRAY( chunk->lines, uint16_t, oldCap, chunk->capacity ); + } + } + + chunk->code[chunk->count] = byte; + if ( chunk->bDebug ) + chunk->lines[chunk->count] = line; + chunk->count++; +} + +// unsigned 16bit write +void writeChunk16( Chunk* chunk, uint16_t word, uint16_t line ) { + writeChunk( chunk, word & 255, line ); + writeChunk( chunk, word >> 8, line ); +} +// unsigned 16bit read +uint16_t readChunk16( Chunk* chunk, int offset ) { + uint16_t val = chunk->code[offset]; + return ((val) | (chunk->code[offset + 1] << 8)); +} + +uint16_t addConstant( Chunk* chunk, Value val ) { + writeValueArray( &chunk->constants, val ); + return chunk->constants.count - 1; +} + +// high level for adding and writing a chunk constant +void writeConstant( Chunk* chunk, Value val, uint16_t line ) { + uint16_t cont = addConstant( chunk, val ); + // more than 256 constants, use 16bit operand + if ( cont > 255 ) { + writeChunk( chunk, OP_CONSTANT_LONG, line ); + writeChunk16( chunk, cont, line ); + } else { + writeChunk( chunk, OP_CONSTANT, line ); + writeChunk( chunk, cont, line ); + } +} + diff --git a/chunk.h b/chunk.h new file mode 100644 index 0000000..bad73e0 --- /dev/null +++ b/chunk.h @@ -0,0 +1,43 @@ +#ifndef _CHUNK_H +#define _CHUNK_H + +#include "common.h" +#include "value.h" + +// max 256 opcodes +typedef enum { + OP_CONSTANT, OP_CONSTANT_LONG, + OP_ADD, OP_SUBTRACT, + OP_MULTIPLY, OP_DIVIDE, + OP_NEGATE, + OP_RETURN, + + NUM_OP, // total number of valid opcodes +} OpCode; + +typedef struct { + int count; + int capacity; + bool bDebug; // if line numbers are kept or not + uint8_t* code; + uint16_t* lines; // 16bit line numbers + ValueArray constants; +} Chunk; + +void initChunk( Chunk* chunk ); +void freeChunk( Chunk* chunk ); + +// write single byte to code memory +void writeChunk( Chunk* chunk, uint8_t byte, uint16_t line ); +// u16 write / read +void writeChunk16( Chunk* chunk, uint16_t word, uint16_t line ); +uint16_t readChunk16( Chunk* chunk, int offset ); + +// add constant val to chunk's array. return index where it was added +uint16_t addConstant( Chunk* chunk, Value val ); + +// high level for adding and writing chunk constants +void writeConstant( Chunk* chunk, Value val, uint16_t line ); + +#endif + diff --git a/common.h b/common.h new file mode 100644 index 0000000..a06556c --- /dev/null +++ b/common.h @@ -0,0 +1,12 @@ +#ifndef _COMMON_H +#define _COMMON_H + +#include +#include +#include +#include + +#define DEBUG_TRACE_EXECUTION + +#endif + diff --git a/compiler.c b/compiler.c new file mode 100644 index 0000000..fbbe76e --- /dev/null +++ b/compiler.c @@ -0,0 +1,7 @@ +#include "compiler.h" +//#include "scanner.h" + +void compile( VM* vm, const char* src ) { + //initScanner( src ); +} + diff --git a/compiler.h b/compiler.h new file mode 100644 index 0000000..35c4bcd --- /dev/null +++ b/compiler.h @@ -0,0 +1,9 @@ +#ifndef _COMPILER_H +#define _COMPILER_H + +#include "vm.h" + +void compile( VM* vm, const char* src ); + +#endif + diff --git a/debug.c b/debug.c new file mode 100644 index 0000000..40bf887 --- /dev/null +++ b/debug.c @@ -0,0 +1,90 @@ +//#include + +#include "debug.h" +#include "value.h" + +void disassembleChunk( Chunk* chunk, const char* name ) { + printChunkInfo( chunk, name ); + for ( int i = 0; i < chunk->count; ) { + i = disassembleInstruction( chunk, i ); + } +} + +void printChunkInfo( Chunk* chunk, const char* name ) { + int codesz = chunk->count * sizeof(chunk->code[0]); + int codecapsz = chunk->capacity * sizeof(chunk->code[0]); + int concnt = chunk->constants.count; + int concap = chunk->constants.capacity; + int consz = concnt * sizeof(chunk->constants.values[0]); + int concapsz = concap * sizeof(chunk->constants.values[0]); + + int linesz = sizeof(chunk->lines[0]) * chunk->count; + int linecapsz = sizeof(chunk->lines[0]) * chunk->capacity; + if ( !chunk->bDebug ) { + linesz = 0; linecapsz = 0; + } + + printf( "=== Chunk \"%s\" Info ===\n=== Code size: %d(%d) bytes ===\n" + "=== Constants: %d(%d) (%d(%d) bytes) ===\n=== Debug lines size: %d(%d) bytes ===\n", name, + codesz, codecapsz, concnt, concap, consz, concapsz, linesz,linecapsz ); + printf( "=== Total size: %d(%d) bytes ===\n", sizeof(Chunk)+codesz+consz+linesz, sizeof(Chunk)+codecapsz+concapsz+linecapsz ); +} + +static int constantInstruction( const char* name, Chunk* chunk, int offset ) { + uint16_t cons = 0; + + cons = chunk->code[offset + 1]; + + if ( chunk->code[offset] == OP_CONSTANT_LONG ) { + name = "OP_CONSTANT_LONG"; + cons = readChunk16( chunk, offset + 1 ); + } + + printf( "%-16s %05u '", name, cons ); + Value conVal = -9001; // cons index out of range + if ( cons < chunk->constants.count ) + conVal = chunk->constants.values[cons]; + printValue( conVal ); + printf( "'\n" ); + + return ((cons > 255) ? (offset + 3) : (offset + 2)); +} + +static int simpleInstruction( const char* name, int offset ) { + printf( "%s\n", name ); + return offset + 1; +} + +int disassembleInstruction( Chunk* chunk, int offset ) { + printf( "0x%08x ", offset ); + if ( chunk->bDebug ) { + if ( offset > 0 && chunk->lines[offset] == chunk->lines[offset - 1] ) { + printf( " | " ); + } else { + printf( "%04d ", chunk->lines[offset] ); + } + } + + uint8_t instruction = chunk->code[offset]; + switch ( instruction ) { + case OP_CONSTANT: + case OP_CONSTANT_LONG: + return constantInstruction( "OP_CONSTANT", chunk, offset ); + case OP_ADD: + return simpleInstruction( "OP_ADD", offset ); + case OP_SUBTRACT: + return simpleInstruction( "OP_SUBTRACT", offset ); + case OP_MULTIPLY: + return simpleInstruction( "OP_MULTIPLY", offset ); + case OP_DIVIDE: + return simpleInstruction( "OP_DIVIDE", offset ); + case OP_NEGATE: + return simpleInstruction( "OP_NEGATE", offset ); + case OP_RETURN: + return simpleInstruction( "OP_RETURN", offset ); + default: + printf( "UNK OP %d\n", instruction ); + return offset + 1; + } +} + diff --git a/debug.h b/debug.h new file mode 100644 index 0000000..4a5a9d1 --- /dev/null +++ b/debug.h @@ -0,0 +1,11 @@ +#ifndef _DEBUG_H +#define _DEBUG_H + +#include "chunk.h" + +void printChunkInfo( Chunk* chunk, const char* name ); +void disassembleChunk( Chunk* chunk, const char* name ); +int disassembleInstruction( Chunk* chunk, int offset ); + +#endif + diff --git a/main.c b/main.c new file mode 100644 index 0000000..329b98a --- /dev/null +++ b/main.c @@ -0,0 +1,118 @@ +#include "common.h" +#include "chunk.h" +#include "debug.h" +#include "vm.h" + +#include + +static void repl( VM* vm ) { + char line[512]; + + for ( ; ; ) { + printf( "> " ); + if ( !fgets( line, sizeof(line), stdin ) ) { + printf( "\n" ); break; + } + interpret( vm, line ); + } +} + +static char* readFile( const char* path ) { + FILE* file = fopen( path, "rb" ); + if ( file == NULL ) { + fprintf( stderr, "Error opening file: \"%s\"\n", path ); + exit( 74 ); + } + + fseek( file, 0, SEEK_END ); + size_t sz = ftell( file ); + rewind( file ); + + char* src = (char*)malloc( sz + 1 ); + if ( src == NULL ) { + fprintf( stderr, "Error allocating memory for file: \"%s\"\n", path ); + exit( 74 ); + } + size_t bread = fread( src, sizeof(char), sz, file ); + if ( bread < sz ) { + fprintf( stderr, "Error reading file: \"%s\"\n", path ); + exit( 74 ); + } + src[bread] = '\0'; + + fclose( file ); + return src; +} + +static void runFile( VM* vm, const char* path ) { + char* source = readFile( path ); + InterpretResult res = interpret( vm, source ); + free( source ); + if ( res == INTERPRET_COMPILE_ERROR ) exit( 65 ); + if ( res == INTERPRET_RUNTIME_ERROR ) exit( 70 ); +} + +int main( int argc, char** argv ) { + (void)argc; (void)argv; + srand( time( NULL ) ); + + double dfp = 22.0 / 7.0; + printf( "DFP: %.64g\n", dfp ); + + float ffp = 22.0f / 7.0f; + printf( "FFP: %.64f\n", ffp ); + + VM vm; + initVM( &vm ); + + if ( argc == 1 ) { + repl( &vm ); + } else if ( argc == 2 ) { + runFile( &vm, argv[1] ); + } else { + fprintf( stderr, "Usage: clox [path]\n" ); + exit( EXIT_FAILURE ); + } + + /*Chunk c; + initChunk( &c ); + //c.bDebug = false; + + writeConstant( &c, 4.2f, 1 ); + writeConstant( &c, rand()/(float)RAND_MAX, 2 ); + + //for ( int i = 0; i < 10; ++i ) + // writeChunk( &c, rand() % NUM_OP, 5 ); + + // const overload + for ( int i = 2; i < 6; ++i ) { + writeConstant( &c, (float)rand()/RAND_MAX, 6 ); + } + + writeConstant( &c, 69069, 9 ); + writeChunk( &c, OP_NEGATE, 9 ); + + writeConstant( &c, 22, 9 ); + writeChunk( &c, OP_ADD, 9 ); + + writeConstant( &c, 42, 9 ); + writeChunk( &c, OP_DIVIDE, 9 ); + + writeChunk( &c, OP_RETURN, 10 ); + + //disassembleChunk( &c, "test" ); + + interpret( &vm, &c ); + printChunkInfo( &c, "test" ); + + printf( "vm %u chunk %u valar %u\n", sizeof(VM), sizeof(Chunk), sizeof(ValueArray) ); + + freeChunk( &c );*/ + + freeVM( &vm ); + + return EXIT_SUCCESS; +} + +//void* malloc( size_t sz ) {} + diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..faec37e --- /dev/null +++ b/memory.c @@ -0,0 +1,13 @@ +#include "common.h" +#include "memory.h" + +void* reallocate( void* prev, size_t oldSz, size_t newSz ) { + (void)oldSz; + if ( newSz == 0 ) { + free( prev ); prev = NULL; + return NULL; + } + + return realloc( prev, newSz ); +} + diff --git a/memory.h b/memory.h new file mode 100644 index 0000000..1025518 --- /dev/null +++ b/memory.h @@ -0,0 +1,16 @@ +#ifndef _MEMORY_H +#define _MEMORY_H + +#define GROW_CAPACITY( cap ) \ + ((cap) < 8 ? 8 : (cap) * 1.5f) + +#define GROW_ARRAY( prev, type, oldCnt, newCnt ) \ + (type*)reallocate( prev, sizeof(type) * (oldCnt), sizeof(type) * (newCnt) ) + +#define FREE_ARRAY( type, ptr, oldCnt ) \ + reallocate( ptr, sizeof(type) * (oldCnt), 0 ) + +void* reallocate( void* prev, size_t oldSz, size_t newSz ); + +#endif + diff --git a/value.c b/value.c new file mode 100644 index 0000000..19e6aff --- /dev/null +++ b/value.c @@ -0,0 +1,29 @@ +#include "value.h" +#include "memory.h" + +void initValueArray( ValueArray* array ) { + array->capacity = 0; + array->count = 0; + array->values = NULL; +} + +void writeValueArray( ValueArray* array, Value val ) { + if ( array->capacity < array->count + 1 ) { + int oldCap = array->capacity; + array->capacity = GROW_CAPACITY( oldCap ); + array->values = GROW_ARRAY( array->values, Value, oldCap, array->capacity ); + } + + array->values[array->count] = val; + array->count++; +} + +void freeValueArray( ValueArray* array ) { + FREE_ARRAY( Value, array->values, array->capacity ); + initValueArray( array ); +} + +void printValue( Value val ) { + printf( "%g", val ); +} + diff --git a/value.h b/value.h new file mode 100644 index 0000000..8227d1a --- /dev/null +++ b/value.h @@ -0,0 +1,20 @@ +#ifndef _VALUE_H +#define _VALUE_H + +#include "common.h" + +typedef float Value; + +typedef struct { + int capacity; + int count; + Value* values; +} ValueArray; + +void initValueArray( ValueArray* array ); +void writeValueArray( ValueArray* array, Value val ); +void freeValueArray( ValueArray* array ); +void printValue( Value val ); + +#endif + diff --git a/vm.c b/vm.c new file mode 100644 index 0000000..9a54e79 --- /dev/null +++ b/vm.c @@ -0,0 +1,86 @@ +#include "common.h" +#include "vm.h" +#include "compiler.h" +#include "debug.h" + +static void resetStack( VM* vm ) { + vm->sp = vm->stack; +} + +void initVM( VM* vm ) { + resetStack( vm ); +} + +void freeVM( VM* vm ) { + (void)vm; +} + +void push( VM* vm, Value val ) { + if ( vm->sp - vm->stack >= MAX_STACK ) { + printf( "VM Error: Stack is full.\n" ); + resetStack( vm ); + } + *(vm->sp) = val; + vm->sp++; +} + +Value pop( VM* vm ) { + vm->sp--; + return *(vm->sp); +} + +static InterpretResult run( VM* vm ) { + #define READ_BYTE() (*(vm->ip++)) + #define READ_CONSTANT( ix ) (vm->chunk->constants.values[ix]) + #define BINARY_OP( op ) do { Value b = pop( vm ), a = pop( vm ); \ + push( vm, a op b ); } while ( false ) + + uint8_t instruction = 0; + + for ( ; ; ) { +#ifdef DEBUG_TRACE_EXECUTION + printf( "\t" ); + for ( Value* v = vm->stack; v < vm->sp; v++ ) { + printf( "[ " ); + printValue( *v ); + printf( " ]" ); + } + printf( "\n" ); + disassembleInstruction( vm->chunk, (int)(vm->ip - vm->chunk->code) ); +#endif + + switch ( instruction = READ_BYTE() ) { + case OP_CONSTANT: + case OP_CONSTANT_LONG: { + uint16_t cons = READ_BYTE(); + if ( instruction == OP_CONSTANT_LONG ) + cons |= (READ_BYTE() << 8); + Value constant = READ_CONSTANT( cons ); + push( vm, constant ); + break; + } + case OP_ADD: BINARY_OP( + ); break; + case OP_SUBTRACT: BINARY_OP( - ); break; + case OP_MULTIPLY: BINARY_OP( * ); break; + case OP_DIVIDE: BINARY_OP( / ); break; + case OP_NEGATE: push( vm, -pop( vm ) ); break; + case OP_RETURN: + printValue( pop( vm ) ); + printf( "\n" ); + return INTERPRET_OK; + } + } + + #undef BINARY_OP + #undef READ_CONSTANT + #undef READ_BYTE +} + +InterpretResult interpret( VM* vm, const char* src ) { + /*vm->chunk = chunk; + vm->ip = vm->chunk->code; + return run( vm );*/ + compile( vm, src ); + return INTERPRET_OK; +} + diff --git a/vm.h b/vm.h new file mode 100644 index 0000000..a5768d6 --- /dev/null +++ b/vm.h @@ -0,0 +1,28 @@ +#ifndef _VM_H +#define _VM_H + +#include "chunk.h" + +#define MAX_STACK 256 + +typedef struct { + Chunk* chunk; + uint8_t* ip; + Value stack[MAX_STACK]; + Value* sp; +} VM; + +typedef enum { + INTERPRET_OK, + INTERPRET_COMPILE_ERROR, + INTERPRET_RUNTIME_ERROR +} InterpretResult; + +void initVM( VM* vm ); +void freeVM( VM* vm ); +InterpretResult interpret( VM* vm, const char* src ); +void push( VM* vm, Value val ); +Value pop( VM* vm ); + +#endif +