include "string"; /* extern func println( string str ); template< typename T > array { T* m_data; int m_size; int m_mem; operator [] ( int index ) : T { if( index < 0 || index >= m_size ) { WARNING( "array index out of bounds" ); return T(); } return m_data[ index ]; } }; func main( array args ) { for( i = 0; i < args.size; ++i ) println( args[ i ] ); println( "Hello, world! (" $ args.size $ ")" ); } func _test() { main( [ "array", "data", "test" ] ); } */ /* PROBLEMS > efficient local variable encoding - all local variables have a scope but name should not be duplicated or resolved, we can use an offset table at most (padding could be arch-dependent) - { type = "scope", locals = [{ name = "one", type = "string" }, { name = "b", type = "int" }], code = [..] }, - locals are added to stack and accessed by their index IDEAS > WARNING arguments: - string specific - the case-specific error part ("15 outside [0;7]") - optional string generic - the error group part ("array index out of bounds") - optional string category - the error category path ("RangeError/OutOfBounds") */ global AST = { "string*" = { type = "pointer", datatype = "string" }, "array" = { type = "object", classname = "array", fullname = "array", members = [ { name = "m_data", type = "string*" }, { name = "m_size", type = "int" }, { name = "m_mem", type = "int" }, ], static = [], }, "array:[](int)" = { type = "function", retntype = "string", params = [ { name = "index", type = "int" }, ], locals = [], code = [ { type = "if", test = { type = "or", lft = { type = "lt", datatype = "int", lft = { type = "local", off = 0 }, rgt = { type = "data", datatype = "int", data = 0 } }, rgt = { type = "gte", datatype = "int", lft = { type = "local", off = 0 }, rgt = { type = "getmember", name = "m_size" } }, }, locals_ontrue = [], locals_onfalse = [], ontrue = [ { type = "fcall", func = "WARNING", ctx = null, args = [{ type = "data", datatype = "string", data = "array index out of bounds" }] }, { type = "return", arg = { type = "data", datatype = "string", data = "" } }, ], onfalse = [], }, { type = "return", arg = { type = "getindex", object = { type = "getmember", name = "m_data" }, index = { type = "local", off = 0 } } }, ], }, "array:size.get" = { type = "getter", retntype = "int", source = "m_size" }, "array:clear()" = { type = "function", retntype = "void", params = [], locals = [], code = [ { type = "fcall", func = "array:resize(int)", ctx = null, args = [{ type = "data", datatype = "int", data = 0 }] }, ], }, "println(string)" = { type = "external", func = println, retntype = "void", params = [{ name = "str", type = "string" }] }, "main(array)" = { type = "function", retntype = "void", params = [ { name = "args", type = "array" }, ], locals = [], code = [ { type = "for", init = { type = "setlocal", datatype = "int", off = 1, value = { type = "data", datatype = "int", data = 0 } }, test = { type = "lt", datatype = "int", lft = { type = "local", off = 1 }, rgt = { type = "getprop", object = { type = "local", off = 0 }, property = { type = "data", datatype = "string", data = "size" } } }, next = { type = "setlocal", datatype = "int", off = 1, value = { type = "add", datatype = "int", lft = { type = "local", off = 1 }, rgt = { type = "data", datatype = "int", data = 1 } } }, locals = [{ name = "i", type = "int" }], body = [ { type = "fcall", func = "println(string)", ctx = null, args = [{ type = "getindex", object = { type = "local", off = 0 }, index = { type = "local", off = 1 } }] }, ] }, { type = "fcall", func = "println(string)", ctx = null, args = [ { type = "concat", args = [ { type = "data", datatype = "string", data = "Hello, world! (" }, { type = "convert", datatype = "string", source = { type = "getprop", object = { type = "local", off = 0 }, property = { type = "data", datatype = "string", data = "size" } } }, { type = "data", datatype = "string", data = ")" }, ]}, ]}, ], }, "_test()" = { type = "function", retntype = "void", params = [], locals = [], code = [ { type = "fcall", func = "main(array)", ctx = null, args = [ { type = "data", datatype = "array", data = { m_data = [ { type = "string", data = "array" }, { type = "string", data = "data" }, { type = "string", data = "test" } ], m_size = { type = "int", data = 3 }, m_mem = { type = "int", data = 3 }, }}, ]}, ], }, }; function resolve_type( typename ) { tydata = @AST[ typename ]; if( tydata ) return tydata; if( typename != "int" && typename != "string" ) return ERROR( "no such type: " $ typename ); return { type = typename }; } function value_is_nonzero( value ) { if( !value ) return false; if( value.type == "bool" ) return value.data; if( value.type == "int" ) return value.data != 0; if( value.type == "string" ) return value.data.size != 0; return true; } global CALL_STACK = []; function do__push_locals( locals ) { outlocals = CALL_STACK.last.locals; foreach( local : locals ) outlocals.push({ name = local.name, type = local.type, data = null }); } function do__pop_locals( locals ) { for( i = 0; i < locals.size; ++i ) CALL_STACK.last.locals.pop(); } function do_fcall( func, ctx, args ) { fndata = @AST[ func ]; if( !fndata ) return WARNING( "function was not found: " $ func ); if( fndata.type == "external" ) { argsdata = []; foreach( carg : args ) argsdata.push( carg.data ); fndata.func.apply( ctx, argsdata ); } else { CALL_STACK.push ({ name = func, func = fndata, ctx = ctx, locals = [], retn = false, }); do__push_locals( fndata.params ); foreach( i, arg : args ) CALL_STACK.last.locals[ i ].data = arg; do__push_locals( fndata.locals ); foreach( line : fndata.code ) { do_item( line ); } return CALL_STACK.pop().retn; } } function vm_is_a( type, what ) { return type == what; } function do_concat( args ) { out = ""; foreach( arg : args ) { arg = do_item( arg ); if( !vm_is_a( arg.type, "string" ) ) ERROR( "CONCAT argument " $ arg $ " is not a string" ); out $= arg.data; } return { type = "string", data = out }; } function do_getprop( obj, prop ) { if( prop.type != "string" ) return WARNING( "property has invalid type: " $ prop ); typeinfo = resolve_type( obj.type ); key = typeinfo.fullname $ ":" $ prop.data $ ".get"; fndata = @AST[ key ]; if( fndata ) { if( fndata.type == "getter" ) return obj.data[ fndata.source ]; if( fndata.type == "function" ) return do_fcall( key, obj, [] ); } foreach( name, val : obj.data ) { if( name == prop.data ) return val; } WARNING( "unknown property " $ prop $ " for object " $ obj ); } function do_getindex( obj, idx ) { // special case: pointers if( is_array( obj ) ) { return obj[ idx.data ]; } typeinfo = resolve_type( obj.type ); key = typeinfo.fullname $ ":[](" $ idx.type $ ")"; fndata = @AST[ key ]; if( fndata ) { if( fndata.type == "function" ) return do_fcall( key, obj, [ idx ] ); } WARNING( "unknown index " $ idx $ " for object " $ obj ); } function do_convert( item, typeto ) { if( item.type == typeto ) return item; if( typeto == "string" ) { return { type = "string", data = tostring( item.data ) }; } ERROR( "don't know how to convert from " $ item.type $ " to " $ typeto ); } function do_add( lft, rgt ) { lft = do_item( lft ); rgt = do_item( rgt ); if( lft.type == "int" && rgt.type == "int" ) return { type = "int", data = lft.data + rgt.data }; ERROR( "don't know how to do 'add' on this type" ); } function do_lt( lft, rgt ) { lft = do_item( lft ); rgt = do_item( rgt ); if( lft.type == "int" && rgt.type == "int" ) return { type = "bool", data = lft.data < rgt.data }; ERROR( "don't know how to do 'lt' on this type" ); } function do_gte( lft, rgt ) { lft = do_item( lft ); rgt = do_item( rgt ); if( lft.type == "int" && rgt.type == "int" ) return { type = "bool", data = lft.data >= rgt.data }; ERROR( "don't know how to do 'gte' on this type" ); } function do_or( lft, rgt ) { if( value_is_nonzero( do_item( lft ) ) ) return true; return value_is_nonzero( do_item( rgt ) ); } function do_item( item ) { if( !item ) return; if( CALL_STACK.last.retn ) return; println( string_repeat( " ", 40 ) $ "doing " $ item.type $ " in " $ CALL_STACK.last.name ); if( item.type == "data" ) return { type = item.datatype, data = item.data }; if( item.type == "local" ) { local = CALL_STACK.last.locals[ item.off ]; println( string_repeat( " ", 30 ) $ "reading local " $ local.name $ "@" $ item.off $ " = " $ local.data ); return local.data; } if( item.type == "setlocal" ) { local = CALL_STACK.last.locals[ item.off ]; value = do_item( item.value ); println( string_repeat( " ", 30 ) $ "writing local " $ local.name $ "@" $ item.off $ " = " $ value ); return local.data = value; } if( item.type == "convert" ) { return do_convert( do_item( item.source ), item.datatype ); } if( item.type == "if" ) { if( value_is_nonzero( do_item( item.test ) ) ) { do__push_locals( item.locals_ontrue ); foreach( line : item.ontrue ) do_item( line ); do__pop_locals( item.locals_ontrue ); } else { do__push_locals( item.locals_onfalse ); foreach( line : item.onfalse ) do_item( line ); do__pop_locals( item.locals_onfalse ); } return; } if( item.type == "for" ) { do__push_locals( item.locals ); do_item( item.init ); while( value_is_nonzero( do_item( item.test ) ) ) { foreach( line : item.body ) do_item( line ); do_item( item.next ); } do__pop_locals( item.locals ); return; } if( item.type == "fcall" ) { args = []; foreach( arg : item.args ) args.push( do_item( arg ) ); return do_fcall( item.func, do_item( item.ctx ), args ); } if( item.type == "return" ) { CALL_STACK.last.retn = if( @item.arg, do_item( item.arg ), true ); return; } if( item.type == "concat" ) return do_concat( item.args ); if( item.type == "getprop" ) return do_getprop( do_item( item.object ), do_item( item.property ) ); if( item.type == "getmember" ) return do_getprop( CALL_STACK.last.ctx, { type = "string", data = item.name } ); if( item.type == "getindex" ) return do_getindex( do_item( item.object ), do_item( item.index ) ); if( item.type == "add" ) return do_add( item.lft, item.rgt ); if( item.type == "lt" ) return do_lt( item.lft, item.rgt ); if( item.type == "gte" ) return do_gte( item.lft, item.rgt ); if( item.type == "or" ) return do_or( item.lft, item.rgt ); ERROR( "cannot do action " $ item.type ); } do_fcall( "_test()", null, [] );