Skip to content

Commit

Permalink
add forth solution (greetings from twitch live)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucaferranti committed Mar 16, 2024
1 parent ec94fe0 commit d8685cd
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 33 deletions.
85 changes: 85 additions & 0 deletions exercises/practice/forth/.meta/reference.chpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module Forth {

use List;
use Map;

const BINARY_OPS = ["+", "-", "*", "/", "swap", "over"];
const UNARY_OPS = ["drop", "dup"];

proc evaluate(statements: [] string) throws {
var stack: list(int);
var env: map(string, string);
for statement in statements {
var instructions = statement.toLower().split();
if instructions[0] == ":" && instructions[instructions.size-1] == ";" {
parse_variable_assignment(env, instructions);
} else {
for instruction in instructions {
evaluate_instruction(stack, env, instruction);
}
}
}
return stack;
}

proc parse_variable_assignment(ref env: map(string, string), instructions: [] string) throws {
const varname = instructions[1],
varvalues = instructions[2..instructions.size-2];
if isNumber(varname) then throw new IllegalArgumentError("illegal operation");
var substitued_instr = " ".join([varvalue in varvalues] env.get(varvalue, varvalue));
if env.contains(varname) then env.replace(varname, substitued_instr);
else env.add(varname, substitued_instr);
}

proc evaluate_instruction(ref stack: list(int), env: map(string, string), instr: string): void throws {
if env.contains(instr) {
var new_instrs = env.get(instr, "").split();
for new_instr in new_instrs {
evaluate_instruction(stack, env, new_instr);
}
} else execute_instruction(stack, instr);
}

proc execute_instruction(ref stack: list(int), instr: string) throws {
if isNumber(instr) then stack.pushBack(instr: int);
else if BINARY_OPS.find(instr) > -1 then execute_binary_operation(stack, instr);
else if UNARY_OPS.find(instr) > -1 then execute_unary_operation(stack, instr);
else throw new IllegalArgumentError("undefined operation");
}

proc execute_binary_operation(ref stack: list(int), instr: string) throws {
if stack.isEmpty() then throw new IllegalArgumentError("empty stack");
if stack.size == 1 then throw new IllegalArgumentError("only one value on the stack");

const op1 = stack.popBack(),
op2 = stack.popBack();

select instr {
when "+" do stack.pushBack(op1 + op2);
when "-" do stack.pushBack(op2 - op1);
when "*" do stack.pushBack(op1 * op2);
when "/" {
if op1 == 0 then throw new IllegalArgumentError("divide by zero");
else stack.pushBack(op2 / op1);
}
when "swap" do stack.pushBack([op1, op2]);
when "over" do stack.pushBack([op2, op1, op2]);
otherwise throw new IllegalArgumentError("How did you even get here?");
}
}

proc execute_unary_operation(ref stack: list(int), instr: string) throws {
if stack.isEmpty() then throw new IllegalArgumentError("empty stack");

select instr {
when "drop" do stack.popBack();
otherwise do stack.pushBack(stack[stack.size-1]); // dup
}
}

proc isNumber(s: string) {
if s.startsWith("+") || s.startsWith("-") then return s[1..].isDigit();
else return s.isDigit();
}

}
67 changes: 34 additions & 33 deletions exercises/practice/forth/test/tests.chpl
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use UnitTest;

use UnitTest.TestError;
use List;
use Forth;

proc testParsingAndNumbersNumbersJustGetPushedOntoTheStack(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 3 4 5"]), [1, 2, 3, 4, 5]);
test.assertEqual(evaluate(["1 2 3 4 5"]), new list([1, 2, 3, 4, 5]));
}

proc testParsingAndNumbersPushesNegativeNumbersOntoTheStack(test : borrowed Test) throws {
test.assertEqual(evaluate(["-1 -2 -3 -4 -5"]), [-1, -2, -3, -4, -5]);
test.assertEqual(evaluate(["-1 -2 -3 -4 -5"]), new list([-1, -2, -3, -4, -5]));
}

proc testAdditionCanAddTwoNumbers(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 +"]), [3]);
test.assertEqual(evaluate(["1 2 +"]), new list([3]));
}

proc testAdditionErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
Expand All @@ -37,7 +38,7 @@ proc testAdditionErrorsIfThereIsOnlyOneValueOnTheStack(test : borrowed Test) thr
}

proc testSubtractionCanSubtractTwoNumbers(test : borrowed Test) throws {
test.assertEqual(evaluate(["3 4 -"]), [-1]);
test.assertEqual(evaluate(["3 4 -"]), new list([-1]));
}

