Skip to content

Commit d50308c

Browse files
authored
forth (#65)
1 parent fe37b8e commit d50308c

File tree

9 files changed

+794
-0
lines changed

9 files changed

+794
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,14 @@
502502
"practices": [],
503503
"prerequisites": [],
504504
"difficulty": 7
505+
},
506+
{
507+
"slug": "forth",
508+
"name": "Forth",
509+
"uuid": "effcc47c-8a75-46be-b6f1-b0a9f00527ff",
510+
"practices": [],
511+
"prerequisites": [],
512+
"difficulty": 8
505513
}
506514
]
507515
},

exercises/practice/forth/.busted

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
return {
2+
default = {
3+
ROOT = { '.' }
4+
}
5+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Instructions
2+
3+
Implement an evaluator for a very simple subset of Forth.
4+
5+
[Forth][forth]
6+
is a stack-based programming language.
7+
Implement a very basic evaluator for a small subset of Forth.
8+
9+
Your evaluator has to support the following words:
10+
11+
- `+`, `-`, `*`, `/` (integer arithmetic)
12+
- `DUP`, `DROP`, `SWAP`, `OVER` (stack manipulation)
13+
14+
Your evaluator also has to support defining new words using the customary syntax: `: word-name definition ;`.
15+
16+
To keep things simple the only data type you need to support is signed integers of at least 16 bits size.
17+
18+
You should use the following rules for the syntax: a number is a sequence of one or more (ASCII) digits, a word is a sequence of one or more letters, digits, symbols or punctuation that is not a number.
19+
(Forth probably uses slightly different rules, but this is close enough.)
20+
21+
Words are case-insensitive.
22+
23+
[forth]: https://en.wikipedia.org/wiki/Forth_%28programming_language%29
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"authors": [
3+
"glennj"
4+
],
5+
"files": {
6+
"solution": [
7+
"forth.moon"
8+
],
9+
"test": [
10+
"forth_spec.moon"
11+
],
12+
"example": [
13+
".meta/example.moon"
14+
]
15+
},
16+
"blurb": "Implement an evaluator for a very simple subset of Forth."
17+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
class Stack
2+
new: (@stack = {}) =>
3+
4+
push: (val) => table.insert @stack, val
5+
pop: => table.remove @stack
6+
peek: => @stack[#@stack]
7+
size: => #@stack
8+
tolist: => @stack
9+
10+
11+
class Forth
12+
new: =>
13+
@_stack = Stack!
14+
@_primatives = {
15+
dup: (-> @dup!),
16+
drop: (-> @drop!),
17+
swap: (-> @swap!),
18+
over: (-> @over!),
19+
['+']: (-> @add!),
20+
['-']: (-> @sub!),
21+
['*']: (-> @mul!),
22+
['/']: (-> @div!),
23+
}
24+
@_words = {}
25+
26+
stack: => @_stack\tolist!
27+
28+
evaluate: (script) =>
29+
program = (table.concat script, ' ')\lower!
30+
tokens = [token for token in program\gmatch('[^%s]+')]
31+
32+
while #tokens > 0
33+
token = table.remove tokens, 1
34+
35+
if @_words[token]
36+
-- prepend the user-defined word into the tokens list
37+
table.insert(tokens, i, v) for i, v in ipairs @_words[token]
38+
39+
elseif token == ':'
40+
-- consume a bunch of tokens for the user word, and return the rest
41+
tokens = @_user_word tokens
42+
43+
elseif @_primatives[token]
44+
@_primatives[token]!
45+
46+
else
47+
num = tonumber token
48+
assert num, 'undefined operation'
49+
@_stack\push num
50+
51+
-- user-defined words
52+
_user_word: (tokens) =>
53+
name = table.remove tokens, 1
54+
assert not tonumber(name), 'illegal operation'
55+
56+
definition = {}
57+
while true
58+
token = table.remove tokens, 1
59+
break if token == ';'
60+
if @_words[token]
61+
table.insert definition, i, v for i, v in ipairs @_words[token]
62+
else
63+
table.insert definition, token
64+
65+
@_words[name] = definition
66+
tokens
67+
68+
-- Arithmetic
69+
add: => @_binary_op (a, b) -> a + b
70+
sub: => @_binary_op (a, b) -> a - b
71+
mul: => @_binary_op (a, b) -> a * b
72+
div: =>
73+
assert @_stack\peek! != 0, 'divide by zero'
74+
@_binary_op (a, b) -> a // b
75+
76+
_binary_op: (f) =>
77+
@_need 2
78+
b = @_stack\pop!
79+
a = @_stack\pop!
80+
@_stack\push f(a, b)
81+
82+
-- stack manipulation
83+
dup: =>
84+
@_need 1
85+
@_stack\push @_stack\peek!
86+
87+
drop: =>
88+
@_need 1
89+
_ = @_stack\pop!
90+
91+
swap: =>
92+
@_need 2
93+
b = @_stack\pop!
94+
a = @_stack\pop!
95+
@_stack\push b
96+
@_stack\push a
97+
98+
over: =>
99+
@_need 2
100+
b = @_stack\pop!
101+
a = @_stack\peek!
102+
@_stack\push b
103+
@_stack\push a
104+
105+
-- utils
106+
_need: (n) =>
107+
switch @_stack\size!
108+
when 0 then error 'empty stack'
109+
when 1 then error 'only one value on the stack' if n > 1
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
table_contains = (list, target) ->
2+
for elem in *list
3+
return true if elem == target
4+
false
5+
6+
int_list = (list) -> "{#{table.concat list, ', '}}"
7+
8+
instruction_list = (list, level) ->
9+
if #list <= 2
10+
"{#{table.concat [quote elem for elem in *list], ', '}}"
11+
else
12+
instrs = [indent quote(elem) .. ',', level + 1 for elem in *list]
13+
table.insert instrs, 1, '{'
14+
table.insert instrs, indent('}', level)
15+
table.concat instrs, '\n'
16+
17+
18+
{
19+
module_name: 'Forth',
20+
21+
generate_test: (case, level) ->
22+
local lines
23+
if case.scenarios and table_contains case.scenarios, 'local-scope'
24+
lines = {
25+
"interp1 = Forth!",
26+
"interp2 = Forth!",
27+
"interp1\\evaluate #{instruction_list case.input.instructionsFirst, level}",
28+
"interp2\\evaluate #{instruction_list case.input.instructionsSecond, level}",
29+
"assert.are.same #{int_list case.expected[1]}, interp1\\stack!",
30+
"assert.are.same #{int_list case.expected[2]}, interp2\\stack!",
31+
}
32+
33+
else
34+
lines = {
35+
'interpreter = Forth!',
36+
"instructions = #{instruction_list case.input.instructions, level}",
37+
}
38+
39+
if case.expected.error
40+
table.insert lines, line for line in *{
41+
'f = -> interpreter\\evaluate instructions',
42+
"assert.has.errors f, #{quote case.expected.error}"
43+
}
44+
45+
else
46+
table.insert lines, line for line in *{
47+
'interpreter\\evaluate instructions',
48+
"expected = #{int_list case.expected}",
49+
'assert.is.same expected, interpreter\\stack!'
50+
}
51+
52+
table.concat [indent line, level for line in *lines], '\n'
53+
}

0 commit comments

Comments
 (0)