commit d6281d2f5cab600feb6e5b4d93c90fc578f27a22 Author: Josh K Date: Wed Jun 6 02:09:17 2018 -0400 Initial commit. My implementation of Clox, the programming language constructed from the book 'Crafting Interpreters' 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 +