proc testSubtractionErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
Expand All @@ -63,7 +64,7 @@ proc testSubtractionErrorsIfThereIsOnlyOneValueOnTheStack(test : borrowed Test)
}

proc testMultiplicationCanMultiplyTwoNumbers(test : borrowed Test) throws {
test.assertEqual(evaluate(["2 4 *"]), [8]);
test.assertEqual(evaluate(["2 4 *"]), new list([8]));
}

proc testMultiplicationErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
Expand All @@ -89,11 +90,11 @@ proc testMultiplicationErrorsIfThereIsOnlyOneValueOnTheStack(test : borrowed Tes
}

proc testDivisionCanDivideTwoNumbers(test : borrowed Test) throws {
test.assertEqual(evaluate(["12 3 /"]), [4]);
test.assertEqual(evaluate(["12 3 /"]), new list([4]));
}

proc testDivisionPerformsIntegerDivision(test : borrowed Test) throws {
test.assertEqual(evaluate(["8 3 /"]), [2]);
test.assertEqual(evaluate(["8 3 /"]), new list([2]));
}

proc testDivisionErrorsIfDividingByZero(test : borrowed Test) throws {
Expand Down Expand Up @@ -130,19 +131,19 @@ proc testDivisionErrorsIfThereIsOnlyOneValueOnTheStack(test : borrowed Test) thr
}

proc testCombinedArithmeticAdditionAndSubtraction(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 + 4 -"]), [-1]);
test.assertEqual(evaluate(["1 2 + 4 -"]), new list([-1]));
}

proc testCombinedArithmeticMultiplicationAndDivision(test : borrowed Test) throws {
test.assertEqual(evaluate(["2 4 * 3 /"]), [2]);
test.assertEqual(evaluate(["2 4 * 3 /"]), new list([2]));
}

proc testDupCopiesAValueOnTheStack(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 dup"]), [1, 1]);
test.assertEqual(evaluate(["1 dup"]), new list([1, 1]));
}

proc testDupCopiesTheTopValueOnTheStack(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 dup"]), [1, 2, 2]);
test.assertEqual(evaluate(["1 2 dup"]), new list([1, 2, 2]));
}

proc testDupErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
Expand All @@ -157,11 +158,11 @@ proc testDupErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
}

proc testDropRemovesTheTopValueOnTheStackIfItIsTheOnlyOne(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 drop"]), []);
test.assertTrue(evaluate(["1 drop"]).isEmpty());
}

proc testDropRemovesTheTopValueOnTheStackIfItIsNotTheOnlyOne(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 drop"]), [1]);
test.assertEqual(evaluate(["1 2 drop"]), new list([1]));
}

proc testDropErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
Expand All @@ -176,11 +177,11 @@ proc testDropErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
}

proc testSwapSwapsTheTopTwoValuesOnTheStackIfTheyAreTheOnlyOnes(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 swap"]), [2, 1]);
test.assertEqual(evaluate(["1 2 swap"]), new list([2, 1]));
}

proc testSwapSwapsTheTopTwoValuesOnTheStackIfTheyAreNotTheOnlyOnes(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 3 swap"]), [1, 3, 2]);
test.assertEqual(evaluate(["1 2 3 swap"]), new list([1, 3, 2]));
}

proc testSwapErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
Expand All @@ -206,11 +207,11 @@ proc testSwapErrorsIfThereIsOnlyOneValueOnTheStack(test : borrowed Test) throws
}

proc testOverCopiesTheSecondElementIfThereAreOnlyTwo(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 over"]), [1, 2, 1]);
test.assertEqual(evaluate(["1 2 over"]), new list([1, 2, 1]));
}

proc testOverCopiesTheSecondElementIfThereAreMoreThanTwo(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 3 over"]), [1, 2, 3, 2]);
test.assertEqual(evaluate(["1 2 3 over"]), new list([1, 2, 3, 2]));
}

proc testOverErrorsIfThereIsNothingOnTheStack(test : borrowed Test) throws {
Expand All @@ -236,33 +237,33 @@ proc testOverErrorsIfThereIsOnlyOneValueOnTheStack(test : borrowed Test) throws
}

proc testUserDefinedWordsCanConsistOfBuiltInWords(test : borrowed Test) throws {
test.assertEqual(evaluate([": dup-twice dup dup ;", "1 dup-twice"]), [1, 1, 1]);
test.assertEqual(evaluate([": dup-twice dup dup ;", "1 dup-twice"]), new list([1, 1, 1]));
}

