From 4021504b34470c91822f2ca71c59ea785ebd0e04 Mon Sep 17 00:00:00 2001 From: Alex Bukin Date: Sat, 3 Sep 2022 18:08:24 +0300 Subject: [PATCH 1/7] contributing translation of js's track 'forth' excersize. --- .../practice/forth/.docs/instructions.md | 26 ++ exercises/practice/forth/.meta/config.json | 22 ++ .../practice/forth/.meta/lib/example.dart | 150 +++++++++ .../practice/forth/analysis_options.yaml | 18 ++ exercises/practice/forth/lib/forth.dart | 3 + exercises/practice/forth/pubspec.yaml | 5 + exercises/practice/forth/test/forth_test.dart | 295 ++++++++++++++++++ 7 files changed, 519 insertions(+) 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/lib/example.dart create mode 100644 exercises/practice/forth/analysis_options.yaml create mode 100644 exercises/practice/forth/lib/forth.dart create mode 100644 exercises/practice/forth/pubspec.yaml create mode 100644 exercises/practice/forth/test/forth_test.dart diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md new file mode 100644 index 00000000..f481b725 --- /dev/null +++ b/exercises/practice/forth/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Implement an evaluator for a very simple subset of Forth. + +[Forth](https://en.wikipedia.org/wiki/Forth_%28programming_language%29) +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. diff --git a/exercises/practice/forth/.meta/config.json b/exercises/practice/forth/.meta/config.json new file mode 100644 index 00000000..e837d879 --- /dev/null +++ b/exercises/practice/forth/.meta/config.json @@ -0,0 +1,22 @@ +{ + "blurb": "Implement an evaluator for a very simple subset of Forth.", + "authors": [ + "matthewmorgan" + ], + "contributors": [ + "AlexeyBukin" + ], + "files": { + "solution": [ + "lib/forth.dart" + ], + "test": [ + "test/forth_test.dart" + ], + "example": [ + ".meta/lib/example.dart" + ] + }, + "source": "Exercism JS track", + "source_url": "https://exercism.org/tracks/javascript/exercises/forth" +} diff --git a/exercises/practice/forth/.meta/lib/example.dart b/exercises/practice/forth/.meta/lib/example.dart new file mode 100644 index 00000000..0e927d88 --- /dev/null +++ b/exercises/practice/forth/.meta/lib/example.dart @@ -0,0 +1,150 @@ +import 'dart:collection'; + +class Forth { + final _stack = Stack(); + + List get stack => _stack.asList; + + late final flattenDictionary = {}; + + late final evaluateDictionary = { + '+': addition, + '-': subtraction, + '*': multiplication, + '/': division, + 'dup': dup, + 'drop': drop, + 'swap': swap, + 'over': over, + }; + + void evaluate(String expression) { + final flat = flatten(expression.toLowerCase()); + + if (flat.isEmpty) { + return; + } + final words = flat.split(' '); + for (final word in words) { + if (evaluateDictionary.containsKey(word)) { + evaluateDictionary[word]!.call(); + } else { + final number = int.parse(word); + _stack.push(number); + } + } + } + + String flatten(String expression) { + final words = expression.split(' '); + for (int t = 0; t < words.length; t++) { + final word = words[t]; + if (flattenDictionary.containsKey(word)) { + words[t] = flattenDictionary[word]!; + } else if (word == ':') { + final semicolon = words.indexOf(';', t); + if (semicolon == -1) { + throw Exception('Unterminated definition'); + } + defineCommand(words[t + 1], words.sublist(t + 2, semicolon).join(' ')); + words.removeRange(t, semicolon + 1); + t = semicolon; + } else if (!evaluateDictionary.containsKey(word) && !isKeyword(word)) { + throw Exception('Unknown command'); + } + } + return words.join(' '); + } + + /// Must be not a number (-?\d+), not a ':', not a ';' + static bool isKeyword(String word) => RegExp(r'^(-?\d+|:|;)$').hasMatch(word); + + void defineCommand(String word, String expression) { + if (isKeyword(word)) { + throw Exception('Invalid definition'); + } + flattenDictionary[word] = flatten(expression); + } + + void addition() { + final a = _stack.pop(); + final b = _stack.pop(); + _stack.push(b + a); + } + + void subtraction() { + final a = _stack.pop(); + final b = _stack.pop(); + _stack.push(b - a); + } + + void multiplication() { + final a = _stack.pop(); + final b = _stack.pop(); + _stack.push(b * a); + } + + void division() { + final a = _stack.pop(); + if (a == 0) { + throw Exception('Division by zero'); + } + final b = _stack.pop(); + _stack.push(b ~/ a); + } + + void dup() { + final a = _stack.peek(); + _stack.push(a); + } + + void drop() { + _stack.pop(); + } + + void swap() { + final a = _stack.pop(); + final b = _stack.pop(); + _stack.push(a); + _stack.push(b); + } + + void over() { + final a = _stack.pop(); + final b = _stack.peek(); + _stack.push(a); + _stack.push(b); + } +} + +class Stack { + List get asList => _stack.toList(); + + final _stack = Queue(); + + bool get isEmpty => _stack.isEmpty; + + int get size => _stack.length; + + int peek() { + if (_stack.isEmpty) { + throw Exception('Stack empty'); + } + return _stack.last; + } + + int pop() { + if (_stack.isEmpty) { + throw Exception('Stack empty'); + } + return _stack.removeLast(); + } + + void push(int number) { + _stack.addLast(number); + } + + void deleteAll({int after = 0}) { + _stack.take(after); + } +} diff --git a/exercises/practice/forth/analysis_options.yaml b/exercises/practice/forth/analysis_options.yaml new file mode 100644 index 00000000..c06363d6 --- /dev/null +++ b/exercises/practice/forth/analysis_options.yaml @@ -0,0 +1,18 @@ +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + errors: + unused_element: error + unused_import: error + unused_local_variable: error + dead_code: error + +linter: + rules: + # Error Rules + - avoid_relative_lib_imports + - avoid_types_as_parameter_names + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - valid_regexps diff --git a/exercises/practice/forth/lib/forth.dart b/exercises/practice/forth/lib/forth.dart new file mode 100644 index 00000000..dbb3b297 --- /dev/null +++ b/exercises/practice/forth/lib/forth.dart @@ -0,0 +1,3 @@ +class Forth { + // Put your code here +} diff --git a/exercises/practice/forth/pubspec.yaml b/exercises/practice/forth/pubspec.yaml new file mode 100644 index 00000000..764fefc9 --- /dev/null +++ b/exercises/practice/forth/pubspec.yaml @@ -0,0 +1,5 @@ +name: 'forth' +environment: + sdk: '>=2.12.0 <3.0.0' +dev_dependencies: + test: '<2.0.0' diff --git a/exercises/practice/forth/test/forth_test.dart b/exercises/practice/forth/test/forth_test.dart new file mode 100644 index 00000000..258b4bef --- /dev/null +++ b/exercises/practice/forth/test/forth_test.dart @@ -0,0 +1,295 @@ +import 'package:forth/forth.dart'; +import 'package:test/test.dart'; + +void main() { + group("Forth", forthTests); +} + +void forthTests() { + final throwsEmptyStack = throwsA(isA().having((e) => e.toString(), 'message', 'Exception: Stack empty')); + + late Forth forth; + + setUp(() => forth = Forth()); + + group('parsing and numbers', () { + test('numbers just get pushed onto the stack', () { + forth.evaluate('1 2 3 4 5'); + expect(forth.stack, equals([1, 2, 3, 4, 5])); + }); + + test('pushes negative numbers onto the stack', () { + forth.evaluate('-1 -2 -3 -4 -5'); + expect(forth.stack, equals([-1, -2, -3, -4, -5])); + }); + }); + + group('addition', () { + test('can add two numbers', () { + forth.evaluate('1 2 +'); + expect(forth.stack, equals([3])); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('+'), throwsEmptyStack); + }); + + test('errors if there is only one value on the stack', () { + expect(() => forth.evaluate('1 +'), throwsEmptyStack); + }); + }); + + group('subtraction', () { + test('can subtract two numbers', () { + forth.evaluate('3 4 -'); + expect(forth.stack, equals([-1])); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('-'), throwsEmptyStack); + }); + + test('errors if there is only one value on the stack', () { + expect(() => forth.evaluate('1 -'), throwsEmptyStack); + }); + }); + + group('multiplication', () { + test('can multiply two numbers', () { + forth.evaluate('2 4 *'); + expect(forth.stack, equals([8])); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('*'), throwsEmptyStack); + }); + + test('errors if there is only one value on the stack', () { + expect(() => forth.evaluate('1 *'), throwsEmptyStack); + }); + }); + + group('division', () { + test('can divide two numbers', () { + forth.evaluate('12 3 /'); + expect(forth.stack, equals([4])); + }); + + test('performs integer division', () { + forth.evaluate('8 3 /'); + expect(forth.stack, equals([2])); + }); + + test('errors if dividing by zero', () { + expect( + () => forth.evaluate('4 0 /'), + throwsA(isA().having((e) => e.toString(), 'message', 'Exception: Division by zero')), + ); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('/'), throwsEmptyStack); + }); + + test('errors if there is only one value on the stack', () { + expect(() => forth.evaluate('1 /'), throwsEmptyStack); + }); + }); + + group('combined arithmetic', () { + test('addition and subtraction', () { + forth.evaluate('1 2 + 4 -'); + expect(forth.stack, equals([-1])); + }); + + test('multiplication and division', () { + forth.evaluate('2 4 * 3 /'); + expect(forth.stack, equals([2])); + }); + }); + + group('dup', () { + test('copies a value on the stack', () { + forth.evaluate('1 dup'); + expect(forth.stack, equals([1, 1])); + }); + + test('copies the top value on the stack', () { + forth.evaluate('1 2 dup'); + expect(forth.stack, equals([1, 2, 2])); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('dup'), throwsEmptyStack); + }); + }); + + group('drop', () { + test('removes the top value on the stack if it is the only one', () { + forth.evaluate('1 drop'); + expect(forth.stack, equals([])); + }); + + test('removes the top value on the stack if it is not the only one', () { + forth.evaluate('1 2 drop'); + expect(forth.stack, equals([1])); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('drop'), throwsEmptyStack); + }); + }); + + group('swap', () { + test('swaps the top two values on the stack if they are the only ones', () { + forth.evaluate('1 2 swap'); + expect(forth.stack, equals([2, 1])); + }); + + test('swaps the top two values on the stack if they are not the only ones', () { + forth.evaluate('1 2 3 swap'); + expect(forth.stack, equals([1, 3, 2])); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('swap'), throwsEmptyStack); + }); + + test('errors if there is only one value on the stack', () { + expect(() => forth.evaluate('1 swap'), throwsEmptyStack); + }); + }); + + group('over', () { + test('copies the second element if there are only two', () { + forth.evaluate('1 2 over'); + expect(forth.stack, equals([1, 2, 1])); + }); + + test('copies the second element if there are more than two', () { + forth.evaluate('1 2 3 over'); + expect(forth.stack, equals([1, 2, 3, 2])); + }); + + test('errors if there is nothing on the stack', () { + expect(() => forth.evaluate('over'), throwsEmptyStack); + }); + + test('errors if there is only one value on the stack', () { + expect(() => forth.evaluate('1 over'), throwsEmptyStack); + }); + }); + + group('user-defined words', () { + test('can consist of built-in words', () { + forth.evaluate(': dup-twice dup dup ;'); + forth.evaluate('1 dup-twice'); + expect(forth.stack, equals([1, 1, 1])); + }); + + test('execute in the right order', () { + forth.evaluate(': countup 1 2 3 ;'); + forth.evaluate('countup'); + expect(forth.stack, equals([1, 2, 3])); + }); + + test('can override other user-defined words', () { + forth.evaluate(': foo dup ;'); + forth.evaluate(': foo dup dup ;'); + forth.evaluate('1 foo'); + expect(forth.stack, equals([1, 1, 1])); + }); + + test('can override built-in words', () { + forth.evaluate(': swap dup ;'); + forth.evaluate('1 swap'); + expect(forth.stack, equals([1, 1])); + }); + + test('can override built-in operators', () { + forth.evaluate(': + * ;'); + forth.evaluate('3 4 +'); + expect(forth.stack, equals([12])); + }); + + test('can use different words with the same name', () { + forth.evaluate(': foo 5 ;'); + forth.evaluate(': bar foo ;'); + forth.evaluate(': foo 6 ;'); + forth.evaluate('bar foo'); + expect(forth.stack, equals([5, 6])); + }); + + test('can define word that uses word with the same name', () { + forth.evaluate(': foo 10 ;'); + forth.evaluate(': foo foo 1 + ;'); + forth.evaluate('foo'); + expect(forth.stack, equals([11])); + }); + + test('cannot redefine numbers', () { + expect( + () => forth.evaluate(': 1 2 ;'), + throwsA(isA().having((e) => e.toString(), 'message', 'Exception: Invalid definition')), + ); + }); + + test('cannot redefine negative numbers', () { + expect( + () => forth.evaluate(': -1 2 ;'), + throwsA(isA().having((e) => e.toString(), 'message', 'Exception: Invalid definition')), + ); + }); + + test('errors if executing a non-existent word', () { + expect( + () => forth.evaluate('foo'), + throwsA(isA().having((e) => e.toString(), 'message', 'Exception: Unknown command')), + ); + }); + + test('only defines locally', () { + final first = Forth(); + final second = Forth(); + first.evaluate(': + - ;'); + first.evaluate('1 1 +'); + second.evaluate('1 1 +'); + expect(first.stack, equals([0])); + expect(second.stack, equals([2])); + }); + }); + + group('case-insensitivity', () { + test('DUP is case-insensitive', () { + forth.evaluate('1 DUP Dup dup'); + expect(forth.stack, equals([1, 1, 1, 1])); + }); + + test('DROP is case-insensitive', () { + forth.evaluate('1 2 3 4 DROP Drop drop'); + expect(forth.stack, equals([1])); + }); + + test('SWAP is case-insensitive', () { + forth.evaluate('1 2 SWAP 3 Swap 4 swap'); + expect(forth.stack, equals([2, 3, 4, 1])); + }); + + test('OVER is case-insensitive', () { + forth.evaluate('1 2 OVER Over over'); + expect(forth.stack, equals([1, 2, 1, 2, 1])); + }); + + test('user-defined words are case-insensitive', () { + forth.evaluate(': foo dup ;'); + forth.evaluate('1 FOO Foo foo'); + expect(forth.stack, equals([1, 1, 1, 1])); + }); + + test('definitions are case-insensitive', () { + forth.evaluate(': SWAP DUP Dup dup ;'); + forth.evaluate('1 swap'); + expect(forth.stack, equals([1, 1, 1, 1])); + }); + }); +} From d4cbd57fc01e670a995981f040b14e121dae2767 Mon Sep 17 00:00:00 2001 From: Alex Bukin Date: Fri, 30 Sep 2022 00:02:12 +0300 Subject: [PATCH 2/7] Update exercises/practice/forth/.docs/instructions.md Co-authored-by: Victor Goff --- .../practice/forth/.docs/instructions.md | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md index f481b725..fdc7888b 100644 --- a/exercises/practice/forth/.docs/instructions.md +++ b/exercises/practice/forth/.docs/instructions.md @@ -2,25 +2,21 @@ Implement an evaluator for a very simple subset of Forth. -[Forth](https://en.wikipedia.org/wiki/Forth_%28programming_language%29) -is a stack-based programming language. Implement a very basic evaluator -for a small subset of 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 ;`. +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. +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.) +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 From 9d02e8874abbcdd400f00171c8e96026f3a4e925 Mon Sep 17 00:00:00 2001 From: Alex Bukin Date: Fri, 7 Oct 2022 18:30:46 +0300 Subject: [PATCH 3/7] Update exercises/practice/forth/.docs/instructions.md Co-authored-by: Stargator <7527155+Stargator@users.noreply.github.com> --- exercises/practice/forth/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md index fdc7888b..0368aff8 100644 --- a/exercises/practice/forth/.docs/instructions.md +++ b/exercises/practice/forth/.docs/instructions.md @@ -19,4 +19,4 @@ You should use the following rules for the syntax: a number is a sequence of one Words are case-insensitive. -[Forth]: https://en.wikipedia.org/wiki/Forth_%28programming_language%29 +[Forth]: https://en.wikipedia.org/wiki/Forth_(programming_language) From 6ee932594afb9be878dcfb7faaff5f9fcbaffcfe Mon Sep 17 00:00:00 2001 From: Alex Bukin Date: Fri, 7 Oct 2022 18:54:52 +0300 Subject: [PATCH 4/7] Made Stack class private because the test suite does not expect to access it. --- exercises/practice/forth/.meta/lib/example.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/forth/.meta/lib/example.dart b/exercises/practice/forth/.meta/lib/example.dart index 0e927d88..ac61caef 100644 --- a/exercises/practice/forth/.meta/lib/example.dart +++ b/exercises/practice/forth/.meta/lib/example.dart @@ -1,7 +1,7 @@ import 'dart:collection'; class Forth { - final _stack = Stack(); + final _stack = _Stack(); List get stack => _stack.asList; @@ -117,7 +117,7 @@ class Forth { } } -class Stack { +class _Stack { List get asList => _stack.toList(); final _stack = Queue(); From bcc2dc0a37d57bce3a6673a36835d7189369e4f6 Mon Sep 17 00:00:00 2001 From: Alex Bukin Date: Fri, 7 Oct 2022 21:05:41 +0300 Subject: [PATCH 5/7] added forth slug into config.json --- config.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config.json b/config.json index 9f148b9f..92bebacf 100644 --- a/config.json +++ b/config.json @@ -153,6 +153,15 @@ "math" ] }, + { + "slug": "forth", + "name": "Forth", + "uuid": "7137cdad-fdac-4e30-836a-c92c352bdc4d", + "practices": [], + "prerequisites": [], + "difficulty": 8, + "topics": ["domain_specific_languages", "parsing", "stacks"] + }, { "slug": "word-count", "name": "Word Count", From b4eb05cff653450e44b4b1282ef85a4cd37845e9 Mon Sep 17 00:00:00 2001 From: Stargator Date: Sat, 8 Oct 2022 16:28:53 -0400 Subject: [PATCH 6/7] links.yml: Disable links.yml workflow for any contributions to exercises --- .github/workflows/links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index e5051b99..1f16bc2f 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -7,7 +7,7 @@ on: - 'concept/**/*.md' - 'concept/**/links.json' - 'docs/*.md' - - 'exercises/**/*.md' +# - 'exercises/**/*.md' Temporarily disabled by @Stargator - 'reference/*.md' - '*.md' - '**/**/*.md' From 2e8e9331490dc05f75fdb6e4703c25a6a2e7e297 Mon Sep 17 00:00:00 2001 From: Stargator Date: Sat, 8 Oct 2022 16:30:37 -0400 Subject: [PATCH 7/7] links.yml: Disable links.yml workflow for any changes to links.yml --- .github/workflows/links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index 1f16bc2f..4426cd04 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -3,7 +3,7 @@ name: Links on: pull_request: paths: - - '.github/workflows/links.yml' +# - '.github/workflows/links.yml' Temporarily disabled by @Stargator - 'concept/**/*.md' - 'concept/**/links.json' - 'docs/*.md'