diff --git a/core/ast/json_serializer.py b/core/ast/json_serializer.py new file mode 100644 index 0000000..0c3d773 --- /dev/null +++ b/core/ast/json_serializer.py @@ -0,0 +1,81 @@ +from core.ast.midas import ( + ConstraintExpr, + ConstraintStmt, + Expr, + LiteralExpr, + OpStmt, + PropertyStmt, + Stmt, + TypeBodyExpr, + TypeExpr, + TypeStmt, + WildcardExpr, +) + + +class AstJsonSerializer(Stmt.Visitor[dict], Expr.Visitor[dict]): + """An AST serializer which produces a JSON-compatible structure""" + + def serialize(self, stmts: list[Stmt]) -> list[dict]: + return [stmt.accept(self) for stmt in stmts] + + def visit_type_stmt(self, stmt: TypeStmt) -> dict: + return { + "_type": "TypeStmt", + "name": stmt.name.lexeme, + "bases": [base.accept(self) for base in stmt.bases], + "body": stmt.body.accept(self) if stmt.body is not None else None, + } + + def visit_type_expr(self, expr: TypeExpr) -> dict: + return { + "_type": "TypeExpr", + "name": expr.name.lexeme, + "constraints": [constraint.accept(self) for constraint in expr.constraints], + } + + def visit_constraint_expr(self, expr: ConstraintExpr) -> dict: + return { + "_type": "ConstraintExpr", + "left": expr.left.accept(self), + "op": expr.op.lexeme, + "right": expr.right.accept(self), + } + + def visit_wildcard_expr(self, expr: WildcardExpr) -> dict: + return {"_type": "WildcardExpr"} + + def visit_literal_expr(self, expr: LiteralExpr) -> dict: + return { + "_type": "LiteralExpr", + "value": expr.value, + } + + def visit_type_body_expr(self, expr: TypeBodyExpr) -> dict: + return { + "_type": "TypeBodyExpr", + "properties": [prop.accept(self) for prop in expr.properties], + } + + def visit_property_stmt(self, stmt: PropertyStmt) -> dict: + return { + "_type": "PropertyStmt", + "name": stmt.name.lexeme, + "type": stmt.type.accept(self), + } + + def visit_op_stmt(self, stmt: OpStmt) -> dict: + return { + "_type": "OpStmt", + "left": stmt.left.accept(self), + "op": stmt.op.lexeme, + "right": stmt.right.accept(self), + "result": stmt.result.accept(self), + } + + def visit_constraint_stmt(self, stmt: ConstraintStmt) -> dict: + return { + "_type": "ConstraintStmt", + "name": stmt.name.lexeme, + "constraint": stmt.constraint.accept(self), + } diff --git a/lexer/base.py b/lexer/base.py index 1104e7a..f6f357d 100644 --- a/lexer/base.py +++ b/lexer/base.py @@ -5,6 +5,13 @@ from lexer.position import Position from lexer.token import Token, TokenType +class MidasSyntaxError(Exception): + def __init__(self, pos: Position, message: str): + super().__init__(f"[ERROR] Error at {pos}: {message}") + self.pos: Position = pos + self.message: str = message + + class Lexer(ABC): """An abstract lexer which provides methods to easily extend it into a concrete one @@ -38,9 +45,9 @@ class Lexer(ABC): msg (str): the error message Raises: - SyntaxError + MidasSyntaxError """ - raise SyntaxError(f"[ERROR] Error at {self.start_pos}: {msg}") + raise MidasSyntaxError(self.start_pos, msg) def process(self) -> list[Token]: """Scan tokens out of the source text @@ -49,7 +56,7 @@ class Lexer(ABC): list[Token]: all the tokens that could be scanned Raises: - SyntaxError: if a syntax error is found + MidasSyntaxError: if a syntax error is found """ self.scan_tokens() self.tokens.append(Token(TokenType.EOF, "", None, self.get_position())) diff --git a/tester.py b/tester.py new file mode 100644 index 0000000..597ddee --- /dev/null +++ b/tester.py @@ -0,0 +1,204 @@ +from __future__ import annotations + +import argparse +import difflib +import json +import sys +from dataclasses import asdict, dataclass, field +from pathlib import Path +from typing import Iterator, Optional + +from core.ast.json_serializer import AstJsonSerializer +from core.ast.midas import Stmt +from lexer.base import MidasSyntaxError +from lexer.midas import MidasLexer +from lexer.token import Token +from parser.midas import MidasParser + +DEFAULT_BASE_DIR: Path = Path() / "tests" + + +@dataclass +class CaseResult: + tokens: Optional[list[dict]] = None + stmts: Optional[list[dict]] = None + errors: list[dict] = field(default_factory=list) + + def dumps(self) -> str: + return json.dumps(asdict(self), indent=2) + + +class Tester: + """A test runner to check for regressions in the lexer and parser""" + + def __init__(self, base_dir: Path): + self.base_dir: Path = base_dir + + def _list_tests(self) -> list[Path]: + return list(self.base_dir.rglob("*.midas")) + + def run_all_tests(self) -> bool: + paths: list[Path] = self._list_tests() + return self.run_tests(paths) + + def run_tests(self, tests: list[Path]) -> bool: + rule: str = "-" * 80 + n: int = len(tests) + successes: int = 0 + failures: int = 0 + + print(rule) + for i, test in enumerate(tests): + print(f"Case {i+1}/{n}: {test}") + success: bool = self._run_test(test) + if success: + successes += 1 + else: + failures += 1 + + print(rule) + print(f"Success: {successes}/{n}") + print(f"Failed: {failures}/{n}") + print(rule) + return failures == 0 + + def _run_test(self, path: Path) -> bool: + result: CaseResult = self._exec_case(path) + result_path: Path = self._result_path(path) + expected: str = result_path.read_text() + actual: str = result.dumps() + + if expected == actual: + return True + + diff = difflib.unified_diff( + expected.splitlines(keepends=True), + actual.splitlines(keepends=True), + fromfile="Snapshot", + tofile="Result", + ) + self._print_diff(diff) + return False + + def _exec_case(self, path: Path) -> CaseResult: + if not path.exists(): + raise FileNotFoundError(f"Could not find test '{path}'") + if not path.is_file(): + raise TypeError(f"Test '{path}' is not a file") + + result: CaseResult = CaseResult() + content: str = path.read_text() + lexer: MidasLexer = MidasLexer(content) + tokens: list[Token] = [] + try: + tokens = lexer.process() + result.tokens = [ + { + "type": token.type.name, + "lexeme": token.lexeme, + "line": token.position.line, + "column": token.position.column, + } + for token in tokens + ] + except MidasSyntaxError as e: + result.errors.append( + { + "type": "SyntaxError", + "line": e.pos.line, + "column": e.pos.column, + "message": e.message, + } + ) + return result + + parser: MidasParser = MidasParser(tokens) + stmts: list[Stmt] = parser.parse() + result.stmts = AstJsonSerializer().serialize(stmts) + result.errors.extend( + [ + { + "line": e.token.position.line, + "column": e.token.position.column, + "message": e.message, + } + for e in parser.errors + ] + ) + return result + + def update_all_tests(self): + paths: list[Path] = self._list_tests() + return self.update_tests(paths) + + def update_tests(self, tests: list[Path]): + updated: int = 0 + for test in tests: + if self._update_test(test): + updated += 1 + print(f"Updated {updated}/{len(tests)} tests") + + def _update_test(self, path: Path) -> bool: + result: CaseResult = self._exec_case(path) + result_path: Path = self._result_path(path) + current: str = result_path.read_text() + new: str = result.dumps() + if current == new: + return False + result_path.write_text(new) + return True + + def _result_path(self, test_path: Path) -> Path: + return test_path.parent / (test_path.name + ".ref.json") + + def _print_diff(self, diff: Iterator[str]): + for line in diff: + if line.startswith("+") and not line.startswith("+++"): + print(f"\033[92m{line}\033[0m", end="") + elif line.startswith("-") and not line.startswith("---"): + print(f"\033[91m{line}\033[0m", end="") + else: + print(line, end="") + print() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-D", + "--base-dir", + help="Base directory containing test files", + type=Path, + default=DEFAULT_BASE_DIR, + ) + subparsers = parser.add_subparsers(dest="subcommand") + + update = subparsers.add_parser("update") + update.add_argument("-a", "--all", action="store_true") + update.add_argument("FILE", type=Path, nargs="*") + + run = subparsers.add_parser("run") + run.add_argument("-a", "--all", action="store_true") + run.add_argument("FILE", type=Path, nargs="*") + args = parser.parse_args() + + tester: Tester = Tester(args.base_dir) + + match args.subcommand: + case "update": + if args.all: + tester.update_all_tests() + else: + tester.update_tests(args.FILE) + case "run": + success: bool + if args.all: + success = tester.run_all_tests() + else: + success = tester.run_tests(args.FILE) + if not success: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/tests/cases/parser/01_simple_types.midas b/tests/cases/parser/01_simple_types.midas new file mode 100644 index 0000000..017e40c --- /dev/null +++ b/tests/cases/parser/01_simple_types.midas @@ -0,0 +1,24 @@ +// Simple custom type derived from floats +type Latitude +type Longitude + +// Complex custom type, containing two values accessible through properties +type GeoLocation { + lat: Latitude + lon: Longitude +} + +type LatitudeDiff +type LongitudeDiff + +// Simple operation defined on our custom types +op - = +op - = + +// Simple custom type with a constraint +type Age + +// Predefined custom constraints that can be referenced in other definitions +constraint Positive = _ >= 0 +constraint StrictlyPositive = _ > 0 +//constraint Even = _ % 2 == 0 \ No newline at end of file diff --git a/tests/cases/parser/01_simple_types.midas.ref.json b/tests/cases/parser/01_simple_types.midas.ref.json new file mode 100644 index 0000000..0697e32 --- /dev/null +++ b/tests/cases/parser/01_simple_types.midas.ref.json @@ -0,0 +1,1145 @@ +{ + "tokens": [ + { + "type": "COMMENT", + "lexeme": "// Simple custom type derived from floats", + "line": 1, + "column": 1 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 1, + "column": 42 + }, + { + "type": "TYPE", + "lexeme": "type", + "line": 2, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 2, + "column": 5 + }, + { + "type": "IDENTIFIER", + "lexeme": "Latitude", + "line": 2, + "column": 6 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 2, + "column": 14 + }, + { + "type": "IDENTIFIER", + "lexeme": "float", + "line": 2, + "column": 15 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 2, + "column": 20 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 2, + "column": 21 + }, + { + "type": "TYPE", + "lexeme": "type", + "line": 3, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 3, + "column": 5 + }, + { + "type": "IDENTIFIER", + "lexeme": "Longitude", + "line": 3, + "column": 6 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 3, + "column": 15 + }, + { + "type": "IDENTIFIER", + "lexeme": "float", + "line": 3, + "column": 16 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 3, + "column": 21 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 3, + "column": 22 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 4, + "column": 1 + }, + { + "type": "COMMENT", + "lexeme": "// Complex custom type, containing two values accessible through properties", + "line": 5, + "column": 1 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 5, + "column": 76 + }, + { + "type": "TYPE", + "lexeme": "type", + "line": 6, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 6, + "column": 5 + }, + { + "type": "IDENTIFIER", + "lexeme": "GeoLocation", + "line": 6, + "column": 6 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 6, + "column": 17 + }, + { + "type": "IDENTIFIER", + "lexeme": "Latitude", + "line": 6, + "column": 18 + }, + { + "type": "COMMA", + "lexeme": ",", + "line": 6, + "column": 26 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 6, + "column": 27 + }, + { + "type": "IDENTIFIER", + "lexeme": "Longitude", + "line": 6, + "column": 28 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 6, + "column": 37 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 6, + "column": 38 + }, + { + "type": "LEFT_BRACE", + "lexeme": "{", + "line": 6, + "column": 39 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 6, + "column": 40 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 7, + "column": 1 + }, + { + "type": "IDENTIFIER", + "lexeme": "lat", + "line": 7, + "column": 5 + }, + { + "type": "COLON", + "lexeme": ":", + "line": 7, + "column": 8 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 7, + "column": 9 + }, + { + "type": "IDENTIFIER", + "lexeme": "Latitude", + "line": 7, + "column": 10 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 7, + "column": 18 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 8, + "column": 1 + }, + { + "type": "IDENTIFIER", + "lexeme": "lon", + "line": 8, + "column": 5 + }, + { + "type": "COLON", + "lexeme": ":", + "line": 8, + "column": 8 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 8, + "column": 9 + }, + { + "type": "IDENTIFIER", + "lexeme": "Longitude", + "line": 8, + "column": 10 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 8, + "column": 19 + }, + { + "type": "RIGHT_BRACE", + "lexeme": "}", + "line": 9, + "column": 1 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 9, + "column": 2 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 10, + "column": 1 + }, + { + "type": "TYPE", + "lexeme": "type", + "line": 11, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 11, + "column": 5 + }, + { + "type": "IDENTIFIER", + "lexeme": "LatitudeDiff", + "line": 11, + "column": 6 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 11, + "column": 18 + }, + { + "type": "IDENTIFIER", + "lexeme": "float", + "line": 11, + "column": 19 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 11, + "column": 24 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 11, + "column": 25 + }, + { + "type": "TYPE", + "lexeme": "type", + "line": 12, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 12, + "column": 5 + }, + { + "type": "IDENTIFIER", + "lexeme": "LongitudeDiff", + "line": 12, + "column": 6 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 12, + "column": 19 + }, + { + "type": "IDENTIFIER", + "lexeme": "float", + "line": 12, + "column": 20 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 12, + "column": 25 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 12, + "column": 26 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 13, + "column": 1 + }, + { + "type": "COMMENT", + "lexeme": "// Simple operation defined on our custom types", + "line": 14, + "column": 1 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 14, + "column": 48 + }, + { + "type": "OP", + "lexeme": "op", + "line": 15, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 15, + "column": 3 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 15, + "column": 4 + }, + { + "type": "IDENTIFIER", + "lexeme": "Latitude", + "line": 15, + "column": 5 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 15, + "column": 13 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 15, + "column": 14 + }, + { + "type": "MINUS", + "lexeme": "-", + "line": 15, + "column": 15 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 15, + "column": 16 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 15, + "column": 17 + }, + { + "type": "IDENTIFIER", + "lexeme": "Latitude", + "line": 15, + "column": 18 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 15, + "column": 26 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 15, + "column": 27 + }, + { + "type": "EQUAL", + "lexeme": "=", + "line": 15, + "column": 28 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 15, + "column": 29 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 15, + "column": 30 + }, + { + "type": "IDENTIFIER", + "lexeme": "LatitudeDiff", + "line": 15, + "column": 31 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 15, + "column": 43 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 15, + "column": 44 + }, + { + "type": "OP", + "lexeme": "op", + "line": 16, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 16, + "column": 3 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 16, + "column": 4 + }, + { + "type": "IDENTIFIER", + "lexeme": "Longitude", + "line": 16, + "column": 5 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 16, + "column": 14 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 16, + "column": 15 + }, + { + "type": "MINUS", + "lexeme": "-", + "line": 16, + "column": 16 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 16, + "column": 17 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 16, + "column": 18 + }, + { + "type": "IDENTIFIER", + "lexeme": "Longitude", + "line": 16, + "column": 19 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 16, + "column": 28 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 16, + "column": 29 + }, + { + "type": "EQUAL", + "lexeme": "=", + "line": 16, + "column": 30 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 16, + "column": 31 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 16, + "column": 32 + }, + { + "type": "IDENTIFIER", + "lexeme": "LongitudeDiff", + "line": 16, + "column": 33 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 16, + "column": 46 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 16, + "column": 47 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 17, + "column": 1 + }, + { + "type": "COMMENT", + "lexeme": "// Simple custom type with a constraint", + "line": 18, + "column": 1 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 18, + "column": 40 + }, + { + "type": "TYPE", + "lexeme": "type", + "line": 19, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 5 + }, + { + "type": "IDENTIFIER", + "lexeme": "Age", + "line": 19, + "column": 6 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 19, + "column": 9 + }, + { + "type": "IDENTIFIER", + "lexeme": "int", + "line": 19, + "column": 10 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 13 + }, + { + "type": "PLUS", + "lexeme": "+", + "line": 19, + "column": 14 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 15 + }, + { + "type": "LEFT_PAREN", + "lexeme": "(", + "line": 19, + "column": 16 + }, + { + "type": "NUMBER", + "lexeme": "0", + "line": 19, + "column": 17 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 18 + }, + { + "type": "LESS_EQUAL", + "lexeme": "<=", + "line": 19, + "column": 19 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 21 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 19, + "column": 22 + }, + { + "type": "RIGHT_PAREN", + "lexeme": ")", + "line": 19, + "column": 23 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 24 + }, + { + "type": "PLUS", + "lexeme": "+", + "line": 19, + "column": 25 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 26 + }, + { + "type": "LEFT_PAREN", + "lexeme": "(", + "line": 19, + "column": 27 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 19, + "column": 28 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 29 + }, + { + "type": "LESS", + "lexeme": "<", + "line": 19, + "column": 30 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 19, + "column": 31 + }, + { + "type": "NUMBER", + "lexeme": "150", + "line": 19, + "column": 32 + }, + { + "type": "RIGHT_PAREN", + "lexeme": ")", + "line": 19, + "column": 35 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 19, + "column": 36 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 19, + "column": 37 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 20, + "column": 1 + }, + { + "type": "COMMENT", + "lexeme": "// Predefined custom constraints that can be referenced in other definitions", + "line": 21, + "column": 1 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 21, + "column": 77 + }, + { + "type": "CONSTRAINT", + "lexeme": "constraint", + "line": 22, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 22, + "column": 11 + }, + { + "type": "IDENTIFIER", + "lexeme": "Positive", + "line": 22, + "column": 12 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 22, + "column": 20 + }, + { + "type": "EQUAL", + "lexeme": "=", + "line": 22, + "column": 21 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 22, + "column": 22 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 22, + "column": 23 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 22, + "column": 24 + }, + { + "type": "GREATER_EQUAL", + "lexeme": ">=", + "line": 22, + "column": 25 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 22, + "column": 27 + }, + { + "type": "NUMBER", + "lexeme": "0", + "line": 22, + "column": 28 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 22, + "column": 29 + }, + { + "type": "CONSTRAINT", + "lexeme": "constraint", + "line": 23, + "column": 1 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 23, + "column": 11 + }, + { + "type": "IDENTIFIER", + "lexeme": "StrictlyPositive", + "line": 23, + "column": 12 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 23, + "column": 28 + }, + { + "type": "EQUAL", + "lexeme": "=", + "line": 23, + "column": 29 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 23, + "column": 30 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 23, + "column": 31 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 23, + "column": 32 + }, + { + "type": "GREATER", + "lexeme": ">", + "line": 23, + "column": 33 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 23, + "column": 34 + }, + { + "type": "NUMBER", + "lexeme": "0", + "line": 23, + "column": 35 + }, + { + "type": "NEWLINE", + "lexeme": "\n", + "line": 23, + "column": 36 + }, + { + "type": "COMMENT", + "lexeme": "//constraint Even = _ % 2 == 0", + "line": 24, + "column": 1 + }, + { + "type": "EOF", + "lexeme": "", + "line": 24, + "column": 31 + } + ], + "stmts": [ + { + "_type": "TypeStmt", + "name": "Latitude", + "bases": [ + { + "_type": "TypeExpr", + "name": "float", + "constraints": [] + } + ], + "body": null + }, + { + "_type": "TypeStmt", + "name": "Longitude", + "bases": [ + { + "_type": "TypeExpr", + "name": "float", + "constraints": [] + } + ], + "body": null + }, + { + "_type": "TypeStmt", + "name": "GeoLocation", + "bases": [ + { + "_type": "TypeExpr", + "name": "Latitude", + "constraints": [] + }, + { + "_type": "TypeExpr", + "name": "Longitude", + "constraints": [] + } + ], + "body": { + "_type": "TypeBodyExpr", + "properties": [ + { + "_type": "PropertyStmt", + "name": "lat", + "type": { + "_type": "TypeExpr", + "name": "Latitude", + "constraints": [] + } + }, + { + "_type": "PropertyStmt", + "name": "lon", + "type": { + "_type": "TypeExpr", + "name": "Longitude", + "constraints": [] + } + } + ] + } + }, + { + "_type": "TypeStmt", + "name": "LatitudeDiff", + "bases": [ + { + "_type": "TypeExpr", + "name": "float", + "constraints": [] + } + ], + "body": null + }, + { + "_type": "TypeStmt", + "name": "LongitudeDiff", + "bases": [ + { + "_type": "TypeExpr", + "name": "float", + "constraints": [] + } + ], + "body": null + }, + { + "_type": "OpStmt", + "left": { + "_type": "TypeExpr", + "name": "Latitude", + "constraints": [] + }, + "op": "-", + "right": { + "_type": "TypeExpr", + "name": "Latitude", + "constraints": [] + }, + "result": { + "_type": "TypeExpr", + "name": "LatitudeDiff", + "constraints": [] + } + }, + { + "_type": "OpStmt", + "left": { + "_type": "TypeExpr", + "name": "Longitude", + "constraints": [] + }, + "op": "-", + "right": { + "_type": "TypeExpr", + "name": "Longitude", + "constraints": [] + }, + "result": { + "_type": "TypeExpr", + "name": "LongitudeDiff", + "constraints": [] + } + }, + { + "_type": "TypeStmt", + "name": "Age", + "bases": [ + { + "_type": "TypeExpr", + "name": "int", + "constraints": [ + { + "_type": "ConstraintExpr", + "left": { + "_type": "LiteralExpr", + "value": 0.0 + }, + "op": "<=", + "right": { + "_type": "WildcardExpr" + } + }, + { + "_type": "ConstraintExpr", + "left": { + "_type": "WildcardExpr" + }, + "op": "<", + "right": { + "_type": "LiteralExpr", + "value": 150.0 + } + } + ] + } + ], + "body": null + }, + { + "_type": "ConstraintStmt", + "name": "Positive", + "constraint": { + "_type": "ConstraintExpr", + "left": { + "_type": "WildcardExpr" + }, + "op": ">=", + "right": { + "_type": "LiteralExpr", + "value": 0.0 + } + } + }, + { + "_type": "ConstraintStmt", + "name": "StrictlyPositive", + "constraint": { + "_type": "ConstraintExpr", + "left": { + "_type": "WildcardExpr" + }, + "op": ">", + "right": { + "_type": "LiteralExpr", + "value": 0.0 + } + } + } + ], + "errors": [] +} \ No newline at end of file