proc testUserDefinedWordsExecuteInTheRightOrder(test : borrowed Test) throws {
test.assertEqual(evaluate([": countup 1 2 3 ;", "countup"]), [1, 2, 3]);
test.assertEqual(evaluate([": countup 1 2 3 ;", "countup"]), new list([1, 2, 3]));
}

proc testUserDefinedWordsCanOverrideOtherUserDefinedWords(test : borrowed Test) throws {
test.assertEqual(evaluate([": foo dup ;", ": foo dup dup ;", "1 foo"]), [1, 1, 1]);
test.assertEqual(evaluate([": foo dup ;", ": foo dup dup ;", "1 foo"]), new list([1, 1, 1]));
}

proc testUserDefinedWordsCanOverrideBuiltInWords(test : borrowed Test) throws {
test.assertEqual(evaluate([": swap dup ;", "1 swap"]), [1, 1]);
test.assertEqual(evaluate([": swap dup ;", "1 swap"]), new list([1, 1]));
}

proc testUserDefinedWordsCanOverrideBuiltInOperators(test : borrowed Test) throws {
test.assertEqual(evaluate([": + * ;", "3 4 +"]), [12]);
test.assertEqual(evaluate([": + * ;", "3 4 +"]), new list([12]));
}

proc testUserDefinedWordsCanUseDifferentWordsWithTheSameName(test : borrowed Test) throws {
test.assertEqual(evaluate([": foo 5 ;", ": bar foo ;", ": foo 6 ;", "bar foo"]), [5, 6]);
test.assertEqual(evaluate([": foo 5 ;", ": bar foo ;", ": foo 6 ;", "bar foo"]), new list([5, 6]));
}

proc testUserDefinedWordsCanDefineWordThatUsesWordWithTheSameName(test : borrowed Test) throws {
test.assertEqual(evaluate([": foo 10 ;", ": foo foo 1 + ;", "foo"]), [11]);
test.assertEqual(evaluate([": foo 10 ;", ": foo foo 1 + ;", "foo"]), new list([11]));
}

proc testUserDefinedWordsCannotRedefineNonNegativeNumbers(test : borrowed Test) throws {
try {
evaluate([": 1 2 ;"]);
Expand Down Expand Up @@ -299,31 +300,31 @@ proc testUserDefinedWordsErrorsIfExecutingANonExistentWord(test : borrowed Test)
proc testUserDefinedWordsOnlyDefinesLocally(test : borrowed Test) throws {
var result1 = evaluate([": + - ;", "1 1 +"]);
var result2 = evaluate(["1 1 +"]);
test.assertEqual([result1, result2], [[0], [2]]);
test.assertEqual([result1, result2], [new list([0]), new list([2])]);
}

proc testCaseInsensitivityDupIsCaseInsensitive(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 DUP Dup dup"]), [1, 1, 1, 1]);
test.assertEqual(evaluate(["1 DUP Dup dup"]), new list([1, 1, 1, 1]));
}

proc testCaseInsensitivityDropIsCaseInsensitive(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 3 4 DROP Drop drop"]), [1]);
test.assertEqual(evaluate(["1 2 3 4 DROP Drop drop"]), new list([1]));
}

proc testCaseInsensitivitySwapIsCaseInsensitive(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 SWAP 3 Swap 4 swap"]), [2, 3, 4, 1]);
test.assertEqual(evaluate(["1 2 SWAP 3 Swap 4 swap"]), new list([2, 3, 4, 1]));
}

proc testCaseInsensitivityOverIsCaseInsensitive(test : borrowed Test) throws {
test.assertEqual(evaluate(["1 2 OVER Over over"]), [1, 2, 1, 2, 1]);
test.assertEqual(evaluate(["1 2 OVER Over over"]), new list([1, 2, 1, 2, 1]));
}

proc testCaseInsensitivityUserDefinedWordsAreCaseInsensitive(test : borrowed Test) throws {
test.assertEqual(evaluate([": foo dup ;", "1 FOO Foo foo"]), [1, 1, 1, 1]);
test.assertEqual(evaluate([": foo dup ;", "1 FOO Foo foo"]), new list([1, 1, 1, 1]));
}

proc testCaseInsensitivityDefinitionsAreCaseInsensitive(test : borrowed Test) throws {
test.assertEqual(evaluate([": SWAP DUP Dup dup ;", "1 swap"]), [1, 1, 1, 1]);
test.assertEqual(evaluate([": SWAP DUP Dup dup ;", "1 swap"]), new list([1, 1, 1, 1]));
}

UnitTest.main();

0 comments on commit d8685cd

Please sign in to comment.