From 50c4a9a860efe1888bc089a8fce80a6d06df3c85 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Wed, 24 Dec 2025 19:21:24 -0500 Subject: [PATCH] forth --- config.json | 8 + exercises/practice/forth/.busted | 5 + .../practice/forth/.docs/instructions.md | 23 + exercises/practice/forth/.meta/config.json | 17 + exercises/practice/forth/.meta/example.moon | 109 +++++ .../practice/forth/.meta/spec_generator.moon | 53 +++ exercises/practice/forth/.meta/tests.toml | 175 ++++++++ exercises/practice/forth/forth.moon | 9 + exercises/practice/forth/forth_spec.moon | 395 ++++++++++++++++++ 9 files changed, 794 insertions(+) create mode 100644 exercises/practice/forth/.busted create mode 100644 exercises/practice/forth/.docs/instructions.md create mode 100644 exercises/practice/forth/.meta/config.json create mode 100644 exercises/practice/forth/.meta/example.moon create mode 100644 exercises/practice/forth/.meta/spec_generator.moon create mode 100644 exercises/practice/forth/.meta/tests.toml create mode 100644 exercises/practice/forth/forth.moon create mode 100644 exercises/practice/forth/forth_spec.moon diff --git a/config.json b/config.json index 03ec44f..60d37cb 100644 --- a/config.json +++ b/config.json @@ -502,6 +502,14 @@ "practices": [], "prerequisites": [], "difficulty": 7 + }, + { + "slug": "forth", + "name": "Forth", + "uuid": "effcc47c-8a75-46be-b6f1-b0a9f00527ff", + "practices": [], + "prerequisites": [], + "difficulty": 8 } ] }, diff --git a/exercises/practice/forth/.busted b/exercises/practice/forth/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/forth/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md new file mode 100644 index 0000000..91ad26e --- /dev/null +++ b/exercises/practice/forth/.docs/instructions.md @@ -0,0 +1,23 @@ +# Instructions + +Implement an evaluator for a very simple subset of Forth. + +[Forth][forth] +is a stack-based programming language. +Implement a very basic evaluator for a small subset of Forth. + +Your evaluator has to support the following words: + +- `+`, `-`, `*`, `/` (integer arithmetic) +- `DUP`, `DROP`, `SWAP`, `OVER` (stack manipulation) + +Your evaluator also has to support defining new words using the customary syntax: `: word-name definition ;`. + +To keep things simple the only data type you need to support is signed integers of at least 16 bits size. + +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. +(Forth probably uses slightly different rules, but this is close enough.) + +Words are case-insensitive. + +[forth]: https://en.wikipedia.org/wiki/Forth_%28programming_language%29 diff --git a/exercises/practice/forth/.meta/config.json b/exercises/practice/forth/.meta/config.json new file mode 100644 index 0000000..068301a --- /dev/null +++ b/exercises/practice/forth/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "forth.moon" + ], + "test": [ + "forth_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Implement an evaluator for a very simple subset of Forth." +} diff --git a/exercises/practice/forth/.meta/example.moon b/exercises/practice/forth/.meta/example.moon new file mode 100644 index 0000000..cc031c9 --- /dev/null +++ b/exercises/practice/forth/.meta/example.moon @@ -0,0 +1,109 @@ +class Stack + new: (@stack = {}) => + + push: (val) => table.insert @stack, val + pop: => table.remove @stack + peek: => @stack[#@stack] + size: => #@stack + tolist: => @stack + + +class Forth + new: => + @_stack = Stack! + @_primatives = { + dup: (-> @dup!), + drop: (-> @drop!), + swap: (-> @swap!), + over: (-> @over!), + ['+']: (-> @add!), + ['-']: (-> @sub!), + ['*']: (-> @mul!), + ['/']: (-> @div!), + } + @_words = {} + + stack: => @_stack\tolist! + + evaluate: (script) => + program = (table.concat script, ' ')\lower! + tokens = [token for token in program\gmatch('[^%s]+')] + + while #tokens > 0 + token = table.remove tokens, 1 + + if @_words[token] + -- prepend the user-defined word into the tokens list + table.insert(tokens, i, v) for i, v in ipairs @_words[token] + + elseif token == ':' + -- consume a bunch of tokens for the user word, and return the rest + tokens = @_user_word tokens + + elseif @_primatives[token] + @_primatives[token]! + + else + num = tonumber token + assert num, 'undefined operation' + @_stack\push num + + -- user-defined words + _user_word: (tokens) => + name = table.remove tokens, 1 + assert not tonumber(name), 'illegal operation' + + definition = {} + while true + token = table.remove tokens, 1 + break if token == ';' + if @_words[token] + table.insert definition, i, v for i, v in ipairs @_words[token] + else + table.insert definition, token + + @_words[name] = definition + tokens + + -- Arithmetic + add: => @_binary_op (a, b) -> a + b + sub: => @_binary_op (a, b) -> a - b + mul: => @_binary_op (a, b) -> a * b + div: => + assert @_stack\peek! != 0, 'divide by zero' + @_binary_op (a, b) -> a // b + + _binary_op: (f) => + @_need 2 + b = @_stack\pop! + a = @_stack\pop! + @_stack\push f(a, b) + + -- stack manipulation + dup: => + @_need 1 + @_stack\push @_stack\peek! + + drop: => + @_need 1 + _ = @_stack\pop! + + swap: => + @_need 2 + b = @_stack\pop! + a = @_stack\pop! + @_stack\push b + @_stack\push a + + over: => + @_need 2 + b = @_stack\pop! + a = @_stack\peek! + @_stack\push b + @_stack\push a + + -- utils + _need: (n) => + switch @_stack\size! + when 0 then error 'empty stack' + when 1 then error 'only one value on the stack' if n > 1 diff --git a/exercises/practice/forth/.meta/spec_generator.moon b/exercises/practice/forth/.meta/spec_generator.moon new file mode 100644 index 0000000..8a9628c --- /dev/null +++ b/exercises/practice/forth/.meta/spec_generator.moon @@ -0,0 +1,53 @@ +table_contains = (list, target) -> + for elem in *list + return true if elem == target + false + +int_list = (list) -> "{#{table.concat list, ', '}}" + +instruction_list = (list, level) -> + if #list <= 2 + "{#{table.concat [quote elem for elem in *list], ', '}}" + else + instrs = [indent quote(elem) .. ',', level + 1 for elem in *list] + table.insert instrs, 1, '{' + table.insert instrs, indent('}', level) + table.concat instrs, '\n' + + +{ + module_name: 'Forth', + + generate_test: (case, level) -> + local lines + if case.scenarios and table_contains case.scenarios, 'local-scope' + lines = { + "interp1 = Forth!", + "interp2 = Forth!", + "interp1\\evaluate #{instruction_list case.input.instructionsFirst, level}", + "interp2\\evaluate #{instruction_list case.input.instructionsSecond, level}", + "assert.are.same #{int_list case.expected[1]}, interp1\\stack!", + "assert.are.same #{int_list case.expected[2]}, interp2\\stack!", + } + + else + lines = { + 'interpreter = Forth!', + "instructions = #{instruction_list case.input.instructions, level}", + } + + if case.expected.error + table.insert lines, line for line in *{ + 'f = -> interpreter\\evaluate instructions', + "assert.has.errors f, #{quote case.expected.error}" + } + + else + table.insert lines, line for line in *{ + 'interpreter\\evaluate instructions', + "expected = #{int_list case.expected}", + 'assert.is.same expected, interpreter\\stack!' + } + + table.concat [indent line, level for line in *lines], '\n' +} diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml new file mode 100644 index 0000000..d1e146a --- /dev/null +++ b/exercises/practice/forth/.meta/tests.toml @@ -0,0 +1,175 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[9962203f-f00a-4a85-b404-8a8ecbcec09d] +description = "parsing and numbers -> numbers just get pushed onto the stack" + +[fd7a8da2-6818-4203-a866-fed0714e7aa0] +description = "parsing and numbers -> pushes negative numbers onto the stack" + +[9e69588e-a3d8-41a3-a371-ea02206c1e6e] +description = "addition -> can add two numbers" + +[52336dd3-30da-4e5c-8523-bdf9a3427657] +description = "addition -> errors if there is nothing on the stack" + +[06efb9a4-817a-435e-b509-06166993c1b8] +description = "addition -> errors if there is only one value on the stack" + +[1e07a098-c5fa-4c66-97b2-3c81205dbc2f] +description = "addition -> more than two values on the stack" + +[09687c99-7bbc-44af-8526-e402f997ccbf] +description = "subtraction -> can subtract two numbers" + +[5d63eee2-1f7d-4538-b475-e27682ab8032] +description = "subtraction -> errors if there is nothing on the stack" + +[b3cee1b2-9159-418a-b00d-a1bb3765c23b] +description = "subtraction -> errors if there is only one value on the stack" + +[2c8cc5ed-da97-4cb1-8b98-fa7b526644f4] +description = "subtraction -> more than two values on the stack" + +[5df0ceb5-922e-401f-974d-8287427dbf21] +description = "multiplication -> can multiply two numbers" + +[9e004339-15ac-4063-8ec1-5720f4e75046] +description = "multiplication -> errors if there is nothing on the stack" + +[8ba4b432-9f94-41e0-8fae-3b3712bd51b3] +description = "multiplication -> errors if there is only one value on the stack" + +[5cd085b5-deb1-43cc-9c17-6b1c38bc9970] +description = "multiplication -> more than two values on the stack" + +[e74c2204-b057-4cff-9aa9-31c7c97a93f5] +description = "division -> can divide two numbers" + +[54f6711c-4b14-4bb0-98ad-d974a22c4620] +description = "division -> performs integer division" + +[a5df3219-29b4-4d2f-b427-81f82f42a3f1] +description = "division -> errors if dividing by zero" + +[1d5bb6b3-6749-4e02-8a79-b5d4d334cb8a] +description = "division -> errors if there is nothing on the stack" + +[d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d] +description = "division -> errors if there is only one value on the stack" + +[f224f3e0-b6b6-4864-81de-9769ecefa03f] +description = "division -> more than two values on the stack" + +[ee28d729-6692-4a30-b9be-0d830c52a68c] +description = "combined arithmetic -> addition and subtraction" + +[40b197da-fa4b-4aca-a50b-f000d19422c1] +description = "combined arithmetic -> multiplication and division" + +[f749b540-53aa-458e-87ec-a70797eddbcb] +description = "combined arithmetic -> multiplication and addition" + +[c8e5a4c2-f9bf-4805-9a35-3c3314e4989a] +description = "combined arithmetic -> addition and multiplication" + +[c5758235-6eef-4bf6-ab62-c878e50b9957] +description = "dup -> copies a value on the stack" + +[f6889006-5a40-41e7-beb3-43b09e5a22f4] +description = "dup -> copies the top value on the stack" + +[40b7569c-8401-4bd4-a30d-9adf70d11bc4] +description = "dup -> errors if there is nothing on the stack" + +[1971da68-1df2-4569-927a-72bf5bb7263c] +description = "drop -> removes the top value on the stack if it is the only one" + +[8929d9f2-4a78-4e0f-90ad-be1a0f313fd9] +description = "drop -> removes the top value on the stack if it is not the only one" + +[6dd31873-6dd7-4cb8-9e90-7daa33ba045c] +description = "drop -> errors if there is nothing on the stack" + +[3ee68e62-f98a-4cce-9e6c-8aae6c65a4e3] +description = "swap -> swaps the top two values on the stack if they are the only ones" + +[8ce869d5-a503-44e4-ab55-1da36816ff1c] +description = "swap -> swaps the top two values on the stack if they are not the only ones" + +[74ba5b2a-b028-4759-9176-c5c0e7b2b154] +description = "swap -> errors if there is nothing on the stack" + +[dd52e154-5d0d-4a5c-9e5d-73eb36052bc8] +description = "swap -> errors if there is only one value on the stack" + +[a2654074-ba68-4f93-b014-6b12693a8b50] +description = "over -> copies the second element if there are only two" + +[c5b51097-741a-4da7-8736-5c93fa856339] +description = "over -> copies the second element if there are more than two" + +[6e1703a6-5963-4a03-abba-02e77e3181fd] +description = "over -> errors if there is nothing on the stack" + +[ee574dc4-ef71-46f6-8c6a-b4af3a10c45f] +description = "over -> errors if there is only one value on the stack" + +[ed45cbbf-4dbf-4901-825b-54b20dbee53b] +description = "user-defined words -> can consist of built-in words" + +[2726ea44-73e4-436b-bc2b-5ff0c6aa014b] +description = "user-defined words -> execute in the right order" + +[9e53c2d0-b8ef-4ad8-b2c9-a559b421eb33] +description = "user-defined words -> can override other user-defined words" + +[669db3f3-5bd6-4be0-83d1-618cd6e4984b] +description = "user-defined words -> can override built-in words" + +[588de2f0-c56e-4c68-be0b-0bb1e603c500] +description = "user-defined words -> can override built-in operators" + +[ac12aaaf-26c6-4a10-8b3c-1c958fa2914c] +description = "user-defined words -> can use different words with the same name" + +[53f82ef0-2750-4ccb-ac04-5d8c1aefabb1] +description = "user-defined words -> can define word that uses word with the same name" + +[35958cee-a976-4a0f-9378-f678518fa322] +description = "user-defined words -> cannot redefine non-negative numbers" + +[df5b2815-3843-4f55-b16c-c3ed507292a7] +description = "user-defined words -> cannot redefine negative numbers" + +[5180f261-89dd-491e-b230-62737e09806f] +description = "user-defined words -> errors if executing a non-existent word" + +[3c8bfef3-edbb-49c1-9993-21d4030043cb] +description = "user-defined words -> only defines locally" + +[7b83bb2e-b0e8-461f-ad3b-96ee2e111ed6] +description = "case-insensitivity -> DUP is case-insensitive" + +[339ed30b-f5b4-47ff-ab1c-67591a9cd336] +description = "case-insensitivity -> DROP is case-insensitive" + +[ee1af31e-1355-4b1b-bb95-f9d0b2961b87] +description = "case-insensitivity -> SWAP is case-insensitive" + +[acdc3a49-14c8-4cc2-945d-11edee6408fa] +description = "case-insensitivity -> OVER is case-insensitive" + +[5934454f-a24f-4efc-9fdd-5794e5f0c23c] +description = "case-insensitivity -> user-defined words are case-insensitive" + +[037d4299-195f-4be7-a46d-f07ca6280a06] +description = "case-insensitivity -> definitions are case-insensitive" diff --git a/exercises/practice/forth/forth.moon b/exercises/practice/forth/forth.moon new file mode 100644 index 0000000..c767708 --- /dev/null +++ b/exercises/practice/forth/forth.moon @@ -0,0 +1,9 @@ +class Forth + new: => + error 'Implement the constructor' + + stack: => + error 'Implement the stack method' + + evaluate: (script) => + error 'Implement the evaluate method' diff --git a/exercises/practice/forth/forth_spec.moon b/exercises/practice/forth/forth_spec.moon new file mode 100644 index 0000000..1d61125 --- /dev/null +++ b/exercises/practice/forth/forth_spec.moon @@ -0,0 +1,395 @@ +Forth = require 'forth' + +describe 'forth', -> + describe 'parsing and numbers', -> + it 'numbers just get pushed onto the stack', -> + interpreter = Forth! + instructions = {'1 2 3 4 5'} + interpreter\evaluate instructions + expected = {1, 2, 3, 4, 5} + assert.is.same expected, interpreter\stack! + + pending 'pushes negative numbers onto the stack', -> + interpreter = Forth! + instructions = {'-1 -2 -3 -4 -5'} + interpreter\evaluate instructions + expected = {-1, -2, -3, -4, -5} + assert.is.same expected, interpreter\stack! + + describe 'addition', -> + pending 'can add two numbers', -> + interpreter = Forth! + instructions = {'1 2 +'} + interpreter\evaluate instructions + expected = {3} + assert.is.same expected, interpreter\stack! + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'+'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + pending 'errors if there is only one value on the stack', -> + interpreter = Forth! + instructions = {'1 +'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'only one value on the stack' + + pending 'more than two values on the stack', -> + interpreter = Forth! + instructions = {'1 2 3 +'} + interpreter\evaluate instructions + expected = {1, 5} + assert.is.same expected, interpreter\stack! + + describe 'subtraction', -> + pending 'can subtract two numbers', -> + interpreter = Forth! + instructions = {'3 4 -'} + interpreter\evaluate instructions + expected = {-1} + assert.is.same expected, interpreter\stack! + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'-'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + pending 'errors if there is only one value on the stack', -> + interpreter = Forth! + instructions = {'1 -'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'only one value on the stack' + + pending 'more than two values on the stack', -> + interpreter = Forth! + instructions = {'1 12 3 -'} + interpreter\evaluate instructions + expected = {1, 9} + assert.is.same expected, interpreter\stack! + + describe 'multiplication', -> + pending 'can multiply two numbers', -> + interpreter = Forth! + instructions = {'2 4 *'} + interpreter\evaluate instructions + expected = {8} + assert.is.same expected, interpreter\stack! + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'*'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + pending 'errors if there is only one value on the stack', -> + interpreter = Forth! + instructions = {'1 *'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'only one value on the stack' + + pending 'more than two values on the stack', -> + interpreter = Forth! + instructions = {'1 2 3 *'} + interpreter\evaluate instructions + expected = {1, 6} + assert.is.same expected, interpreter\stack! + + describe 'division', -> + pending 'can divide two numbers', -> + interpreter = Forth! + instructions = {'12 3 /'} + interpreter\evaluate instructions + expected = {4} + assert.is.same expected, interpreter\stack! + + pending 'performs integer division', -> + interpreter = Forth! + instructions = {'8 3 /'} + interpreter\evaluate instructions + expected = {2} + assert.is.same expected, interpreter\stack! + + pending 'errors if dividing by zero', -> + interpreter = Forth! + instructions = {'4 0 /'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'divide by zero' + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'/'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + pending 'errors if there is only one value on the stack', -> + interpreter = Forth! + instructions = {'1 /'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'only one value on the stack' + + pending 'more than two values on the stack', -> + interpreter = Forth! + instructions = {'1 12 3 /'} + interpreter\evaluate instructions + expected = {1, 4} + assert.is.same expected, interpreter\stack! + + describe 'combined arithmetic', -> + pending 'addition and subtraction', -> + interpreter = Forth! + instructions = {'1 2 + 4 -'} + interpreter\evaluate instructions + expected = {-1} + assert.is.same expected, interpreter\stack! + + pending 'multiplication and division', -> + interpreter = Forth! + instructions = {'2 4 * 3 /'} + interpreter\evaluate instructions + expected = {2} + assert.is.same expected, interpreter\stack! + + pending 'multiplication and addition', -> + interpreter = Forth! + instructions = {'1 3 4 * +'} + interpreter\evaluate instructions + expected = {13} + assert.is.same expected, interpreter\stack! + + pending 'addition and multiplication', -> + interpreter = Forth! + instructions = {'1 3 4 + *'} + interpreter\evaluate instructions + expected = {7} + assert.is.same expected, interpreter\stack! + + describe 'dup', -> + pending 'copies a value on the stack', -> + interpreter = Forth! + instructions = {'1 dup'} + interpreter\evaluate instructions + expected = {1, 1} + assert.is.same expected, interpreter\stack! + + pending 'copies the top value on the stack', -> + interpreter = Forth! + instructions = {'1 2 dup'} + interpreter\evaluate instructions + expected = {1, 2, 2} + assert.is.same expected, interpreter\stack! + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'dup'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + describe 'drop', -> + pending 'removes the top value on the stack if it is the only one', -> + interpreter = Forth! + instructions = {'1 drop'} + interpreter\evaluate instructions + expected = {} + assert.is.same expected, interpreter\stack! + + pending 'removes the top value on the stack if it is not the only one', -> + interpreter = Forth! + instructions = {'1 2 drop'} + interpreter\evaluate instructions + expected = {1} + assert.is.same expected, interpreter\stack! + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'drop'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + describe 'swap', -> + pending 'swaps the top two values on the stack if they are the only ones', -> + interpreter = Forth! + instructions = {'1 2 swap'} + interpreter\evaluate instructions + expected = {2, 1} + assert.is.same expected, interpreter\stack! + + pending 'swaps the top two values on the stack if they are not the only ones', -> + interpreter = Forth! + instructions = {'1 2 3 swap'} + interpreter\evaluate instructions + expected = {1, 3, 2} + assert.is.same expected, interpreter\stack! + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'swap'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + pending 'errors if there is only one value on the stack', -> + interpreter = Forth! + instructions = {'1 swap'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'only one value on the stack' + + describe 'over', -> + pending 'copies the second element if there are only two', -> + interpreter = Forth! + instructions = {'1 2 over'} + interpreter\evaluate instructions + expected = {1, 2, 1} + assert.is.same expected, interpreter\stack! + + pending 'copies the second element if there are more than two', -> + interpreter = Forth! + instructions = {'1 2 3 over'} + interpreter\evaluate instructions + expected = {1, 2, 3, 2} + assert.is.same expected, interpreter\stack! + + pending 'errors if there is nothing on the stack', -> + interpreter = Forth! + instructions = {'over'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'empty stack' + + pending 'errors if there is only one value on the stack', -> + interpreter = Forth! + instructions = {'1 over'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'only one value on the stack' + + describe 'user-defined words', -> + pending 'can consist of built-in words', -> + interpreter = Forth! + instructions = {': dup-twice dup dup ;', '1 dup-twice'} + interpreter\evaluate instructions + expected = {1, 1, 1} + assert.is.same expected, interpreter\stack! + + pending 'execute in the right order', -> + interpreter = Forth! + instructions = {': countup 1 2 3 ;', 'countup'} + interpreter\evaluate instructions + expected = {1, 2, 3} + assert.is.same expected, interpreter\stack! + + pending 'can override other user-defined words', -> + interpreter = Forth! + instructions = { + ': foo dup ;', + ': foo dup dup ;', + '1 foo', + } + interpreter\evaluate instructions + expected = {1, 1, 1} + assert.is.same expected, interpreter\stack! + + pending 'can override built-in words', -> + interpreter = Forth! + instructions = {': swap dup ;', '1 swap'} + interpreter\evaluate instructions + expected = {1, 1} + assert.is.same expected, interpreter\stack! + + pending 'can override built-in operators', -> + interpreter = Forth! + instructions = {': + * ;', '3 4 +'} + interpreter\evaluate instructions + expected = {12} + assert.is.same expected, interpreter\stack! + + pending 'can use different words with the same name', -> + interpreter = Forth! + instructions = { + ': foo 5 ;', + ': bar foo ;', + ': foo 6 ;', + 'bar foo', + } + interpreter\evaluate instructions + expected = {5, 6} + assert.is.same expected, interpreter\stack! + + pending 'can define word that uses word with the same name', -> + interpreter = Forth! + instructions = { + ': foo 10 ;', + ': foo foo 1 + ;', + 'foo', + } + interpreter\evaluate instructions + expected = {11} + assert.is.same expected, interpreter\stack! + + pending 'cannot redefine non-negative numbers', -> + interpreter = Forth! + instructions = {': 1 2 ;'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'illegal operation' + + pending 'cannot redefine negative numbers', -> + interpreter = Forth! + instructions = {': -1 2 ;'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'illegal operation' + + pending 'errors if executing a non-existent word', -> + interpreter = Forth! + instructions = {'foo'} + f = -> interpreter\evaluate instructions + assert.has.errors f, 'undefined operation' + + pending 'only defines locally', -> + interp1 = Forth! + interp2 = Forth! + interp1\evaluate {': + - ;', '1 1 +'} + interp2\evaluate {'1 1 +'} + assert.are.same {0}, interp1\stack! + assert.are.same {2}, interp2\stack! + + describe 'case-insensitivity', -> + pending 'DUP is case-insensitive', -> + interpreter = Forth! + instructions = {'1 DUP Dup dup'} + interpreter\evaluate instructions + expected = {1, 1, 1, 1} + assert.is.same expected, interpreter\stack! + + pending 'DROP is case-insensitive', -> + interpreter = Forth! + instructions = {'1 2 3 4 DROP Drop drop'} + interpreter\evaluate instructions + expected = {1} + assert.is.same expected, interpreter\stack! + + pending 'SWAP is case-insensitive', -> + interpreter = Forth! + instructions = {'1 2 SWAP 3 Swap 4 swap'} + interpreter\evaluate instructions + expected = {2, 3, 4, 1} + assert.is.same expected, interpreter\stack! + + pending 'OVER is case-insensitive', -> + interpreter = Forth! + instructions = {'1 2 OVER Over over'} + interpreter\evaluate instructions + expected = {1, 2, 1, 2, 1} + assert.is.same expected, interpreter\stack! + + pending 'user-defined words are case-insensitive', -> + interpreter = Forth! + instructions = {': foo dup ;', '1 FOO Foo foo'} + interpreter\evaluate instructions + expected = {1, 1, 1, 1} + assert.is.same expected, interpreter\stack! + + pending 'definitions are case-insensitive', -> + interpreter = Forth! + instructions = {': SWAP DUP Dup dup ;', '1 swap'} + interpreter\evaluate instructions + expected = {1, 1, 1, 1} + assert.is.same expected, interpreter\stack!