""" forth.cobra This is a budding FORTH interpreter featuring: * basic stack item manipulation (dup, over, swap, drop, ...) * stack manipulation (0SP aka stack.clear) * math (+ - * / %) * definitions (; NAME CODE : -OR- def NAME CODE end) * You can multiply strings as in: foo 3 * -- foofoofoo ...and not much else right now. Run with no arguments to see some example FORTH get executed. Run with -i for "interactive mode" to enter your own FORTH. Enter "quit" or "exit" to do so. Forth tutorial: http://www.softsynth.com/pforth/pf_tut.htm In addition to some extra synonym words which you'll see in the list of words, this particular interperter also accepts 'def' for ':' and 'end' for ';'. So these are equivalent: : AVERAGE ( a b -- avg ) + 2 / ; def AVERAGE ( a b -- avg ) + 2 / end TODO: [ ] Text in parens is a comment. [ ] variables [ ] Could do the built-in definitions by tagging their methods with attributes [ ] Support fractional numbers. In other words, decimals. [ ] Automated tests. [ ] Consider making Definition abstract with concrete subs: BuiltInDefinition, WordsDefinition with possible DelegateDefinition as well [ ] Could preserve the comment in a definition that immediatel follows the definition's name and store it with the definition. Then could offer it back later with a help command. [ ] Shouldn't there be some kind of quoting character so a name can be pushed on the stack without invoking it? Probably have not gotten that far in the tutorial. [ ] More FORTH: - In general, cover everything at http://www.softsynth.com/pforth/pf_tut.htm ROT ( a b c -- b c a , ROTate third item to top ) PICK ( ... v3 v2 v1 v0 N -- ... v3 v2 v1 v0 vN ) 0 PICK is equivalent to DUP 1 PICK is equivalent to OVER DROP ( n -- , remove top of stack ) ?DUP ( n -- n n | 0 , duplicate only if non-zero, '|' means OR ) -ROT ( a b c -- c a b , rotate top to third position ) 2SWAP ( a b c d -- c d a b , swap pairs ) 2OVER ( a b c d -- a b c d a b , leapfrog pair ) 2DUP ( a b -- a b a b , duplicate pair ) 2DROP ( a b -- , remove pair ) NIP ( a b -- b , remove second item from stack ) TUCK ( a b -- b a b , copy top item to third position ) Fast math operations: 1+ 1- 2+ 2- 2* 2/ Define new words: : AVERAGE ( a b -- avg ) + 2/ ; """ class ForthMachine var _stack as Stack var _definitions as Dictionary var _didQuit as bool var _verbosity = 1 cue init base.init _stack = Stack() _definitions = Dictionary() .define('quit', 'built-in:quit') .define('exit', 'built-in:quit') .define('words', 'built-in:printDefinitionNames') .define('.', 'built-in:printTop') .define('.S', 'built-in:printStack') .define('stack.print', 'built-in:printStack') .define('0SP', 'built-in:clearStack') .define('stack.clear', 'built-in:clearStack') .define('drop', 'built-in:drop') .define('dup', 'built-in:dup') .define('over', 'built-in:over') .define('swap', 'built-in:swap') .define('+', 'built-in:add') .define('-', 'built-in:sub') .define('*', 'built-in:mul') .define('/', 'built-in:div') .define('%', 'built-in:mod') get didQuit from var pro verbosity from var ## Fundamental operations to implement a FORTH machine def wordFor(text as String) as Word require text.trim.length return Word(text) def wordFor(i as int) as Word return Word(i) def push(word as Word) require word.isString implies word.string.trim.length _stack.push(word) def push(word as String) .push(Word(word)) def peek as Word if _stack.count return _stack.peek else print 'ERROR: Stack is empty.' return Word(0) def pop as Word if _stack.count return _stack.pop else print 'ERROR: Stack is empty.' return Word(0) def define(word as String, definition as String) words = for rawWord in definition.split where rawWord.trim.length>0 get .wordFor(rawWord.trim) # CC: axe > 0 _definitions[.wordFor(word)] = Definition(words) def define(word as Word, definition as IEnumerable) _definitions[word] = Definition(definition) def definitionFor(word as Word) as Definition? result as Definition? if _definitions.tryGetValue(word, out result) return result to ! else return nil def invoke(name as Word, definition as Definition) if definition.count word = definition[0] if word.toString.startsWith('built-in:') methodName = word.toString['built-in:'.length:] methodName = methodName[0].toString.toUpper + methodName[1:] # because at run-time, method names are capped for compatibility with C# and VB method = .typeOf.getMethod(methodName) method.invoke(this, nil) else for word in definition if word.isString defin = .definitionFor(word) if defin .invoke(word, defin) continue .push(word) else # empty definition is a no-op pass def error(message as String) print 'ERROR:', message ## Conveniences def processInput(input as String) mode = 0 # 0 - normal, 1 - define if _verbosity print 'input>', input rawWords = input.trim.split for rawWord in rawWords rawWord = rawWord.trim if not rawWord.length continue branch mode on 0 # normal if rawWord in [':', 'def'] defWords = List() mode = 1 # into define mode else word = .wordFor(rawWord) if word.isString defin = .definitionFor(word) if defin .invoke(word, defin) continue .push(word) on 1 # definition mode if rawWord in [';', 'end'] .define(defWords[0], defWords[1:]) defWords.clear mode = 0 # back to normal else defWords.add(.wordFor(rawWord)) if _verbosity print 'stack> ' stop .printStack print ## Built-in definitions def quit _didQuit = true def printDefinitionNames keys = List(_definitions.keys) keys.sort for key in keys, print '[key] ' stop print def printTop print .pop def printStack for word in Stack(_stack) # Stack wrapper reverses the order so right most printed element is the top of the stack print '[word] | ' stop print ## Built-ins: whole stack manipulation def clearStack _stack.clear ## Built-ins: stack element manipulations def drop .pop def dup .push(.peek) def over """ a b -- a b a """ b = .pop a = .pop .push(a) .push(b) .push(a) def swap """ a b -- b a """ b = .pop a = .pop .push(b) .push(a) ## Built-ins: arithmetic def add b = .pop a = .pop if a.isInt and b.isInt .push(.wordFor(a.int + b.int)) else if a.isString and b.isString .push(.wordFor(a.string + b.string)) else .error('Cannot add "[a]" and "[b]".') def sub b = .pop a = .pop if a.isInt and b.isInt .push(.wordFor(a.int - b.int)) else .error('Cannot subtract "[a]" and "[b]".') def mul b = .pop a = .pop if a.isInt and b.isInt .push(.wordFor(a.int * b.int)) return else if a.isString and b.isInt s = a.string i = b.int else if a.isInt and b.isString s = b.string i = a.int else .error('Cannot add "[a]" and "[b]".') sb = StringBuilder() for j in i, sb.append(s) .push(.wordFor(sb.toString)) def div b = .pop a = .pop if a.isInt and b.isInt .push(.wordFor(a.int // b.int)) else .error('Cannot divide "[a]" and "[b]".') def mod b = .pop a = .pop if a.isInt and b.isInt .push(.wordFor(a.int % b.int)) else .error('Cannot modulate "[a]" and "[b]".') class Word implements IComparable enum WordType String Int var _which as WordType var _i as int var _s as String? cue init(text as String) """ Creates a new word, interpreting the text as an integer if possible. """ .init(text, true) cue init(text as String, interpret as bool) """ When interpretation is false, text is simply taken as is. Otherwise if the text represents an integer, this word will be one. """ test w = Word('foo') assert w.isString w = Word('10') assert w.isInt w = Word('10', false) assert w.isString body base.init if interpret and int.tryParse(text, out _i) _which = WordType.Int else _s = text _which = WordType.String cue init(i as int) base.init _i = i _which = WordType.Int get isInt as bool return _which == WordType.Int get int as int require .isInt return _i get isString as bool return _which == WordType.String get string as String require .isString return _s to ! def equals(other as Object?) as bool is override if other inherits Word if _which == other._which branch _which on WordType.String, return _s == other._s on WordType.Int, return _i == other._i return false else return false def compareTo(other as Word) as int if .isInt if other.isInt return .int.compareTo(other.int) else return -1 else if .isString if other.isString return .string.compareTo(other.string) else return 1 else throw FallThroughException(other) def getHashCode as int is override branch _which on WordType.String, return _s.getHashCode on WordType.Int, return _i.getHashCode throw FallThroughException(_which) def toString as String is override branch _which on WordType.String if _s == '', return '(EMPTY-STRING)' if _s.trim == '', return '(BLANK-STRING)' return _s to ! on WordType.Int return _i.toString return '' def toTechString as String branch _which on WordType.String, return '[.typeOf.name](string, "[_s]")' on WordType.Int, return '[.typeOf.name](int, "[_i]")' throw FallThroughException(_which) class Definition inherits List cue init(words as IEnumerable?) base.init(words) class Program shared def main print 'FORTH interpeter written in Cobra [CobraCore.version]' args = CobraCore.commandLineArgs if args.count > 1 and args[1] == '-i' .interactive else .runExamples print 'Run with -i for interactive mode.' def runExamples print 'Examples/tests:' print fm = ForthMachine() fm.printDefinitionNames fm.processInput('1 .') fm.processInput('1 2 +') fm.processInput('stack.clear') fm.processInput('2 dup + .') fm.processInput('1 3 swap') fm.processInput('drop drop') fm.processInput('3 5 over') # fm.processInput(': AVERAGE ( a b -- avg ) + 2 / ; words stack.clear 10 20 AVERAGE') fm.processInput(': AVERAGE + 2 / ; words stack.clear 10 20 AVERAGE .') fm.processInput('def double 2 * end 2 double dup . double dup . double dup . drop') def interactive print 'Enter "quit" or "exit" to do so.' print fm = ForthMachine() fm.verbosity = 0 print 'words:' fm.printDefinitionNames print while true print 'forth> ' stop input = Console.readLine if input fm.processInput(input) if fm.didQuit, break print 'stack> ' stop fm.printStack print else break