=encoding utf8
=head1 NAME
lang/forth - Interpret ANS Forth-style source.
=head1 SYNOPSIS
from lang/forth import forth, forth_system;
say( forth(": square dup * ; 9 square .") ); # 81
let system := forth_system();
system.evaluate("variable x 41 x ! x @ 1+ .");
say( system.output() ); # 42
=head1 DESCRIPTION
This module implements a small pure-Zuzu ANS Forth-oriented interpreter.
It provides the ordinary data stack, return stack, dictionary, colon
definitions, variables, constants, values, memory cells, output capture,
and a practical Core word set.
The implementation is intended for embedding Forth snippets and scripts
inside ZuzuScript applications. It is not a native-code Forth system and
does not expose implementation-defined address arithmetic beyond the
managed cell store used by C<@>, C<!>, C<,>, and related words.
=head1 EXPORTS
=over
=item C<forth(String source, Dict options?)>
Evaluates C<source> in a new C<ForthSystem> and returns captured output.
=item C<forth_system(Dict options?)>
Returns a fresh C<ForthSystem> with the standard dictionary installed.
=item C<parse_forth(String source, String source_name?)>
Tokenizes source text and returns C<ForthToken> values.
=back
=head1 COPYRIGHT AND LICENCE
B<< lang/forth >> is copyright Toby Inkster.
It is free software; you may redistribute it and/or modify it under the
terms of either the Artistic License 1.0 or the GNU General Public License
version 2.
=cut
from std/string import chr, index, join, ord, substr;
function _execute_tokens;
function _run_body;
function _install_core;
function parse_forth;
function _array_contains;
class ForthError extends Exception {
let String source_name with get := "<string>";
let Number line with get := 0;
let Number column with get := 0;
method to_String () {
if ( self{line} > 0 ) {
return self{message} _ " at " _ self{source_name} _ ":" _
self{line} _ ":" _ self{column};
}
return self{message};
}
}
class ForthSyntaxError extends ForthError;
class ForthRuntimeError extends ForthError;
class ForthToken {
let String text with get := "";
let String string_value with get := "";
let String source_name with get := "<string>";
let Number line with get := 1;
let Number column with get := 1;
method word () {
return uc(text);
}
}
class ForthWord {
let String name with get := "";
let String kind with get := "primitive";
let callback with get := null;
let Array body with get := [];
let value with get := null;
let Boolean compile_only with get := false;
method __build__ () {
body := [] if body == null;
}
}
class ForthSystem {
let Array stack with get := [];
let Array return_stack with get := [];
let Array loop_stack with get := [];
let Dict dictionary with get := {};
let Array word_order with get := [];
let Array memory with get := [];
let Number here with get := 0;
let Number base with get := 10;
let Array output_buffer with get := [];
let output_callback with get := null;
let Boolean compiling with get := false;
let String current_name with get := "";
let Array current_body with get := [];
let String source_name with get := "<string>";
method __build__ () {
stack := [] if stack == null;
return_stack := [] if return_stack == null;
loop_stack := [] if loop_stack == null;
dictionary := {} if dictionary == null;
word_order := [] if word_order == null;
memory := [] if memory == null;
output_buffer := [] if output_buffer == null;
current_body := [] if current_body == null;
_install_core(self);
}
method set_base ( Number new_base ) {
if ( new_base < 2 or new_base > 36 ) {
self.runtime_error("BASE must be between 2 and 36");
}
base := new_base;
return base;
}
method define_primitive ( String name, Function callback ) {
return self.define_word(
new ForthWord(
name: uc(name),
kind: "primitive",
callback: callback,
),
);
}
method define_user ( String name, Array body ) {
return self.define_word(
new ForthWord( name: uc(name), kind: "user", body: body ),
);
}
method define_constant ( String name, value ) {
return self.define_word(
new ForthWord( name: uc(name), kind: "constant", value: value ),
);
}
method define_variable ( String name, value := 0 ) {
let address := here;
memory.push(value);
here++;
self.define_word(
new ForthWord( name: uc(name), kind: "variable", value: address ),
);
return address;
}
method define_value ( String name, value ) {
return self.define_word(
new ForthWord( name: uc(name), kind: "value", value: value ),
);
}
method define_word ( ForthWord word ) {
let name := uc(word.get_name());
dictionary.set( name, word );
word_order.push(name);
return word;
}
method lookup ( String name ) {
let key := uc(name);
return dictionary.get(key) if dictionary.exists(key);
return null;
}
method require_word ( String name ) {
let word := self.lookup(name);
return word if word != null;
self.runtime_error("Undefined word: " _ name);
}
method push ( value ) {
stack.push(value);
return value;
}
method pop () {
self.runtime_error("Stack underflow") if stack.length() == 0;
return stack.pop();
}
method peek ( Number depth := 0 ) {
let pos := stack.length() - 1 - depth;
self.runtime_error("Stack underflow") if pos < 0;
return stack[pos];
}
method rpush ( value ) {
return_stack.push(value);
return value;
}
method rpop () {
self.runtime_error("Return stack underflow") if return_stack.length() == 0;
return return_stack.pop();
}
method rpeek () {
self.runtime_error("Return stack underflow") if return_stack.length() == 0;
return return_stack[ return_stack.length() - 1 ];
}
method output_text ( String text ) {
if ( output_callback != null ) {
output_callback(text);
}
else {
output_buffer.push(text);
}
return text;
}
method output () {
return join( "", output_buffer );
}
method clear_output () {
output_buffer := [];
return self;
}
method evaluate ( String source, String name := "<string>" ) {
source_name := name;
_execute_tokens( self, parse_forth( source, name ) );
if ( compiling ) {
self.syntax_error("Unterminated colon definition");
}
return self;
}
method execute_word ( ForthWord word ) {
if ( word.get_kind() eq "primitive" ) {
return word.get_callback()(self);
}
if ( word.get_kind() eq "user" ) {
return _run_body( self, word.get_body() );
}
if ( word.get_kind() eq "constant" or word.get_kind() eq "value" ) {
return self.push(word.get_value());
}
if ( word.get_kind() eq "variable" ) {
return self.push(word.get_value());
}
self.runtime_error("Unsupported word kind: " _ word.get_kind());
}
method syntax_error ( String message, token? ) {
if ( token instanceof ForthToken ) {
throw new ForthSyntaxError(
message: message,
source_name: token.get_source_name(),
line: token.get_line(),
column: token.get_column(),
);
}
throw new ForthSyntaxError(
message: message,
source_name: source_name,
);
}
method runtime_error ( String message, token? ) {
if ( token instanceof ForthToken ) {
throw new ForthRuntimeError(
message: message,
source_name: token.get_source_name(),
line: token.get_line(),
column: token.get_column(),
);
}
throw new ForthRuntimeError(
message: message,
source_name: source_name,
);
}
}
function _option ( options, String key, fallback ) {
if ( typeof options == "Dict" and options.exists(key) ) {
return options.get(key);
}
return fallback;
}
function _is_space ( String ch ) {
return ch eq " " or ch eq "\n" or ch eq "\t" or ch eq "\r";
}
function _advance_position ( String ch, Dict pos ) {
if ( ch eq "\n" ) {
pos{line}++;
pos{column} := 1;
}
else {
pos{column}++;
}
pos{index}++;
}
function _skip_to_eol ( String source, Dict pos ) {
while ( pos{index} < length source ) {
let ch := substr( source, pos{index}, 1 );
_advance_position( ch, pos );
last if ch eq "\n";
}
}
function _skip_paren_comment ( String source, Dict pos ) {
while ( pos{index} < length source ) {
let ch := substr( source, pos{index}, 1 );
_advance_position( ch, pos );
return true if ch eq ")";
}
return false;
}
function _read_quoted ( String source, Dict pos ) {
let value := "";
while ( pos{index} < length source ) {
let ch := substr( source, pos{index}, 1 );
_advance_position( ch, pos );
return value if ch eq "\"";
if ( ch eq "\\" and pos{index} < length source ) {
let nxt := substr( source, pos{index}, 1 );
_advance_position( nxt, pos );
if ( nxt eq "n" ) {
value _= "\n";
}
else if ( nxt eq "t" ) {
value _= "\t";
}
else if ( nxt eq "r" ) {
value _= "\r";
}
else {
value _= nxt;
}
}
else {
value _= ch;
}
}
return null;
}
function parse_forth (
String source,
String source_name := "<string>",
) {
let tokens := [];
let pos := { index: 0, line: 1, column: 1 };
while ( pos{index} < length source ) {
let ch := substr( source, pos{index}, 1 );
if ( _is_space(ch) ) {
_advance_position( ch, pos );
}
else if ( ch eq "\\" ) {
_skip_to_eol( source, pos );
}
else if ( ch eq "(" ) {
let line := pos{line};
let column := pos{column};
_advance_position( ch, pos );
if ( not _skip_paren_comment( source, pos ) ) {
throw new ForthSyntaxError(
message: "Unterminated parenthesized comment",
source_name: source_name,
line: line,
column: column,
);
}
}
else {
let line := pos{line};
let column := pos{column};
let text := "";
while ( pos{index} < length source ) {
ch := substr( source, pos{index}, 1 );
last if _is_space(ch);
text _= ch;
_advance_position( ch, pos );
}
let word := uc(text);
let string_value := "";
if ( word eq ".\"" or word eq "S\"" or word eq "C\"" or
word eq "ABORT\"" ) {
if (
pos{index} < length source and
substr( source, pos{index}, 1 ) eq " "
) {
_advance_position( " ", pos );
}
let read := _read_quoted( source, pos );
if ( read == null ) {
throw new ForthSyntaxError(
message: "Unterminated string literal",
source_name: source_name,
line: line,
column: column,
);
}
string_value := read;
}
tokens.push(
new ForthToken(
text: text,
string_value: string_value,
source_name: source_name,
line: line,
column: column,
),
);
}
}
return tokens;
}
function _truth ( value ) {
if ( typeof value == "Boolean" ) {
return value ? -1 : 0;
}
return value == 0 ? 0 : -1;
}
function _num ( value ) {
return value + 0 if typeof value == "Number";
return ( "" _ value ) + 0 if ( "" _ value ) ~ /^[+-]?\d+$/;
return value + 0;
}
function _digit_value ( String ch ) {
return index( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", uc(ch) );
}
function _parse_number ( String text, Number base ) {
let sign := 1;
let i := 0;
if ( text eq "" ) {
return null;
}
if ( substr( text, 0, 1 ) eq "-" ) {
sign := -1;
i := 1;
}
else if ( substr( text, 0, 1 ) eq "+" ) {
i := 1;
}
return null if i >= length text;
let n := 0;
while ( i < length text ) {
let d := _digit_value( substr( text, i, 1 ) );
return null if d < 0 or d >= base;
n := n * base + d;
i++;
}
return sign * n;
}
function _store_at ( ForthSystem sys, Number address, value ) {
sys.runtime_error("Invalid memory address") if address < 0;
if (
sys.get_dictionary().exists("__BASE_ADDR") and
address == sys.get_dictionary().get("__BASE_ADDR").get_value()
) {
sys.set_base(value);
}
while ( address >= sys.get_memory().length() ) {
sys.get_memory().push(0);
}
sys.get_memory()[address] := value;
return value;
}
function _fetch_at ( ForthSystem sys, Number address ) {
sys.runtime_error("Invalid memory address") if address < 0;
while ( address >= sys.get_memory().length() ) {
sys.get_memory().push(0);
}
return sys.get_memory()[address];
}
function _next_name ( ForthSystem sys, Array tokens, Number ip, String word ) {
sys.syntax_error( word _ " requires a following name", tokens[ip] )
if ip + 1 >= tokens.length();
return tokens[ip + 1].get_text();
}
function _execute_normal_token (
ForthSystem sys,
ForthToken token,
Array tokens,
Number ip,
) {
let name := token.word();
if ( name eq ":" ) {
let new_name := _next_name( sys, tokens, ip, ":" );
sys{compiling} := true;
sys{current_name} := new_name;
sys{current_body} := [];
return ip + 2;
}
if ( name eq "VARIABLE" ) {
sys.define_variable( _next_name( sys, tokens, ip, "VARIABLE" ) );
return ip + 2;
}
if ( name eq "CONSTANT" ) {
sys.define_constant( _next_name( sys, tokens, ip, "CONSTANT" ), sys.pop() );
return ip + 2;
}
if ( name eq "VALUE" ) {
sys.define_value( _next_name( sys, tokens, ip, "VALUE" ), sys.pop() );
return ip + 2;
}
if ( name eq "TO" ) {
let word := sys.require_word( _next_name( sys, tokens, ip, "TO" ) );
if ( word.get_kind() ne "value" ) {
sys.runtime_error("TO requires a VALUE", token);
}
word{value} := sys.pop();
return ip + 2;
}
if ( name eq "CHAR" or name eq "[CHAR]" ) {
let raw := _next_name( sys, tokens, ip, name );
sys.push( ord( raw, 0 ) );
return ip + 2;
}
if ( name eq ".\"" ) {
sys.output_text(token.get_string_value());
return ip + 1;
}
if ( name eq "S\"" or name eq "C\"" ) {
sys.push(token.get_string_value());
return ip + 1;
}
if ( name eq "ABORT\"" ) {
if ( sys.pop() != 0 ) {
sys.runtime_error(token.get_string_value(), token);
}
return ip + 1;
}
let word := sys.lookup(name);
if ( word != null ) {
sys.execute_word(word);
return ip + 1;
}
let number := _parse_number( token.get_text(), sys.get_base() );
if ( number != null ) {
sys.push(number);
return ip + 1;
}
sys.runtime_error("Undefined word: " _ token.get_text(), token);
}
function _execute_tokens ( ForthSystem sys, Array tokens ) {
let ip := 0;
while ( ip < tokens.length() ) {
let token := tokens[ip];
if ( sys.get_compiling() ) {
if ( token.word() eq ";" ) {
sys.define_user( sys.get_current_name(), sys.get_current_body() );
sys{compiling} := false;
sys{current_name} := "";
sys{current_body} := [];
}
else {
sys.get_current_body().push(token);
}
ip++;
}
else {
ip := _execute_normal_token( sys, token, tokens, ip );
}
}
}
function _find_forward (
Array body,
Number ip,
Array targets,
Array opens,
) {
let depth := 0;
let i := ip + 1;
while ( i < body.length() ) {
let word := body[i].word();
if ( _array_contains( opens, word ) ) {
depth++;
}
else if ( _array_contains( targets, word ) ) {
return i if depth == 0;
depth--;
}
i++;
}
return -1;
}
function _find_backward (
Array body,
Number ip,
String target,
Array closers,
) {
let depth := 0;
let i := ip - 1;
while ( i >= 0 ) {
let word := body[i].word();
if ( _array_contains( closers, word ) ) {
depth++;
}
else if ( word eq target ) {
return i if depth == 0;
depth--;
}
i--;
}
return -1;
}
function _array_contains ( Array values, value ) {
for ( let item in values ) {
return true if item == value;
}
return false;
}
function _loop_done ( Number old_index, Number new_index, Number limit,
Number step ) {
if ( step >= 0 ) {
return new_index >= limit;
}
return new_index < limit;
}
function _execute_body_token (
ForthSystem sys,
Array body,
Number ip,
) {
let token := body[ip];
let name := token.word();
if ( name eq "IF" ) {
if ( sys.pop() == 0 ) {
let target := _find_forward( body, ip, [ "ELSE", "THEN" ], [ "IF" ] );
sys.syntax_error("IF without THEN", token) if target < 0;
return body[target].word() eq "ELSE" ? target + 1 : target + 1;
}
return ip + 1;
}
if ( name eq "ELSE" ) {
let target := _find_forward( body, ip, [ "THEN" ], [ "IF" ] );
sys.syntax_error("ELSE without THEN", token) if target < 0;
return target + 1;
}
if ( name eq "THEN" ) {
return ip + 1;
}
if ( name eq "BEGIN" ) {
return ip + 1;
}
if ( name eq "UNTIL" ) {
if ( sys.pop() == 0 ) {
let target := _find_backward(
body,
ip,
"BEGIN",
[ "UNTIL", "AGAIN", "REPEAT" ],
);
sys.syntax_error("UNTIL without BEGIN", token) if target < 0;
return target + 1;
}
return ip + 1;
}
if ( name eq "AGAIN" ) {
let target := _find_backward(
body,
ip,
"BEGIN",
[ "UNTIL", "AGAIN", "REPEAT" ],
);
sys.syntax_error("AGAIN without BEGIN", token) if target < 0;
return target + 1;
}
if ( name eq "WHILE" ) {
if ( sys.pop() == 0 ) {
let target := _find_forward(
body,
ip,
[ "REPEAT" ],
[ "BEGIN", "WHILE" ],
);
sys.syntax_error("WHILE without REPEAT", token) if target < 0;
return target + 1;
}
return ip + 1;
}
if ( name eq "REPEAT" ) {
let target := _find_backward(
body,
ip,
"BEGIN",
[ "UNTIL", "AGAIN", "REPEAT" ],
);
sys.syntax_error("REPEAT without BEGIN", token) if target < 0;
return target + 1;
}
if ( name eq "DO" ) {
let limit := sys.pop();
let start := sys.pop();
sys.get_loop_stack().push( { index: start, limit: limit, start_ip: ip } );
return ip + 1;
}
if ( name eq "LOOP" or name eq "+LOOP" ) {
sys.runtime_error("LOOP without DO", token)
if sys.get_loop_stack().length() == 0;
let step := name eq "LOOP" ? 1 : sys.pop();
let frame := sys.get_loop_stack()[ sys.get_loop_stack().length() - 1 ];
let old_index := frame{index};
frame{index} += step;
if ( _loop_done( old_index, frame{index}, frame{limit}, step ) ) {
sys.get_loop_stack().pop();
return ip + 1;
}
return frame{start_ip} + 1;
}
if ( name eq "I" or name eq "J" ) {
let depth := name eq "I" ? 0 : 1;
let pos := sys.get_loop_stack().length() - 1 - depth;
sys.runtime_error(name _ " outside DO loop", token) if pos < 0;
sys.push( sys.get_loop_stack()[pos]{index} );
return ip + 1;
}
if ( name eq "LEAVE" ) {
sys.runtime_error("LEAVE outside DO loop", token)
if sys.get_loop_stack().length() == 0;
sys.get_loop_stack().pop();
let target := _find_forward( body, ip, [ "LOOP", "+LOOP" ], [ "DO" ] );
sys.syntax_error("LEAVE without LOOP", token) if target < 0;
return target + 1;
}
return _execute_normal_token( sys, token, body, ip );
}
function _run_body ( ForthSystem sys, Array body ) {
let saved_compiling := sys.get_compiling();
sys{compiling} := false;
let ip := 0;
while ( ip < body.length() ) {
ip := _execute_body_token( sys, body, ip );
}
sys{compiling} := saved_compiling;
return sys;
}
function _binop ( ForthSystem sys, Function f ) {
let b := sys.pop();
let a := sys.pop();
return sys.push( f( a, b ) );
}
function _cmpop ( ForthSystem sys, Function f ) {
let b := sys.pop();
let a := sys.pop();
return sys.push( f( a, b ) ? -1 : 0 );
}
function _spaces ( Number count ) {
let out := "";
let i := 0;
while ( i < count ) {
out _= " ";
i++;
}
return out;
}
function _install_core ( ForthSystem sys ) {
let base_address := sys.define_variable( "__BASE_ADDR", sys.get_base() );
sys.define_word(
new ForthWord( name: "BASE", kind: "variable", value: base_address ),
);
sys.define_primitive( "DROP", function ( s ) { s.pop(); return s; } );
sys.define_primitive( "DUP", function ( s ) { return s.push(s.peek()); } );
sys.define_primitive( "?DUP", function ( s ) {
let x := s.peek();
s.push(x) if x != 0;
return s;
} );
sys.define_primitive( "SWAP", function ( s ) {
let b := s.pop();
let a := s.pop();
s.push(b);
s.push(a);
return s;
} );
sys.define_primitive( "OVER", function ( s ) { return s.push(s.peek(1)); } );
sys.define_primitive( "ROT", function ( s ) {
let c := s.pop();
let b := s.pop();
let a := s.pop();
s.push(b);
s.push(c);
s.push(a);
return s;
} );
sys.define_primitive( "-ROT", function ( s ) {
let c := s.pop();
let b := s.pop();
let a := s.pop();
s.push(c);
s.push(a);
s.push(b);
return s;
} );
sys.define_primitive( "NIP", function ( s ) {
let b := s.pop();
s.pop();
return s.push(b);
} );
sys.define_primitive( "TUCK", function ( s ) {
let b := s.pop();
let a := s.pop();
s.push(b);
s.push(a);
s.push(b);
return s;
} );
sys.define_primitive( "DEPTH", function ( s ) {
return s.push(s.get_stack().length());
} );
sys.define_primitive( "2DROP", function ( s ) {
s.pop();
s.pop();
return s;
} );
sys.define_primitive( "2DUP", function ( s ) {
let b := s.peek();
let a := s.peek(1);
s.push(a);
s.push(b);
return s;
} );
sys.define_primitive( "2OVER", function ( s ) {
let b := s.peek(2);
let a := s.peek(3);
s.push(a);
s.push(b);
return s;
} );
sys.define_primitive( "2SWAP", function ( s ) {
let d := s.pop();
let c := s.pop();
let b := s.pop();
let a := s.pop();
s.push(c);
s.push(d);
s.push(a);
s.push(b);
return s;
} );
sys.define_primitive( ">R", function ( s ) { return s.rpush(s.pop()); } );
sys.define_primitive( "R>", function ( s ) { return s.push(s.rpop()); } );
sys.define_primitive( "R@", function ( s ) { return s.push(s.rpeek()); } );
sys.define_primitive( "+", function ( s ) {
return _binop( s, function ( a, b ) { return a + b; } );
} );
sys.define_primitive( "-", function ( s ) {
return _binop( s, function ( a, b ) { return a - b; } );
} );
sys.define_primitive( "*", function ( s ) {
return _binop( s, function ( a, b ) { return a * b; } );
} );
sys.define_primitive( "/", function ( s ) {
return _binop( s, function ( a, b ) {
s.runtime_error("Division by zero") if b == 0;
return int(a / b);
} );
} );
sys.define_primitive( "MOD", function ( s ) {
return _binop( s, function ( a, b ) {
s.runtime_error("Division by zero") if b == 0;
return a mod b;
} );
} );
sys.define_primitive( "/MOD", function ( s ) {
let b := s.pop();
let a := s.pop();
s.runtime_error("Division by zero") if b == 0;
s.push(a mod b);
s.push(int(a / b));
return s;
} );
sys.define_primitive( "1+", function ( s ) { return s.push(s.pop() + 1); } );
sys.define_primitive( "1-", function ( s ) { return s.push(s.pop() - 1); } );
sys.define_primitive( "2*", function ( s ) { return s.push(s.pop() * 2); } );
sys.define_primitive( "2/", function ( s ) { return s.push(int(s.pop() / 2)); } );
sys.define_primitive( "NEGATE", function ( s ) { return s.push(0 - s.pop()); } );
sys.define_primitive( "ABS", function ( s ) {
let x := s.pop();
return s.push( x < 0 ? 0 - x : x );
} );
sys.define_primitive( "MIN", function ( s ) {
return _binop( s, function ( a, b ) { return a < b ? a : b; } );
} );
sys.define_primitive( "MAX", function ( s ) {
return _binop( s, function ( a, b ) { return a > b ? a : b; } );
} );
sys.define_primitive( "=", function ( s ) {
return _cmpop( s, function ( a, b ) { return a == b; } );
} );
sys.define_primitive( "<>", function ( s ) {
return _cmpop( s, function ( a, b ) { return a != b; } );
} );
sys.define_primitive( "<", function ( s ) {
return _cmpop( s, function ( a, b ) { return a < b; } );
} );
sys.define_primitive( ">", function ( s ) {
return _cmpop( s, function ( a, b ) { return a > b; } );
} );
sys.define_primitive( "<=", function ( s ) {
return _cmpop( s, function ( a, b ) { return a <= b; } );
} );
sys.define_primitive( ">=", function ( s ) {
return _cmpop( s, function ( a, b ) { return a >= b; } );
} );
sys.define_primitive( "0=", function ( s ) { return s.push(_truth(s.pop() == 0)); } );
sys.define_primitive( "0<", function ( s ) { return s.push(_truth(s.pop() < 0)); } );
sys.define_primitive( "0>", function ( s ) { return s.push(_truth(s.pop() > 0)); } );
sys.define_primitive( "WITHIN", function ( s ) {
let high := s.pop();
let low := s.pop();
let x := s.pop();
return s.push( x >= low and x < high ? -1 : 0 );
} );
sys.define_primitive( "AND", function ( s ) {
return _binop( s, function ( a, b ) {
return ( a != 0 and b != 0 ) ? -1 : 0;
} );
} );
sys.define_primitive( "OR", function ( s ) {
return _binop( s, function ( a, b ) {
return ( a != 0 or b != 0 ) ? -1 : 0;
} );
} );
sys.define_primitive( "XOR", function ( s ) {
return _binop( s, function ( a, b ) {
return ( ( a != 0 ) != ( b != 0 ) ) ? -1 : 0;
} );
} );
sys.define_primitive( "INVERT", function ( s ) {
return s.push( s.pop() == 0 ? -1 : 0 );
} );
sys.define_primitive( ".", function ( s ) {
s.output_text( "" _ s.pop() _ " " );
return s;
} );
sys.define_primitive( ".S", function ( s ) {
s.output_text( "<" _ s.get_stack().length() _ "> " );
for ( let item in s.get_stack() ) {
s.output_text( "" _ item _ " " );
}
return s;
} );
sys.define_primitive( "EMIT", function ( s ) {
s.output_text(chr(s.pop()));
return s;
} );
sys.define_primitive( "CR", function ( s ) {
s.output_text("\n");
return s;
} );
sys.define_primitive( "SPACE", function ( s ) {
s.output_text(" ");
return s;
} );
sys.define_primitive( "SPACES", function ( s ) {
s.output_text(_spaces(s.pop()));
return s;
} );
sys.define_primitive( "TYPE", function ( s ) {
let value := s.pop();
if ( typeof value == "String" ) {
s.output_text(value);
return s;
}
let address := s.pop();
let len := value;
let i := 0;
while ( i < len ) {
s.output_text(chr(_fetch_at( s, address + i )));
i++;
}
return s;
} );
sys.define_primitive( "!", function ( s ) {
let address := s.pop();
let value := s.pop();
_store_at( s, address, value );
return s;
} );
sys.define_primitive( "@", function ( s ) {
return s.push(_fetch_at( s, s.pop() ));
} );
sys.define_primitive( "+!", function ( s ) {
let address := s.pop();
let value := s.pop();
_store_at( s, address, _fetch_at( s, address ) + value );
return s;
} );
sys.define_primitive( "C!", function ( s ) {
let address := s.pop();
let value := s.pop();
_store_at( s, address, value );
return s;
} );
sys.define_primitive( "C@", function ( s ) {
return s.push(_fetch_at( s, s.pop() ));
} );
sys.define_primitive( "HERE", function ( s ) {
return s.push(s.get_here());
} );
sys.define_primitive( "ALLOT", function ( s ) {
let count := s.pop();
let i := 0;
while ( i < count ) {
s.get_memory().push(0);
s{here}++;
i++;
}
return s;
} );
sys.define_primitive( ",", function ( s ) {
_store_at( s, s.get_here(), s.pop() );
s{here}++;
return s;
} );
sys.define_primitive( "CELLS", function ( s ) { return s; } );
sys.define_primitive( "CELL+", function ( s ) {
return s.push(s.pop() + 1);
} );
sys.define_primitive( "CHARS", function ( s ) { return s; } );
sys.define_primitive( "CHAR+", function ( s ) {
return s.push(s.pop() + 1);
} );
sys.define_primitive( "DECIMAL", function ( s ) {
_store_at( s, s.get_dictionary().get("__BASE_ADDR").get_value(), 10 );
return s;
} );
sys.define_primitive( "HEX", function ( s ) {
_store_at( s, s.get_dictionary().get("__BASE_ADDR").get_value(), 16 );
return s;
} );
sys.define_primitive( "WORDS", function ( s ) {
for ( let name in s.get_word_order() ) {
if ( substr( name, 0, 2 ) ne "__" ) {
s.output_text(name _ " ");
}
}
return s;
} );
sys.define_primitive( "BYE", function ( s ) { return s; } );
}
function forth_system ( options? ) {
let system := new ForthSystem(
output_callback: _option( options, "output_callback", null ),
);
return system;
}
function forth ( String source, options? ) {
let system := forth_system(options);
system.evaluate(
source,
"" _ _option( options, "source_name", "<string>" ),
);
return system.output();
}
modules/lang/forth.zzm
lang-forth-0.0.2 source code
Package
- Name
- lang-forth
- Version
- 0.0.2
- Uploaded
- 2026-06-12 23:11:04
- Repository
- https://github.com/tobyink/zuzu-lang-forth
- Dependencies
-
-
std/getopt>= 0 -
std/io>= 0 -
std/proc>= 0 -
std/string>= 0
-
- Metadata
- zuzu-distribution.json
- Archive
- Download .tar.gz