From 2d91200cf5cfff97c7df6310548f78d5a869680f Mon Sep 17 00:00:00 2001 From: Lukas Renggli Date: Fri, 5 Jan 2024 16:54:06 +0100 Subject: [PATCH] Add a Pascal grammar. --- README.md | 4 + lib/pascal.dart | 4 + lib/src/pascal/grammar.dart | 427 ++++++++++++++++++++++++++++++++++++ test/all_test.dart | 2 + test/pascal_test.dart | 331 ++++++++++++++++++++++++++++ 5 files changed, 768 insertions(+) create mode 100644 lib/pascal.dart create mode 100644 lib/src/pascal/grammar.dart create mode 100644 test/pascal_test.dart diff --git a/README.md b/README.md index 0de93d7..a91ccf8 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ dart run bin/lisp/lisp.dart This example contains a simple evaluator for mathematical expressions, it builds a parse-tree that can then be used to print or evaluate expressions. +### Pascal + +A complete pascal grammar following the Apple Pascal Standard from 1978. + ### Prolog This example contains a simple grammar and evaluator for Prolog programs. The code is reasonably complete to run and evaluate basic prolog programs. Binaries for a Read–Eval–Print Loop (REPL) are provided for the console and the web browser. diff --git a/lib/pascal.dart b/lib/pascal.dart new file mode 100644 index 0000000..8d11642 --- /dev/null +++ b/lib/pascal.dart @@ -0,0 +1,4 @@ +/// This package contains the grammar of Pascal. +library pascal; + +export 'src/pascal/grammar.dart'; diff --git a/lib/src/pascal/grammar.dart b/lib/src/pascal/grammar.dart new file mode 100644 index 0000000..a719471 --- /dev/null +++ b/lib/src/pascal/grammar.dart @@ -0,0 +1,427 @@ +import 'package:petitparser/petitparser.dart'; + +/// Pascal grammar definition based on +/// http://www.danamania.com/print/Apple%20Pascal%20Poster/PascalPosterV3%20A1.pdf +class PascalGrammarDefinition extends GrammarDefinition { + @override + Parser start() => ref0(program).end(); + + Parser program() => seq6( + ref1(token, 'program'), + ref0(identifier), + seq3( + ref1(token, '('), + ref0(identifier).plusSeparated(ref1(token, ',')), + ref1(token, ')'), + ).optional(), + ref1(token, ';'), + ref0(block), + ref1(token, '.'), + ); + + // region statement + Parser statement() => seq2( + ref0(statementLabel).optional(), + [ + ref0(statementAssign), + ref0(statementCall), + ref0(statementBlock), + ref0(statementIf), + ref0(statementRepeat), + ref0(statementWhile), + ref0(statementFor), + ref0(statementCase), + ref0(statementWith), + ref0(statementGoto), + ref0(statementExit), + ].toChoiceParser()) + .optional(); + + Parser statementLabel() => seq2(ref0(unsignedInteger), ref1(token, ':')); + + Parser statementAssign() => seq3( + ref0(variable), + ref1(token, ':='), + ref0(expression), + ); + + Parser statementCall() => seq2( + ref0(identifier), + seq3(ref1(token, '('), ref0(expression).plusSeparated(ref1(token, ',')), + ref1(token, ')')) + .optional(), + ); + + Parser statementBlock() => seq3( + ref1(token, 'begin'), + ref0(statement).plusSeparated(ref1(token, ';')), + ref1(token, 'end'), + ); + + Parser statementIf() => seq5( + ref1(token, 'if'), + ref0(expression), + ref1(token, 'then'), + ref0(statement), + seq2( + ref1(token, 'else'), + ref0(statement), + ).optional(), + ); + + Parser statementRepeat() => seq4( + ref1(token, 'repeat'), + ref0(statement).plusSeparated(ref1(token, ';')), + ref1(token, 'until'), + ref0(expression), + ); + + Parser statementWhile() => seq4( + ref1(token, 'while'), + ref0(expression), + ref1(token, 'do'), + ref0(statement), + ); + + Parser statementFor() => seq8( + ref1(token, 'for'), + ref0(identifier), + ref1(token, ':='), + ref0(expression), + [ref1(token, 'to'), ref1(token, 'downto')].toChoiceParser(), + ref0(expression), + ref1(token, 'do'), + ref0(statement), + ); + + Parser statementCase() => seq5( + ref1(token, 'case'), + ref0(expression), + ref1(token, 'of'), + seq3( + ref0(constant).plusSeparated(ref1(token, ',')), + ref1(token, ':'), + ref0(statement), + ).plusSeparated(ref1(token, ';')), + ref1(token, 'end'), + ); + + Parser statementWith() => seq4( + ref1(token, 'with'), + ref0(variable).plusSeparated(ref1(token, ',')), + ref1(token, 'do'), + ref0(statement), + ); + + Parser statementGoto() => seq2( + ref1(token, 'goto'), + ref0(unsignedInteger), + ); + + Parser statementExit() => seq4( + ref1(token, 'exit'), + ref1(token, '('), + [ref1(token, 'program'), ref0(identifier)].toChoiceParser(), + ref1(token, ')'), + ); + + // endregion + + // region block + + Parser block() => seq6( + ref0(blockLabel).optional(), + ref0(blockConst).optional(), + ref0(blockType).optional(), + ref0(blockVar).optional(), + [ref0(blockProcedure), ref0(blockFunction)].toChoiceParser().star(), + ref0(blockStatement), + ); + + Parser blockLabel() => seq3( + ref1(token, 'label'), + ref0(unsignedInteger).plusSeparated(ref1(token, ',')), + ref1(token, ';'), + ); + + Parser blockConst() => seq2( + ref1(token, 'const'), + seq4( + ref0(identifier), + ref1(token, '='), + ref0(constant), + ref1(token, ';'), + ).plus(), + ); + + Parser blockType() => seq2( + ref1(token, 'type'), + seq4( + ref0(identifier), + ref1(token, '='), + ref0(type), + ref1(token, ';'), + ).plus(), + ); + + Parser blockVar() => seq2( + ref1(token, 'var'), + seq4( + ref0(identifier).plusSeparated(ref1(token, ',')), + ref1(token, ':'), + ref0(type), + ref1(token, ';'), + ).plus(), + ); + + Parser blockProcedure() => seq6( + ref1(token, 'procedure'), + ref0(identifier), + ref0(parameterList), + ref1(token, ';'), + ref0(block), + ref1(token, ';'), + ); + + Parser blockFunction() => seq8( + ref1(token, 'function'), + ref0(identifier), + ref0(parameterList), + ref1(token, ':'), + ref0(identifier), + ref1(token, ';'), + ref0(block), + ref1(token, ';'), + ); + + Parser blockStatement() => seq3( + ref1(token, 'begin'), + ref0(statement).plusSeparated(ref1(token, ';')), + ref1(token, 'end'), + ); + + // endregion + + // region type + + Parser type() => [ + ref0(simpleType), + ref0(typePointer), + seq2( + ref1(token, 'packed').optional(), + [ + ref0(typeSet), + ref0(typeArray), + ref0(typeRecord), + ref0(typeFile), + ].toChoiceParser(), + ) + ].toChoiceParser(); + + Parser typePointer() => seq2( + ref1(token, '^'), + ref0(identifier), + ); + + Parser typeSet() => seq3( + ref1(token, 'set'), + ref1(token, 'of'), + ref0(simpleType), + ); + + Parser typeArray() => seq6( + ref1(token, 'array'), + ref1(token, '['), + ref0(simpleType).plusSeparated(ref1(token, ',')), + ref1(token, ']'), + ref1(token, 'of'), + ref0(type), + ); + + Parser typeRecord() => seq3( + ref1(token, 'record'), + ref0(fieldList), + ref1(token, 'end'), + ); + + Parser typeFile() => seq2( + ref1(token, 'file'), + seq2(ref1(token, 'of'), ref0(type)).optional(), + ); + + // endregion + + Parser identifier() => + ref1(token, seq2(letter(), word().star()).flatten('identifier expected')) + .where((each) => !_keywords.contains(each)); + + Parser variable() => seq2( + ref0(identifier), + [ + seq3( + ref1(token, '['), + ref0(expression).plusSeparated(ref1(token, ',')), + ref1(token, ']'), + ), + seq2(ref1(token, '.'), ref0(identifier)), + ref1(token, '^'), + ].toChoiceParser().star()); + + Parser unsignedNumber() => ref1( + token, + seq3( + digit().plus(), + seq2(char('.'), digit().plus()).optional(), + seq3( + pattern('eE'), + pattern('+-').optional(), + digit().plus(), + ).optional(), + ).flatten('unsigned number expected').map(num.parse)); + + Parser stringLiteral() => ref1( + token, + seq3( + char("'"), + pattern("^'").star(), + char("'"), + ).flatten('string expected')); + + Parser expression() => seq2( + ref0(simpleExpression), + seq2( + [ + ref1(token, '<'), + ref1(token, '<='), + ref1(token, '='), + ref1(token, '<>'), + ref1(token, '>='), + ref1(token, '>'), + ref1(token, 'in'), + ].toChoiceParser(), + ref0(simpleExpression)) + .optional()); + + Parser simpleExpression() => seq2( + [ref1(token, '+'), ref1(token, '-')].toChoiceParser().optional(), + ref0(term).plusSeparated(ref1(token, 'or')), + ).plus(); + + Parser term() => ref0(factor).plusSeparated([ + ref1(token, '*'), + ref1(token, '/'), + ref1(token, 'div'), + ref1(token, 'mod'), + ref1(token, 'and'), + ].toChoiceParser()); + + Parser factor() => [ + seq3(ref1(token, '('), ref0(expression), ref1(token, ')')), + seq2(ref1(token, 'not'), ref0(factor)), + seq3( + ref1(token, '['), + seq2( + ref0(expression), + seq2(ref1(token, '..'), ref0(expression)).optional(), + ).starSeparated(ref1(token, ',')), + ref1(token, ']'), + ), + seq2( + ref0(identifier), + seq3( + ref1(token, '('), + ref0(expression).plusSeparated(ref1(token, ',')), + ref1(token, ')')) + .optional()), + ref0(unsignedConstant), + ref0(variable), + ].toChoiceParser(); + + Parser unsignedConstant() => [ + ref1(token, 'nil'), + ref0(stringLiteral), + ref0(unsignedNumber), + ref0(identifier), + ].toChoiceParser(); + + Parser parameterList() => seq3( + ref1(token, '('), + seq4( + ref1(token, 'var').optional(), + ref0(identifier).plusSeparated(ref1(token, ',')), + ref1(token, ':'), + ref0(identifier)) + .plusSeparated(ref1(token, ';')), + ref1(token, ')')) + .optional(); + + Parser unsignedInteger() => ref1( + token, digit().plusString('unsigned integer expected').map(int.parse)); + + Parser constant() => [ + seq2(pattern('+-'), + [ref0(identifier), ref0(unsignedNumber)].toChoiceParser()), + ref0(unsignedConstant), + ].toChoiceParser(); + + Parser simpleType() => [ + seq3(ref1(token, '('), ref0(identifier).plusSeparated(ref1(token, ',')), + ref1(token, ')')), + seq3(ref0(constant), ref1(token, '..'), ref0(constant)), + ref0(identifier), + ].toChoiceParser(); + + Parser fieldList() => [ + seq2(ref0(fieldListBase), ref0(fieldListCase).optional()), + ref0(fieldListCase), + ].toChoiceParser(); + + Parser fieldListBase() => seq3( + ref0(identifier).plusSeparated(ref1(token, ',')), + ref1(token, ':'), + ref0(type)) + .plusSeparated(ref1(token, ';')); + + Parser fieldListCase() => seq5( + ref1(token, 'case'), + seq2(ref0(identifier), ref1(token, ':')).optional(), + ref0(identifier), + ref1(token, 'of'), + seq5(ref0(constant).plusSeparated(ref1(token, ',')), ref1(token, ':'), + ref1(token, '('), ref0(fieldList), ref1(token, ')')) + .plusSeparated(ref1(token, ';'))); + + // region custom helpers + Parser spacer() => [ + whitespace(), + comment(), + ].toChoiceParser().plus(); + + Parser comment() => seq3( + string('(*'), + [ref0(comment), any()].toChoiceParser().starLazy(string('*)')), + string('*)'), + ); + + final _keywords = {}; + + Parser token(Object source) { + if (source is String) { + final message = '"$source" expected'; + if (_isKeyword.accept(source)) { + _keywords.add(source); + return token(source + .toParser(caseInsensitive: true, message: message) + .skip(after: word().not())); + } else { + return token(source.toParser(message: message)); + } + } else if (source is Parser) { + return source.trim(ref0(spacer)); + } else { + throw ArgumentError('Unknown token type: $source.'); + } + } +} + +final _isKeyword = word().plusString().end(); diff --git a/test/all_test.dart b/test/all_test.dart index 7497f27..072ecd2 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -5,6 +5,7 @@ import 'dart_test.dart' as dart_test; import 'json_test.dart' as json_test; import 'lisp_test.dart' as lisp_test; import 'math_test.dart' as math_test; +import 'pascal_test.dart' as pascal_test; import 'prolog_test.dart' as prolog_test; import 'regexp_test.dart' as regexp_test; import 'smalltalk_test.dart' as smalltalk_test; @@ -16,6 +17,7 @@ void main() { group('json', json_test.main); group('lisp', lisp_test.main); group('math', math_test.main); + group('pascal', pascal_test.main); group('prolog', prolog_test.main); group('regexp', regexp_test.main); group('smalltalk', smalltalk_test.main); diff --git a/test/pascal_test.dart b/test/pascal_test.dart new file mode 100644 index 0000000..34193ac --- /dev/null +++ b/test/pascal_test.dart @@ -0,0 +1,331 @@ +import 'package:petitparser/petitparser.dart'; +import 'package:petitparser/reflection.dart'; +import 'package:petitparser_examples/pascal.dart'; +import 'package:test/test.dart'; + +final grammar = PascalGrammarDefinition(); +final parser = grammar.build(); + +TypeMatcher isSuccess(String input, {dynamic value = anything}) => + isA().having( + (parser) => parser.parse(input), + 'parse', + isA().having( + (success) => success.value is Token && value is! Token + ? success.value.value + : success.value, + 'value', + value)); + +void main() { + group('productions', () { + test('program', () { + final parser = grammar.buildFrom(grammar.program()).end(); + expect(parser, isSuccess('program foo; begin end.')); + expect(parser, isSuccess('program foo(a); begin end.')); + expect(parser, isSuccess('program foo(a, b); begin end.')); + }); + test('statement', () { + final parser = grammar.buildFrom(grammar.statement()).end(); + expect(parser, isSuccess('foo')); + expect(parser, isSuccess('foo(1)')); + expect(parser, isSuccess('123: a := 1')); + expect(parser, isSuccess('123: a(1, 2)')); + }); + test('statement assign', () { + final parser = grammar.buildFrom(grammar.statementAssign()).end(); + expect(parser, isSuccess('a := 1')); + expect(parser, isSuccess('a := b')); + expect(parser, isSuccess('a := b + 1')); + }); + test('statement call', () { + final parser = grammar.buildFrom(grammar.statementCall()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('a(1)')); + expect(parser, isSuccess('a(1, 2)')); + }); + test('statement block', () { + final parser = grammar.buildFrom(grammar.statementBlock()).end(); + expect(parser, isSuccess('begin foo end')); + expect(parser, isSuccess('begin foo; bar end')); + }); + test('statement if', () { + final parser = grammar.buildFrom(grammar.statementIf()).end(); + expect(parser, isSuccess('if a then foo')); + expect(parser, isSuccess('if a then foo else bar')); + }); + test('statement repeat', () { + final parser = grammar.buildFrom(grammar.statementRepeat()).end(); + expect(parser, isSuccess('repeat foo until a')); + expect(parser, isSuccess('repeat foo; bar until a')); + }); + test('statement while', () { + final parser = grammar.buildFrom(grammar.statementWhile()).end(); + expect(parser, isSuccess('while a do foo')); + }); + test('statement for', () { + final parser = grammar.buildFrom(grammar.statementFor()).end(); + expect(parser, isSuccess('for i := a to b do foo')); + expect(parser, isSuccess('for i := a downto b do foo')); + }); + test('statement case', () { + final parser = grammar.buildFrom(grammar.statementCase()).end(); + expect(parser, isSuccess('case a of 1: foo end')); + expect(parser, isSuccess('case a of 1, 2: foo end')); + expect(parser, isSuccess('case a of 1: foo; 2: bar end')); + }); + test('statement with', () { + final parser = grammar.buildFrom(grammar.statementWith()).end(); + expect(parser, isSuccess('with a do a := 1')); + expect(parser, isSuccess('with a, b do a := 1')); + }); + test('statement goto', () { + final parser = grammar.buildFrom(grammar.statementGoto()).end(); + expect(parser, isSuccess('goto 1')); + }); + test('statement exit', () { + final parser = grammar.buildFrom(grammar.statementExit()).end(); + expect(parser, isSuccess('exit(program)')); + expect(parser, isSuccess('exit(foo)')); + }); + test('block', () { + final parser = grammar.buildFrom(grammar.block()).end(); + expect(parser, isSuccess('begin end')); + expect(parser, isSuccess('label 1; begin end')); + expect(parser, isSuccess('const a = 1; begin end')); + expect(parser, isSuccess('type a = b; begin end')); + expect(parser, isSuccess('var a: b; begin end')); + expect(parser, isSuccess('procedure foo; begin end; begin end')); + expect(parser, isSuccess('function foo: a; begin end; begin end')); + }); + test('block label', () { + final parser = grammar.buildFrom(grammar.blockLabel()).end(); + expect(parser, isSuccess('label 1;')); + expect(parser, isSuccess('label 1, 2;')); + }); + test('block const', () { + final parser = grammar.buildFrom(grammar.blockConst()).end(); + expect(parser, isSuccess('const a = 1;')); + expect(parser, isSuccess('const a = 1; b = 2;')); + }); + test('block type', () { + final parser = grammar.buildFrom(grammar.blockType()).end(); + expect(parser, isSuccess('type a = b;')); + expect(parser, isSuccess('type a = b; c = d;')); + }); + test('block var', () { + final parser = grammar.buildFrom(grammar.blockVar()).end(); + expect(parser, isSuccess('var a: b;')); + expect(parser, isSuccess('var a, b: c;')); + expect(parser, isSuccess('var a: b; c: d;')); + }); + test('block procedure', () { + final parser = grammar.buildFrom(grammar.blockProcedure()).end(); + expect(parser, isSuccess('procedure foo; begin end;')); + expect(parser, isSuccess('procedure foo(a: b); begin end;')); + expect(parser, isSuccess('procedure foo(a: b); var a: b; begin end;')); + }); + test('block function', () { + final parser = grammar.buildFrom(grammar.blockFunction()).end(); + expect(parser, isSuccess('function foo: a; begin end;')); + expect(parser, isSuccess('function foo(a: b): c; begin end;')); + expect(parser, isSuccess('function foo(a: b): c; var a: b; begin end;')); + }); + test('block statement', () { + final parser = grammar.buildFrom(grammar.blockStatement()).end(); + expect(parser, isSuccess('begin end')); + expect(parser, isSuccess('begin foo end')); + expect(parser, isSuccess('begin foo; bar end')); + }); + test('type', () { + final parser = grammar.buildFrom(grammar.type()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('^a')); + expect(parser, isSuccess('packed set of a')); + expect(parser, isSuccess('packed array [a] of b')); + expect(parser, isSuccess('packed record a: b end')); + expect(parser, isSuccess('packed file')); + }); + test('type pointer', () { + final parser = grammar.buildFrom(grammar.typePointer()).end(); + expect(parser, isSuccess('^a')); + }); + test('type set', () { + final parser = grammar.buildFrom(grammar.typeSet()).end(); + expect(parser, isSuccess('set of a')); + }); + test('type array', () { + final parser = grammar.buildFrom(grammar.typeArray()).end(); + expect(parser, isSuccess('array [a] of b')); + expect(parser, isSuccess('array [a, b] of c')); + }); + test('type record', () { + final parser = grammar.buildFrom(grammar.typeRecord()).end(); + expect(parser, isSuccess('record a: b end')); + expect(parser, isSuccess('record a, b: c end')); + expect(parser, isSuccess('record case a of 1: (b: c) end')); + expect(parser, isSuccess('record case a: b of 1: (a: b) end')); + expect(parser, isSuccess('record case a of 1, 2: (a: b) end')); + expect(parser, isSuccess('record case a of 1: (b: c); 2: (d: e) end')); + }); + test('type file', () { + final parser = grammar.buildFrom(grammar.typeFile()).end(); + expect(parser, isSuccess('file')); + expect(parser, isSuccess('file of a')); + }); + test('identifier', () { + final parser = grammar.buildFrom(grammar.identifier()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('abc')); + expect(parser, isSuccess('a123')); + }); + test('variable', () { + final parser = grammar.buildFrom(grammar.variable()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('a[1]')); + expect(parser, isSuccess('a[1,2]')); + expect(parser, isSuccess('a[1][2]')); + expect(parser, isSuccess('a.b')); + expect(parser, isSuccess('a.b.c')); + expect(parser, isSuccess('a^')); + expect(parser, isSuccess('a^^')); + }); + test('unsigned number', () { + final parser = grammar.buildFrom(grammar.unsignedNumber()).end(); + expect(parser, isSuccess('0', value: 0)); + expect(parser, isSuccess('123', value: 123)); + expect(parser, isSuccess('123.456', value: 123.456)); + expect(parser, isSuccess('123.456e7', value: 123.456e7)); + expect(parser, isSuccess('123.456e+7', value: 123.456e+7)); + expect(parser, isSuccess('123e-4', value: 123e-4)); + }); + test('string literal', () { + final parser = grammar.buildFrom(grammar.stringLiteral()).end(); + expect(parser, isSuccess("''", value: "''")); + expect(parser, isSuccess("'whatever'", value: "'whatever'")); + }); + test('expression', () { + final parser = grammar.buildFrom(grammar.expression()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('a = b')); + expect(parser, isSuccess('1 in b')); + }); + test('simple expression', () { + final parser = grammar.buildFrom(grammar.simpleExpression()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('+ a')); + expect(parser, isSuccess('- a')); + expect(parser, isSuccess('a + b')); + expect(parser, isSuccess('a - b - c')); + expect(parser, isSuccess('a or b')); + expect(parser, isSuccess('a or b or c')); + }); + test('term', () { + final parser = grammar.buildFrom(grammar.term()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('a * b')); + expect(parser, isSuccess('a mod b')); + expect(parser, isSuccess('a * b / c')); + expect(parser, isSuccess('a and b and c')); + }); + test('factor', () { + final parser = grammar.buildFrom(grammar.factor()).end(); + expect(parser, isSuccess('1')); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('sin(a)')); + expect(parser, isSuccess('arctan(a, b)')); + expect(parser, isSuccess('not a')); + expect(parser, isSuccess('[]')); + expect(parser, isSuccess('[1]')); + expect(parser, isSuccess('[1, 2]')); + expect(parser, isSuccess('[1..2]')); + expect(parser, isSuccess('[1..2, 3..4]')); + }); + test('unsigned constant', () { + final parser = grammar.buildFrom(grammar.unsignedConstant()).end(); + expect(parser, isSuccess('1')); + expect(parser, isSuccess('a')); + expect(parser, isSuccess("''")); + expect(parser, isSuccess('nil')); + }); + test('parameter list', () { + final parser = grammar.buildFrom(grammar.parameterList()).end(); + expect(parser, isSuccess('')); + expect(parser, isSuccess('(a: b)')); + expect(parser, isSuccess('(a: b; c: d)')); + expect(parser, isSuccess('(a, b: c)')); + expect(parser, isSuccess('(var a: b)')); + expect(parser, isSuccess('(var a: b; var c: d)')); + expect(parser, isSuccess('(var a, b: c)')); + }); + test('unsigned integer', () { + final parser = grammar.buildFrom(grammar.unsignedInteger()).end(); + expect(parser, isSuccess('0', value: 0)); + expect(parser, isSuccess('123', value: 123)); + expect(parser, isSuccess('12345', value: 12345)); + }); + test('constant', () { + final parser = grammar.buildFrom(grammar.constant()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('+b')); + expect(parser, isSuccess('-c')); + expect(parser, isSuccess('1')); + expect(parser, isSuccess('+2')); + expect(parser, isSuccess('-3')); + expect(parser, isSuccess('a')); + expect(parser, isSuccess("'hello'")); + expect(parser, isSuccess('nil')); + }); + test('simple type', () { + final parser = grammar.buildFrom(grammar.simpleType()).end(); + expect(parser, isSuccess('a')); + expect(parser, isSuccess('(a)')); + expect(parser, isSuccess('(a, b)')); + expect(parser, isSuccess('a..b')); + }); + test('field list', () { + final parser = grammar.buildFrom(grammar.fieldList()).end(); + expect(parser, isSuccess('a: b')); + expect(parser, isSuccess('a, b: c')); + expect(parser, isSuccess('case a of b : (c: d)')); + expect(parser, isSuccess('case a : b of c : (d: e)')); + expect(parser, isSuccess('case a of b, c : (d: e)')); + expect(parser, isSuccess('case a of b : (c: d); e : (f: g)')); + expect(parser, isSuccess('a: b case c of d : (e: f)')); + }); + }); + group('grammar', () { + test('hello world', () { + expect( + parser, + isSuccess([ + "program simple;", + "begin", + " writeln('Hello World!');", + "end.", + ].join('\n'))); + }); + test('comparestrings', () { + expect( + parser, + isSuccess([ + "program comparestrings;", + "var s: string;", + " t: string;" + "begin", + " s := 'something';", + " t := 'something bigger';", + " if s = t then", + " writeln(s, ' is equal to ', t)" + " else", + " if s > t then", + " writeln(s, ' is greater than ', t)", + " else", + " if s < t then", + " writeln(s, ' is less than ', t);", + "end.", + ].join('\n'))); + }); + test('linter', () => expect(linter(parser), isEmpty)); + }); +}