├── LICENSE ├── README.md └── fortik.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Peter Sovietov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fortik 2 | 3 | Tiny Forth-like language for teaching purposes. 4 | 5 | 1. Extend it! 6 | 2. Rewrite it in your favorite language! 7 | -------------------------------------------------------------------------------- /fortik.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | 4 | def parse(tokens): 5 | stack = [[]] 6 | op = 'call' 7 | for token in tokens: 8 | match token: 9 | case '[': 10 | stack.append([]) 11 | case ']': 12 | code = stack.pop() 13 | stack[-1].append(('push', code)) 14 | case 'is' | 'to': 15 | op = token 16 | case num if num.isdigit(): 17 | stack[-1].append(('push', int(num))) 18 | case _: 19 | stack[-1].append((op, token)) 20 | op = 'call' 21 | return stack[0] 22 | 23 | 24 | def execute(words, stack, tree): 25 | words = words.copy() 26 | for node in tree: 27 | match node: 28 | case ('is', name): 29 | words[name] = stack.pop() 30 | case ('to', name): 31 | words[name] = [('push', stack.pop())] 32 | case ('push', value): 33 | stack.append(value) 34 | case ('call', name) if name in words: 35 | execute(words, stack, words[name]) 36 | case ('call', name) if name in LIB: 37 | LIB[name](words, stack) 38 | case _: 39 | raise SystemExit(f'unknown word: {node}') 40 | return words 41 | 42 | 43 | def binop(func): 44 | def word(words, stack): 45 | tos = stack.pop() 46 | stack.append(func(stack.pop(), tos)) 47 | return word 48 | 49 | 50 | def ifelse(words, stack): 51 | f_tree, t_tree = stack.pop(), stack.pop() 52 | execute(words, stack, t_tree if stack.pop() else f_tree) 53 | 54 | 55 | LIB = { 56 | '+': binop(operator.add), 57 | '-': binop(operator.sub), 58 | '*': binop(operator.mul), 59 | '/': binop(operator.floordiv), 60 | '<': binop(operator.lt), 61 | '.': lambda _, stack: print(stack.pop()), 62 | 'ifelse': ifelse 63 | } 64 | 65 | 66 | def repl(words, stack): 67 | while True: 68 | words = execute(words, stack, parse(input('> ').split())) 69 | 70 | 71 | tokens = ''' 72 | [ to a a a ] is dup 73 | [ to a ] is drop 74 | [ to b to a b a ] is swap 75 | [ 0 swap - ] is neg 76 | [ to c to b to a b b * 4 a c * * - ] is D 77 | 78 | 5 neg 4 neg 1 D . 79 | 80 | [ to n n 2 < [ 1 ] [ n n 1 - fact * ] ifelse ] is fact 81 | 82 | 5 fact . 83 | 84 | [ to n n [ n 1 - odd ] [ 1 ] ifelse ] is even 85 | [ to n n [ n 1 - even ] [ 0 ] ifelse ] is odd 86 | 87 | 42 dup even . odd . 88 | '''.split() 89 | 90 | repl(execute({}, [], parse(tokens)), []) 91 | --------------------------------------------------------------------------------