Initial commit. My implementation of Clox, the programming language constructed from the book 'Crafting Interpreters'

This commit is contained in:
Josh K 2018-06-06 02:09:17 -04:00
commit d6281d2f5c
15 changed files with 629 additions and 0 deletions

82
Makefile Normal file
View File

@ -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)

65
chunk.c Normal file
View File

@ -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 );
}
}

43
chunk.h Normal file
View File

@ -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

12
common.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef _COMMON_H
#define _COMMON_H
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#define DEBUG_TRACE_EXECUTION
#endif

7
compiler.c Normal file
View File

@ -0,0 +1,7 @@
#include "compiler.h"
//#include "scanner.h"
void compile( VM* vm, const char* src ) {
//initScanner( src );
}

9
compiler.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef _COMPILER_H
#define _COMPILER_H
#include "vm.h"
void compile( VM* vm, const char* src );
#endif

90
debug.c Normal file
View File

@ -0,0 +1,90 @@
//#include <stdio.h>
#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;
}
}

11
debug.h Normal file
View File

@ -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

118
main.c Normal file
View File

@ -0,0 +1,118 @@
#include "common.h"
#include "chunk.h"
#include "debug.h"
#include "vm.h"
#include <time.h>
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 ) {}

13
memory.c Normal file
View File

@ -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 );
}

16
memory.h Normal file
View File

@ -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

29
value.c Normal file
View File

@ -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 );
}

20
value.h Normal file
View File

@ -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

86
vm.c Normal file
View File

@ -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;
}

28
vm.h Normal file
View File

@ -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