Wiki

root/cobra/trunk/Samples/forth.cobra

Revision 2339, 10.8 KB (checked in by Chuck.Esterbrook, 2 years ago)

Code cleanup.

  • Property svn:eol-style set to native
Line 
1"""
2forth.cobra
3
4This is a budding FORTH interpreter featuring:
5* basic stack item manipulation (dup, over, swap, drop, ...)
6* stack manipulation (0SP aka stack.clear)
7* math (+ - * / %)
8* definitions (; NAME CODE :  -OR-  def NAME CODE end)
9* You can multiply strings as in: foo 3 * -- foofoofoo
10
11...and not much else right now.
12
13Run with no arguments to see some example FORTH get executed.
14
15Run with -i for "interactive mode" to enter your own FORTH.
16Enter "quit" or "exit" to do so.
17
18Forth tutorial:
19http://www.softsynth.com/pforth/pf_tut.htm
20
21In addition to some extra synonym words which you'll see in the list of words, this particular
22interperter also accepts 'def' for ':' and 'end' for ';'. So these are equivalent:
23
24    : AVERAGE ( a b -- avg ) + 2 / ;
25    def AVERAGE ( a b -- avg ) + 2 / end
26
27TODO:
28    [ ] Text in parens is a comment.
29    [ ] variables
30    [ ] Could do the built-in definitions by tagging their methods with attributes
31    [ ] Support fractional numbers. In other words, decimals.
32    [ ] Automated tests.
33    [ ] Consider making Definition abstract with concrete subs: BuiltInDefinition, WordsDefinition with possible DelegateDefinition as well
34    [ ] 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.
35    [ ] 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.
36    [ ] More FORTH:
37- In general, cover everything at http://www.softsynth.com/pforth/pf_tut.htm
38ROT ( a b c -- b c a , ROTate third item to top )
39PICK ( ... v3 v2 v1 v0 N -- ... v3 v2 v1 v0 vN )
40      0 PICK is equivalent to DUP
41      1 PICK is equivalent to OVER
42DROP ( n -- , remove top of stack )
43?DUP ( n -- n n | 0 , duplicate only if non-zero, '|' means OR )
44-ROT ( a b c -- c a b , rotate top to third position )
452SWAP ( a b c d -- c d a b , swap pairs )
462OVER ( a b c d -- a b c d a b , leapfrog pair )
472DUP ( a b -- a b a b , duplicate pair )
482DROP ( a b -- , remove pair )
49NIP ( a b -- b , remove second item from stack )
50TUCK ( a b -- b a b , copy top item to third position ) 
51
52Fast math operations:
531+ 1- 2+ 2- 2* 2/
54
55Define new words:
56      : AVERAGE ( a b -- avg ) + 2/ ;
57"""
58
59
60class ForthMachine
61
62    var _stack as Stack<of Word>
63    var _definitions as Dictionary<of Word, Definition>
64    var _didQuit as bool
65    var _verbosity = 1
66   
67    cue init
68        base.init
69        _stack = Stack<of Word>()
70        _definitions = Dictionary<of Word, Definition>()
71
72        .define('quit', 'built-in:quit')
73        .define('exit', 'built-in:quit')
74
75        .define('words', 'built-in:printDefinitionNames')
76
77        .define('.', 'built-in:printTop')
78        .define('.S', 'built-in:printStack')
79        .define('stack.print', 'built-in:printStack')
80
81        .define('0SP', 'built-in:clearStack')
82        .define('stack.clear', 'built-in:clearStack')
83
84        .define('drop', 'built-in:drop')
85        .define('dup', 'built-in:dup')
86        .define('over', 'built-in:over')
87        .define('swap', 'built-in:swap')
88
89        .define('+', 'built-in:add')
90        .define('-', 'built-in:sub')
91        .define('*', 'built-in:mul')
92        .define('/', 'built-in:div')
93        .define('%', 'built-in:mod')
94
95    get didQuit from var
96
97    pro verbosity from var
98
99    ## Fundamental operations to implement a FORTH machine
100
101    def wordFor(text as String) as Word
102        require text.trim.length
103        return Word(text)
104
105    def wordFor(i as int) as Word
106        return Word(i)
107
108    def push(word as Word)
109        require word.isString implies word.string.trim.length
110        _stack.push(word)
111
112    def push(word as String)
113        .push(Word(word))
114
115    def peek as Word
116        if _stack.count
117            return _stack.peek
118        else
119            print 'ERROR: Stack is empty.'
120            return Word(0)
121
122    def pop as Word
123        if _stack.count
124            return _stack.pop
125        else
126            print 'ERROR: Stack is empty.'
127            return Word(0)
128
129    def define(word as String, definition as String)
130        words = for rawWord in definition.split where rawWord.trim.length>0 get .wordFor(rawWord.trim)  # CC: axe > 0
131        _definitions[.wordFor(word)] = Definition(words)
132
133    def define(word as Word, definition as IEnumerable<of Word>)
134        _definitions[word] = Definition(definition)
135
136    def definitionFor(word as Word) as Definition?
137        result as Definition?
138        if _definitions.tryGetValue(word, out result)
139            return result to !
140        else
141            return nil
142
143    def invoke(name as Word, definition as Definition)
144        if definition.count
145            word = definition[0]
146            if word.toString.startsWith('built-in:')
147                methodName = word.toString['built-in:'.length:]
148                methodName = methodName[0].toString.toUpper + methodName[1:] # because at run-time, method names are capped for compatibility with C# and VB
149                method = .typeOf.getMethod(methodName)
150                method.invoke(this, nil)
151            else
152                for word in definition
153                    if word.isString
154                        defin = .definitionFor(word)
155                        if defin
156                            .invoke(word, defin)
157                            continue
158                    .push(word)
159        else
160            # empty definition is a no-op
161            pass
162
163    def error(message as String)
164        print 'ERROR:', message
165
166
167    ## Conveniences
168
169    def processInput(input as String)
170        mode = 0  # 0 - normal, 1 - define
171        if _verbosity
172            print 'input>', input
173        rawWords = input.trim.split
174        for rawWord in rawWords
175            rawWord = rawWord.trim
176            if not rawWord.length
177                continue
178            branch mode
179                on 0  # normal
180                    if rawWord in [':', 'def']
181                        defWords = List<of Word>()
182                        mode = 1  # into define mode
183                    else
184                        word = .wordFor(rawWord)
185                        if word.isString
186                            defin = .definitionFor(word)
187                            if defin
188                                .invoke(word, defin)
189                                continue
190                        .push(word)
191                on 1  # definition mode
192                    if rawWord in [';', 'end']
193                        .define(defWords[0], defWords[1:])
194                        defWords.clear
195                        mode = 0  # back to normal
196                    else
197                        defWords.add(.wordFor(rawWord))
198        if _verbosity
199            print 'stack> ' stop
200            .printStack
201            print
202
203
204    ## Built-in definitions
205
206    def quit
207        _didQuit = true
208
209    def printDefinitionNames
210        keys = List<of Word>(_definitions.keys)
211        keys.sort
212        for key in keys, print '[key] ' stop
213        print
214
215    def printTop
216        print .pop
217
218    def printStack
219        for word in Stack<of Word>(_stack)  # Stack wrapper reverses the order so right most printed element is the top of the stack
220            print '[word] | ' stop
221        print
222
223
224    ## Built-ins: whole stack manipulation
225
226    def clearStack
227        _stack.clear
228
229
230    ## Built-ins: stack element manipulations
231   
232    def drop
233        .pop
234
235    def dup
236        .push(.peek)
237
238    def over
239        """
240        a b -- a b a
241        """
242        b = .pop
243        a = .pop
244        .push(a)
245        .push(b)
246        .push(a)
247
248    def swap
249        """
250        a b -- b a
251        """
252        b = .pop
253        a = .pop
254        .push(b)
255        .push(a)
256
257
258    ## Built-ins: arithmetic
259   
260    def add
261        b = .pop
262        a = .pop
263        if a.isInt and b.isInt
264            .push(.wordFor(a.int + b.int))
265        else if a.isString and b.isString
266            .push(.wordFor(a.string + b.string))
267        else
268            .error('Cannot add "[a]" and "[b]".')
269
270    def sub
271        b = .pop
272        a = .pop
273        if a.isInt and b.isInt
274            .push(.wordFor(a.int - b.int))
275        else
276            .error('Cannot subtract "[a]" and "[b]".')
277
278    def mul
279        b = .pop
280        a = .pop
281        if a.isInt and b.isInt
282            .push(.wordFor(a.int * b.int))
283            return
284        else if a.isString and b.isInt
285            s = a.string
286            i = b.int
287        else if a.isInt and b.isString
288            s = b.string
289            i = a.int
290        else
291            .error('Cannot add "[a]" and "[b]".')
292        sb = StringBuilder()
293        for j in i, sb.append(s)
294        .push(.wordFor(sb.toString))
295
296    def div
297        b = .pop
298        a = .pop
299        if a.isInt and b.isInt
300            .push(.wordFor(a.int // b.int))
301        else
302            .error('Cannot divide "[a]" and "[b]".')
303
304    def mod
305        b = .pop
306        a = .pop
307        if a.isInt and b.isInt
308            .push(.wordFor(a.int % b.int))
309        else
310            .error('Cannot modulate "[a]" and "[b]".')
311
312
313class Word implements IComparable<of Word>
314
315    enum WordType
316        String
317        Int
318
319    var _which as WordType
320    var _i as int
321    var _s as String?
322
323    cue init(text as String)
324        """
325        Creates a new word, interpreting the text as an integer if possible.
326        """
327        .init(text, true)
328
329    cue init(text as String, interpret as bool)
330        """
331        When interpretation is false, text is simply taken as is.
332        Otherwise if the text represents an integer, this word will be one.
333        """
334        test
335            w = Word('foo')
336            assert w.isString
337            w = Word('10')
338            assert w.isInt
339            w = Word('10', false)
340            assert w.isString
341        body
342            base.init
343            if interpret and int.tryParse(text, out _i)
344                _which = WordType.Int
345            else
346                _s = text
347                _which = WordType.String
348
349    cue init(i as int)
350        base.init
351        _i = i
352        _which = WordType.Int
353
354    get isInt as bool
355        return _which == WordType.Int
356
357    get int as int
358        require .isInt
359        return _i
360
361    get isString as bool
362        return _which == WordType.String
363
364    get string as String
365        require .isString
366        return _s to !
367
368    def equals(other as Object?) as bool is override
369        if other inherits Word
370            if _which == other._which
371                branch _which
372                    on WordType.String, return _s == other._s
373                    on WordType.Int, return _i == other._i
374            return false
375        else
376            return false
377   
378    def compareTo(other as Word) as int
379        if .isInt
380            if other.isInt
381                return .int.compareTo(other.int)
382            else
383                return -1
384        else if .isString
385            if other.isString
386                return .string.compareTo(other.string)
387            else
388                return 1
389        else
390            throw FallThroughException(other)
391
392    def getHashCode as int is override
393        branch _which
394            on WordType.String, return _s.getHashCode
395            on WordType.Int, return _i.getHashCode
396        throw FallThroughException(_which)
397
398    def toString as String is override
399        branch _which
400            on WordType.String
401                if _s == '', return '(EMPTY-STRING)'
402                if _s.trim == '', return '(BLANK-STRING)'
403                return _s to !
404            on WordType.Int
405                return _i.toString
406        return ''
407
408    def toTechString as String
409        branch _which
410            on WordType.String, return '[.typeOf.name](string, "[_s]")'
411            on WordType.Int, return '[.typeOf.name](int, "[_i]")'
412        throw FallThroughException(_which)
413
414
415class Definition inherits List<of Word>
416
417    cue init(words as IEnumerable<of Word>?)
418        base.init(words)
419
420
421class Program
422
423    shared
424
425        def main
426            print 'FORTH interpeter written in Cobra [CobraCore.version]'
427            args = CobraCore.commandLineArgs
428            if args.count > 1 and args[1] == '-i'
429                .interactive
430            else
431                .runExamples
432                print 'Run with -i for interactive mode.'
433
434        def runExamples
435            print 'Examples/tests:'
436            print
437            fm = ForthMachine()
438            fm.printDefinitionNames
439            fm.processInput('1 .')
440            fm.processInput('1 2 +')
441            fm.processInput('stack.clear')
442            fm.processInput('2 dup + .')
443            fm.processInput('1 3 swap')
444            fm.processInput('drop drop')
445            fm.processInput('3 5 over')
446#           fm.processInput(': AVERAGE ( a b -- avg ) + 2 / ; words stack.clear 10 20 AVERAGE')
447            fm.processInput(': AVERAGE + 2 / ; words stack.clear 10 20 AVERAGE .')
448            fm.processInput('def double 2 * end  2  double dup .   double dup .  double dup .  drop')
449
450        def interactive
451            print 'Enter "quit" or "exit" to do so.'
452            print
453            fm = ForthMachine()
454            fm.verbosity = 0
455            print 'words:'
456            fm.printDefinitionNames
457            print
458            while true
459                print 'forth> ' stop
460                input = Console.readLine
461                if input
462                    fm.processInput(input)
463                    if fm.didQuit, break
464                    print 'stack> ' stop
465                    fm.printStack
466                    print
467                else
468                    break
Note: See TracBrowser for help on using the browser.