summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjluehrs2 <jluehrs2@uiuc.edu>2007-09-02 15:50:48 -0500
committerjluehrs2 <jluehrs2@uiuc.edu>2007-09-02 15:50:48 -0500
commitca9c150fc0b67a3d6901594f2d2d2f22017b166e (patch)
treec405c8f2747395a082d9af36ce8e1c2df0ff3189
downloadsonnet-ca9c150fc0b67a3d6901594f2d2d2f22017b166e.tar.gz
sonnet-ca9c150fc0b67a3d6901594f2d2d2f22017b166e.zip
initial importHEADmaster
-rw-r--r--src/forth.lua905
-rw-r--r--test/test.snt15
-rw-r--r--test/test2.snt8
3 files changed, 928 insertions, 0 deletions
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 ;