From ca9c150fc0b67a3d6901594f2d2d2f22017b166e Mon Sep 17 00:00:00 2001 From: jluehrs2 Date: Sun, 2 Sep 2007 15:50:48 -0500 Subject: initial import --- src/forth.lua | 905 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test.snt | 15 + test/test2.snt | 8 + 3 files changed, 928 insertions(+) create mode 100644 src/forth.lua create mode 100644 test/test.snt create mode 100644 test/test2.snt diff --git a/src/forth.lua b/src/forth.lua new file mode 100644 index 0000000..503d2a1 --- /dev/null +++ b/src/forth.lua @@ -0,0 +1,905 @@ +#!/usr/bin/lua + +local debug = false +local xdebug = false + +local stack, while_stack, if_stack, commands = {}, {}, {}, {} + +local executing = true +local look_for_end_while = false + +-- basic stack manipulation +local function push( val ) + table.insert( stack, tostring( val ) ) +end + +local function pop() + return table.remove( stack ) +end + +local function peek( n ) + return stack[ table.getn( stack ) - n ] +end + +-- returns true if every element of tab is true +-- uses i and v because a for loop with ipairs() was about 30% slower +local function multiand( tab ) + local i = 1 + local v = tab[ i ] + while v ~= nil do + if not v then return false end + i = i + 1 + v = tab[ i ] + end + return true +end + +-- executes the given function (in the form of an array of ops) +local function exec_ops( optable ) + local i = 1 + local v = optable[ i ] + + while v do + if look_for_end_while then + if v == keywords["repeat"] or + v == keywords["until"] then + look_for_end_while = false + end + i = i + 1 + else + if executing then + local oldi = i + i = v( i ) + if i == 0 then return end -- ;, exit + while type( i ) == "string" do -- execute + if keywords[ i ] then + i = keywords[ i ]() + elseif userfuncs[ i ] then + i = userfuncs[ i ]() + else + if debug then + io.write( "invalid function name: ", i, "\n" ) + end + break + end + end + if not i then i = oldi + 1 end + if xdebug then + if optable.name then + io.write( optable.name, ": ", oldi, " (", + ( commands[ optable.loc + i ] or "" ), + "): { " + ) + for _, v in ipairs( stack ) do + io.write( "\"", v, "\" " ) + end + io.write( "}\n" ) + end + end + if done then return end + else + if v == keywords["if"] or + v == keywords["else"] or + v == keywords["then"] then + v() + end + i = i + 1 + end + end + + v = optable[ i ] + end + + if debug then io.write( "unterminated function: ", optable.name, "\n" ) end +end + +-- __call metamethod for ops +local function op_call( func ) + local d, t = func.data, type( func.data ) + if t == "table" then return exec_ops( d ) end + if t == "string" then + if string.sub( d, 1, 1 ) == "\"" then + return push( string.sub( d, 2 ) ) + elseif keywords[ d ] then return keywords[ d ]() + elseif userfuncs[ d ] then return userfuncs[ d ]() + end + end + if t == "number" then return push( d ) end +end + +-- user defined variable table, with some initial definitions +local uservars = { + ["pi"] = math.pi, + ["e"] = math.exp( 1 ), +} + +-- built in keywords for the language +keywords = { + ["+"] = function() + push( pop() + pop() ) + end + , + ["-"] = function() + push( -pop() + pop() ) + end + , + ["*"] = function() + push( pop() * pop() ) + end + , + ["/"] = function() + push( 1 / pop() * pop() ) + end + , + ["%"] = function() + local num1, num2 = pop(), pop() + push( math.mod( num2, num1 ) ) + end + , + ["^"] = function() + local num1, num2 = pop(), pop() + push( num2 ^ num1 ) + end + , + ["notify"] = function() + io.write( pop(), "\n" ) + end + , + ["read"] = function() + push( io.read() ) + end + , + ["pop"] = function() + pop() + end + , + ["dup"] = function() + push( peek( 0 ) ) + end + , + ["swap"] = function() + local val1, val2 = pop(), pop() + push( val1 ) + push( val2 ) + end + , + ["over"] = function() + push( peek( 1 ) ) + end + , + ["rotate"] = function() + local num = tonumber( pop() ) + if num > 0 then + push( table.remove( stack, + table.getn( stack ) - num + 1 + ) + ) + elseif num < 0 then + table.insert( stack, table.getn( stack ) + num + 1, + table.remove( stack ) + ) + end + end + , + ["pick"] = function() + push( peek( pop() - 1 ) ) + end + , + ["="] = function() + if tonumber( pop() ) == tonumber( pop() ) then push( 1 ) + else push( 0 ) + end + end + , + ["!="] = function() + if tonumber( pop() ) ~= tonumber( pop() ) then push( 1 ) + else push( 0 ) + end + end + , + [">"] = function() + if tonumber( pop() ) < tonumber( pop() ) then push( 1 ) + else push( 0 ) + end + end + , + ["<"] = function() + if tonumber( pop() ) > tonumber( pop() ) then push( 1 ) + else push( 0 ) + end + end + , + [">="] = function() + if tonumber( pop() ) <= tonumber( pop() ) then push( 1 ) + else push( 0 ) + end + end + , + ["<="] = function() + if tonumber( pop() ) >= tonumber( pop() ) then push( 1 ) + else push( 0 ) + end + end + , + ["sin"] = function() + push( math.sin( pop() ) ) + end + , + ["cos"] = function() + push( math.cos( pop() ) ) + end + , + ["tan"] = function() + push( math.tan( pop() ) ) + end + , + ["asin"] = function() + push( math.asin( pop() ) ) + end + , + ["acos"] = function() + push( math.acos( pop() ) ) + end + , + ["atan"] = function() + push( math.atan( pop() ) ) + end + , + ["atan2"] = function() + local num = pop() + push( math.atan2( pop(), num ) ) + end + , + ["abs"] = function() + push( math.abs( pop() ) ) + end + , + ["ceil"] = function() + push( math.ceil( pop() ) ) + end + , + ["floor"] = function() + push( math.floor( pop() ) ) + end + , + ["rad"] = function() + push( math.rad( pop() ) ) + end + , + ["deg"] = function() + push( math.deg( pop() ) ) + end + , + ["log"] = function() + push( math.log10( pop() ) ) + end + , + ["ln"] = function() + push( math.log( pop() ) ) + end + , + ["max"] = function() + push( math.max( pop(), pop() ) ) + end + , + ["min"] = function() + push( math.min( pop(), pop() ) ) + end + , + ["and"] = function() -- locals to avoid short circuiting + local num1, num2 = tonumber( pop() ), tonumber( pop() ) + if num1 ~= 0 and num2 ~= 0 then push( 1 ) + else push( 0 ) + end + end + , + ["or"] = function() -- locals to avoid short circuiting + local num1, num2 = tonumber( pop() ), tonumber( pop() ) + if num1 == 0 and num2 == 0 then push( 0 ) + else push( 1 ) + end + end + , + ["not"] = function() + if tonumber( pop() ) == 0 then push( 1 ) + else push( 0 ) + end + end + , + ["strcat"] = function() + local str = pop() + push( pop() .. str ) + end + , + ["spac"] = function() + push( " " ) + end + , + ["empty"] = function() + while peek( 0 ) do pop() end + end + , + ["stack"] = function() + for i = 0, table.getn( stack ) - 1 do + io.write( peek( i ), "\n" ) + end + end + , + ["depth"] = function() + push( table.getn( stack ) ) + end + , + ["explode"] = function() + local delim, str, count, ret = pop(), pop(), 0, {} + str = str .. delim + for match in string.gfind( str, "(.-)" .. delim ) do + table.insert( ret, match ) + count = count + 1 + end + -- push in reverse order so they pop off in order + for i = table.getn( ret ), 1, -1 do + push( ret[ i ] ) + end + push( count ) + end + , + ["!"] = function() + uservars[ pop() ] = pop() + end + , + ["@"] = function() + push( uservars[ pop() ] or 0 ) + end + , + ["unlet"] = function() + uservars[ pop() ] = nil + end + , + ["begin"] = function( i ) + table.insert( while_stack, { loc = i + 1, ifs = 0 } ) + end + , + ["while"] = function( i ) + if tonumber( pop() ) ~= 0 then + return i + 1 + else + table.remove( while_stack ) + look_for_end_while = true + end + end + , + ["until"] = function() + if tonumber( pop() ) == 0 then + return while_stack[ table.getn( while_stack ) ].loc + else + table.remove( while_stack ) + end + end + , + ["repeat"] = function() + return while_stack[ table.getn( while_stack ) ].loc + end + , + ["continue"] = function() -- different from repeat since break ignores it + return while_stack[ table.getn( while_stack ) ].loc + end + , + ["if"] = function() + if pop() == '0' then table.insert( if_stack, false ) + else table.insert( if_stack, true ) + end + if table.getn( while_stack ) > 0 then + while_stack[ table.getn( while_stack ) ].ifs = + while_stack[ table.getn( while_stack ) ].ifs + 1 + end + + executing = multiand( if_stack ) + end + , + ["else"] = function() + if_stack[ table.getn( if_stack ) ] = + not if_stack[ table.getn( if_stack ) ] + executing = multiand( if_stack ) + end + , + ["then"] = function() + table.remove( if_stack ) + if table.getn( while_stack ) > 0 then + while_stack[ table.getn( while_stack ) ].ifs = + while_stack[ table.getn( while_stack ) ].ifs - 1 + end + executing = multiand( if_stack ) + end + , + ["break"] = function() + local ifs = while_stack[ table.getn( while_stack ) ].ifs + while ifs > 0 do + table.remove( if_stack ) + ifs = ifs - 1 + end + table.remove( while_stack ) + look_for_end_while = true + end + , + ["address?"] = function() + local str = pop() + if userfuncs[ str ] or keywords[ str ] then push( 1 ) + else push( 0 ) + end + end + , + ["int?"] = function() + local num = tonumber( pop() ) + if num and num == math.floor( num ) then push( 1 ) + else push( 0 ) + end + end + , + ["number?"] = function() + if tonumber( pop() ) then push( 1 ) + else push( 0 ) + end + end + , + ["put"] = function() + stack[ table.getn( stack ) - pop() - 1 ] = pop() + end + , + ["random"] = function() + push( math.random( pop() ) ) + end + , + ["date"] = function() + local date = os.date( "*t" ) + push( date.year ) + push( date.month ) + push( date.day ) + end + , + ["execute"] = function() + return pop() + end + , + ["instr"] = function() + local find = pop() + push( string.find( pop(), find, 1, true ) or 0 ) + end + , + ["rinstr"] = function() + local last_found, found + local find, str = pop(), pop() + found = string.find( str, find, 1, true ) + if not found then push( 0 ); return end + while found do + last_found = found + 1 + found = string.find( str, find, last_found, true ) + end + push( last_found - 1 ) + end + , + ["strcut"] = function() + local num, str = pop(), pop() + push( string.sub( str, num + 1 ) ) + push( string.sub( str, 1, num ) ) + end + , + ["stringpfx"] = function() + local pfx, str = pop(), pop() + if string.sub( str, 1, string.len( pfx ) ) == pfx then + push( 1 ) + else + push( 0 ) + end + end + , + ["strcmp"] = function() + local str1, str2 = pop(), pop() + local i = 1 + while string.byte( str1, i ) == + string.byte( str2, i ) do + if not string.byte( str1, i ) then + push( 0 ) + return + end + i = i + 1 + end + local char1 = string.byte( str1, i ) + local char2 = string.byte( str2, i ) + if char1 and char2 then + push( char1 - char2 ) + else + push( char1 or -char2 ) + end + end + , + ["strncmp"] = function() + local count, str1, str2 = pop(), pop(), pop() + local i = 1 + for j = 1, count - 1 do + if string.byte( str1, i ) ~= + string.byte( str2, i ) then + break + end + if not string.byte( str1, i ) then + push( 0 ) + return + end + i = i + 1 + end + local char1 = string.byte( str1, i ) + local char2 = string.byte( str2, i ) + if char1 and char2 then + push( char1 - char2 ) + else + push( char1 or -char2 ) + end + end + , + ["subst"] = function() + local old, new, str = pop(), pop(), pop() + push( string.gsub( str, old, new ) ) + end + , + ["strlen"] = function() + push( string.len( pop() ) ) + end + , + ["tolower"] = function() + push( string.lower( pop() ) ) + end + , + ["toupper"] = function() + push( string.upper( pop() ) ) + end + , + ["striplead"] = function() + push( string.gsub( pop(), "^%s*", "" ) ) + end + , + ["striptail"] = function() + push( string.gsub( pop(), "%s*$", "" ) ) + end + , + ["systime"] = function() + push( os.time() ) + end + , + ["time"] = function() + local date = os.date( "*t" ) + push( date.sec ) + push( date.min ) + push( date.hour ) + end + , + ["timefmt"] = function() + local time = pop() + push( os.date( pop(), time ) ) + end + , + ["timesplit"] = function() + local date = os.date( "*t", pop() ) + push( date.sec ) + push( date.min ) + push( date.hour ) + push( date.day ) + push( date.month ) + push( date.year ) + push( date.wday ) + push( date.yday ) + end + , + ["debug"] = function() + debug = not debug + end + , + ["xdebug"] = function() + xdebug = not xdebug + end + , + [";"] = function() + return 0 + end + , + ["exit"] = function() + return 0 + end + , + [";;"] = function() + done = true + end +} + +-- user defined functions, with some initial definitions +userfuncs = { + ["wait"] = setmetatable( { data = { keywords["read"], + keywords["pop"], + keywords[";"] + }, + name = "wait" + }, { __call = op_call } ) + , + ["spacecat"] = setmetatable( { data = { keywords["spac"], + keywords["swap"], + keywords["strcat"], + keywords["strcat"], + keywords[";"] + }, + name = "spacecat" + }, { __call = op_call } ) + , + ["incr"] = setmetatable( { data = { setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["+"], + keywords[";"] + }, + name = "incr" + }, { __call = op_call } ) + , + ["decr"] = setmetatable( { data = { setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["-"], + keywords[";"] + }, + name = "decr" + }, { __call = op_call } ) + , + ["root"] = setmetatable( { data = { setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["swap"], + keywords["/"], + keywords["^"], + keywords[";"] + }, + name = "root" + }, { __call = op_call } ) + , + ["sqrt"] = setmetatable( { data = { setmetatable( { data = .5 }, + { __call = op_call } + ), + keywords["^"], + keywords[";"] + }, + name = "sqrt" + }, { __call = op_call } ) + , + ["csc"] = setmetatable( { data = { keywords["sin"], + setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["swap"], + keywords["/"], + keywords[";"] + }, + name = "csc" + }, { __call = op_call } ) + , + ["sec"] = setmetatable( { data = { keywords["cos"], + setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["swap"], + keywords["/"], + keywords[";"] + }, + name = "sec" + }, { __call = op_call } ) + , + ["cot"] = setmetatable( { data = { keywords["tan"], + setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["swap"], + keywords["/"], + keywords[";"] + }, + name = "cot" + }, { __call = op_call } ) + , + ["acsc"] = setmetatable( { data = { setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["swap"], + keywords["/"], + keywords["asin"], + keywords[";"] + }, + name = "acsc" + }, { __call = op_call } ) + , + ["asec"] = setmetatable( { data = { setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["swap"], + keywords["/"], + keywords["acos"], + keywords[";"] + }, + name = "asec" + }, { __call = op_call } ) + , + ["acot"] = setmetatable( { data = { setmetatable( { data = 1 }, + { __call = op_call } + ), + keywords["swap"], + keywords["/"], + keywords["atan"], + keywords[";"] + }, + name = "acot" + }, { __call = op_call } ) + , + ["instring"] = setmetatable( { data = { keywords["tolower"], + keywords["swap"], + keywords["tolower"], + keywords["swap"], + keywords["instr"], + keywords[";"] + }, + name = "instring" + }, { __call = op_call } ) + , + ["rinstring"] = setmetatable( { data = { keywords["tolower"], + keywords["swap"], + keywords["tolower"], + keywords["swap"], + keywords["rinstr"], + keywords[";"] + }, + name = "rinstring" + }, { __call = op_call } ) + , + ["stringcmp"] = setmetatable( { data = { keywords["tolower"], + keywords["swap"], + keywords["tolower"], + keywords["swap"], + keywords["strcmp"], + keywords[";"] + }, + name = "stringcmp" + }, { __call = op_call } ) + , + ["strip"] = setmetatable( { data = { keywords["striplead"], + keywords["striptail"], + keywords[";"] + }, + name = "strip" + }, { __call = op_call } ) + , + ["rot"] = setmetatable( { data = { setmetatable( { data = 3 }, + { __call = op_call } + ), + keywords["rotate"], + keywords[";"] + }, + name = "rot" + }, { __call = op_call } ) +} + +local from_file, continuing = false, false +local script_file = io.stdin +if arg[ 1 ] then + -- use explicit file descriptors so that read will work properly in files + script_file = assert( io.open( arg[ 1 ], "r" ) ) + from_file = true +end + +local whiles, ifs = 0, 0 + +-- main program loop +done = false +while not done do + if not from_file then + if not continuing then io.write( "> " ) + else io.write( ">> " ) + end + end + + local exec_str + if from_file then + exec_str = script_file:read( "*a" ) + else + if continuing then + exec_str = script_file:read() + else + exec_str = ": main " .. script_file:read() + end + end + + -- split the input string (add stuff from args here for quote parsing) + local comment = false + for word in string.gfind( exec_str, "%S+" ) do + if word == "begin" then whiles = whiles + 1 end + if word == "repeat" then whiles = whiles - 1 end + if word == "until" then whiles = whiles - 1 end + if word == "if" then ifs = ifs + 1 end + if word == "then" then ifs = ifs - 1 end + + if word == "(" then comment = true end + if not comment then table.insert( commands, word ) end + if word == ")" then comment = false end + --io.stdout:write( word .. ": " .. whiles .. " " .. ifs .. " \n" ) + end + + if ifs ~= 0 or whiles ~= 0 then + continuing = true + else + continuing = false + -- no need to test for from_file here since extra ;s are ignored + table.insert( commands, ";" ) + end + + if not continuing then -- don't execute if command list is incomplete + local current_op_table = setmetatable( {}, { __call = op_call } ) + + -- turn the tokens into opcodes + local is_funcname = false + for i, v in ipairs( commands ) do + if is_funcname then + current_op_table.name = v + current_op_table.loc = i - 1 + userfuncs[ v ] = true -- allow recursive definitions + is_funcname = false + elseif v == ":" then + if current_op_table.name then + -- recursion. this stores a table reference, so multiple + -- levels of recursion work + for i, v in ipairs( current_op_table ) do + if v == current_op_table.name then + current_op_table[ i ] = + setmetatable( { data = current_op_table }, + { __call = op_call } ) + end + end + + userfuncs[ current_op_table.name ] = + setmetatable( { data = current_op_table }, + { __call = op_call } ) + end + current_op_table = {} + is_funcname = true + -- this allows stray - signs within decimals (like 3.14-15) fix? + elseif string.find( v, "^%-?%d*%.?%d+[eE]?%-?%d*$" ) then + table.insert( current_op_table, + setmetatable( { data = tonumber( v ) }, + { __call = op_call } + ) + ) + elseif string.find( v, "^\".+" ) then + table.insert( current_op_table, + setmetatable( { data = v }, { __call = op_call } ) + ) + elseif keywords[ v ] then + table.insert( current_op_table, keywords[ v ] ) + elseif type( userfuncs[ v ] ) == "boolean" then + table.insert( current_op_table, v ) -- string for recursion + elseif type( userfuncs[ v ] ) == "table" then + table.insert( current_op_table, userfuncs[ v ] ) + else + if debug then io.write( "unknown word: ", v, "\n" ) end + break + end + end + + -- grab the last function definition + userfuncs[ current_op_table.name ] = current_op_table + + -- execute the main function + if not done then exec_ops( userfuncs.main ) end + + if debug then + io.write( "{ " ) + for _, v in ipairs( stack ) do + io.write( "\"", v, "\" " ) + end + io.write( "}\n" ) + end + + commands = {} + end + + -- we read the whole file at once, so looping won't get us anymore text + if from_file then break end +end diff --git a/test/test.snt b/test/test.snt new file mode 100644 index 0000000..32f1eab --- /dev/null +++ b/test/test.snt @@ -0,0 +1,15 @@ +: fact_rec + dup 1 != if + dup rot * swap 1 - fact_rec + then +; + +: fact ( recursive factorial function ) + 1 swap fact_rec pop +; + +: main + 1 begin + dup ": strcat 15 fact spacecat notify + 1 + dup 1000 > until +; diff --git a/test/test2.snt b/test/test2.snt new file mode 100644 index 0000000..e02d7b5 --- /dev/null +++ b/test/test2.snt @@ -0,0 +1,8 @@ +: test + begin + 1 + dup notify + dup 10 > if ; then + repeat ; + +: main + 0 test ; -- cgit v1.2.3