From 5c49f83da79b52bbc7a37903e6a6500e2db1a3df Mon Sep 17 00:00:00 2001 From: ImShyMike <122023566+ImShyMike@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:49:59 +0000 Subject: [PATCH] logo + favicon + parser error handling + --- README.md | 10 ++--- assets/eryx.png | Bin 0 -> 4372 bytes assets/eryx_small.png | Bin 0 -> 3298 bytes eryx/__main__.py | 20 ++++++---- eryx/frontend/lexer.py | 57 +++++++++------------------ eryx/frontend/parser.py | 28 +++++++++---- eryx/playground/playground.py | 8 +++- eryx/playground/static/eryx.ico | Bin 0 -> 19630 bytes eryx/playground/templates/index.html | 31 +++++++++------ eryx/runtime/interpreter.py | 3 +- eryx/utils/errors.py | 42 ++++++++++++++++++++ 11 files changed, 124 insertions(+), 75 deletions(-) create mode 100644 assets/eryx.png create mode 100644 assets/eryx_small.png create mode 100644 eryx/playground/static/eryx.ico create mode 100644 eryx/utils/errors.py diff --git a/README.md b/README.md index 757f1d7..23b60ff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Eryx +# [![Eryx](https://github.com/ImShyMike/Eryx/blob/main/assets/eryx_small.png)][pypi_url] [![Build Status](https://github.com/ImShyMike/Eryx/actions/workflows/python-package.yml/badge.svg)](https://github.com/ImShyMike/Eryx/actions/workflows/python-package.yml) [![License](https://img.shields.io/pypi/l/Eryx)](https://github.com/ImShyMike/Eryx/blob/main/LICENSE) [![PyPI](https://img.shields.io/pypi/v/Eryx)][pypi_url] @@ -31,10 +31,10 @@ python -m eryx ``` The list of subcommands is: -- repl => Start the [REPL](https://wikipedia.org/wiki/REPL) -- run => Run an Eryx file -- playground => Start the web playground -- test => Run the test suite +* **repl**: Start the [REPL](https://wikipedia.org/wiki/REPL) +* **run**: Run an Eryx file +* **playground**: Start the web playground +* **test**: Run the test suite ## Documentation Coming soon... diff --git a/assets/eryx.png b/assets/eryx.png new file mode 100644 index 0000000000000000000000000000000000000000..f21ca1d52f7f92522e05e73ba774feb952eb6bfa GIT binary patch literal 4372 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 E0zTc#vj6}9 literal 0 HcmV?d00001 diff --git a/eryx/__main__.py b/eryx/__main__.py index 9aa6605..b7d879d 100644 --- a/eryx/__main__.py +++ b/eryx/__main__.py @@ -1,6 +1,7 @@ """Eryx entry point and Command Line Interface (CLI) module.""" import argparse +import os import pytest from colorama import init @@ -74,14 +75,17 @@ def main(): if args.command == "repl": start_repl(log_ast=args.ast, log_result=args.result, log_tokens=args.tokenize) elif args.command == "run": - with open(args.filepath, "r", encoding="utf8") as file: - source_code = file.read() - run_code( - source_code, - log_ast=args.ast, - log_result=args.result, - log_tokens=args.tokenize, - ) + try: + with open(args.filepath, "r", encoding="utf8") as file: + source_code = file.read() + run_code( + source_code, + log_ast=args.ast, + log_result=args.result, + log_tokens=args.tokenize, + ) + except Exception as e: # pylint: disable=broad-except + print(f"eryx: can't open file '{args.filepath}': [Errno {e.args[0]}] {e.args[1]}") elif args.command == "playground": start_playground(args.host or "0.0.0.0", port=args.port or 80) elif args.command == "test": diff --git a/eryx/frontend/lexer.py b/eryx/frontend/lexer.py index a08104d..6cb0e88 100644 --- a/eryx/frontend/lexer.py +++ b/eryx/frontend/lexer.py @@ -1,11 +1,12 @@ """Lexer for the fronted.""" -import sys from enum import Enum, auto from typing import Any, Union from colorama import Fore, init +from eryx.utils.errors import syntax_error + init(autoreset=True) @@ -78,38 +79,23 @@ def is_skipable(char: str) -> bool: ) # Skip spaces, newlines, tabs, and carriage returns -def position_to_line_column(source_code: str, position: int) -> tuple[int, int]: - """Convert a position to a line and column number.""" - # Get the substring up to the given position - substring = source_code[:position] - - # Count the number of newline characters to determine the line - line = substring.count("\n") + 1 - - # Find the column by looking for the last newline - last_newline_pos = substring.rfind("\n") - column = position - last_newline_pos if last_newline_pos != -1 else position + 1 - - return (line, column) - - -def get_line_string(source_code: str, line: int) -> str: - """Get the line string from the source code.""" - lines = source_code.split("\n") - - return lines[line - 1] - - def tokenize(source_code: str) -> list[Token]: """Tokenize the source code.""" tokens = [] source_size = len(source_code) src = list(source_code) + comment = False while len(src) > 0: negative_num = False current_pos = source_size - len(src) + if comment: + if src[0] in ("\n", "\r", ";"): + comment = False + src.pop(0) + continue + single_char_tokens = { "(": TokenType.OPEN_PAREN, ")": TokenType.CLOSE_PAREN, @@ -133,6 +119,12 @@ def tokenize(source_code: str) -> list[Token]: tokens.append(Token(token, single_char_tokens[token], current_pos)) continue + # Check for comments + if src[0] == "#": + comment = True + src.pop(0) + continue + # If its not a single character token, check for negative numbers if src[0] == "-": if len(src) > 0 and src[1].isdigit(): @@ -249,22 +241,11 @@ def tokenize(source_code: str) -> list[Token]: else: # If this is reached, its an unknown character - current_line, current_col = position_to_line_column( - source_code, current_pos - ) - line = get_line_string(source_code, current_line) - current_line_str = str(current_line).rjust(3) - print(f"\n{Fore.CYAN}{current_line_str}:{Fore.WHITE} {line}") - print( - Fore.YELLOW - + "^".rjust(current_col + len(current_line_str) + 2) - + Fore.WHITE - ) - print( - f"{Fore.RED}SyntaxError{Fore.WHITE}: Unknown character found in source " - f"'{Fore.MAGENTA}{src.pop(0)}{Fore.WHITE}'" + syntax_error( + source_code, + current_pos, + f"Unknown character found in source '{Fore.MAGENTA}{src.pop(0)}{Fore.WHITE}'", ) - sys.exit(1) tokens.append(Token("EOF", TokenType.EOF, source_size - len(src))) diff --git a/eryx/frontend/parser.py b/eryx/frontend/parser.py index 9596d91..3608180 100644 --- a/eryx/frontend/parser.py +++ b/eryx/frontend/parser.py @@ -21,11 +21,13 @@ ) from eryx.frontend.lexer import Token, TokenType, tokenize +from eryx.utils.errors import syntax_error class Parser: """Parser class.""" def __init__(self) -> None: + self.source_code = "" self.tokens = [] def not_eof(self) -> bool: @@ -48,10 +50,7 @@ def assert_next(self, token_type: TokenType, error: str) -> Token: """Assert that the next token is of a certain type and return it.""" token = self.next() if token.type != token_type: - raise RuntimeError( - f"Parser error on position {token.position}: " - f"\n{error} {token} - Expected: {token_type}" - ) + syntax_error(self.source_code, token.position, error) return token def parse_additive_expression(self) -> Expression: @@ -120,7 +119,11 @@ def parse_member_expression(self) -> Expression: proprty = self.parse_primary_expression() # Identifier if not isinstance(proprty, Identifier): - raise RuntimeError("Expected an identifier as a property.") + syntax_error( + self.source_code, + self.at().position, + "Expected an identifier as a property.", + ) else: computed = True proprty = self.parse_expression() @@ -186,7 +189,7 @@ def parse_primary_expression(self) -> Expression: ) # Skip the close parenthesis return expression case _: - raise RuntimeError(f"Unexpected token: {token}") + syntax_error(self.source_code, token.position, "Unexpected token.") def parse_assignment_expression(self) -> Expression: """Parse an assignment expression.""" @@ -319,7 +322,11 @@ def parse_function_declaration(self) -> Statement: parameters = [] for argument in arguments: if not isinstance(argument, Identifier): - raise RuntimeError("Function arguments must be identifiers.") + syntax_error( + self.source_code, + self.at().position, + "Function arguments must be identifiers.", + ) parameters.append(argument.symbol) self.assert_next(TokenType.OPEN_BRACE, "Expected an opening brace.") @@ -345,7 +352,11 @@ def parse_variable_declaration(self) -> Statement: if self.at().type == TokenType.SEMICOLON: self.next() # Skip the semicolon if is_constant: - raise RuntimeError("Constant declaration must have an initial value.") + syntax_error( + self.source_code, + self.at().position, + "Constant declaration must have an initial value.", + ) return VariableDeclaration(is_constant, Identifier(identifier)) @@ -381,6 +392,7 @@ def parse_statement(self) -> Statement: def produce_ast(self, source_code: str) -> Program: """Produce an abstract syntax tree (AST) from source code.""" + self.source_code = source_code self.tokens = tokenize(source_code) program = Program(body=[]) diff --git a/eryx/playground/playground.py b/eryx/playground/playground.py index b84b5c3..3bd7b99 100644 --- a/eryx/playground/playground.py +++ b/eryx/playground/playground.py @@ -1,4 +1,4 @@ -"""Web UI for Eryx.""" +"""Web playground for Eryx.""" import io import json @@ -208,6 +208,12 @@ def static_route(path): return app.send_static_file(path) +@app.route("/favicon.ico") +def favicon(): + """Serve the favicon.""" + return app.send_static_file("favicon.ico") + + def start_playground(host="0.0.0.0", port=80): """Start the web playground.""" app.run(host=host, port=port, debug=False, use_reloader=False) diff --git a/eryx/playground/static/eryx.ico b/eryx/playground/static/eryx.ico new file mode 100644 index 0000000000000000000000000000000000000000..29f1977fe106eccc17a9b6820da38a5944ba4ad9 GIT binary patch literal 19630 zcmeHMeO#5*9sivN3AiTDN)chU);2XRZH{&~QENESCCf*)45t&exzt?9Y|5B2O|2wM zAzw%V-%wFeL`1{A--`3@nU>(HxQq` zkoe5S#8+%2e(eVF>z@&S>m2bHGKsI=MEqtI@$(mP4_kS-|NJKLlAV~dh_0r9WDCZ4f^c-1Z9|Nn@17W61T01Rlg6LWG3q1kQNI8J;9I4=gz z^rgh#cpExm?q}e$4Eknc-c9&^>r3#w2L8al1)E=>t)P_nR`B=|ecRA}>@;-xg7}A* ziNE_ld@exyv&7$rj^}WH@=fRi4HiP1RVBnsV2xRvQ~g`Jhf&}>4&M6_m#QlGgE@uJqZGMt4SDik#Ao(0;tQbRMc6n2A1aU| z*KfcE@@)y?dI`RtMIL2gZA?dO3&FQ!2ioA*OyurV@ZJxNRu!ApnY@t-NCmzN1y~4* z6~|o56qek|JGWKWvXWWgYK>2Q`Xz$TdB*zpBPQ z<+>H8qRv7xum{9uCUtUkFrR&Bs_18U%Z5}SND0M8i)%0}{ICxESTnw5om>3m1f6}xCZO~d%(|+)A3`cxW>aHrEj^aj(Vc{ zU|{2-2C3?O6Ags~@16cfjSmz{x|fqYdB#^F^(|Oc_lJr`vksu?V4%Z>GJAxo`q|ta zQx5+-Cs|F!1@9T3?Sa&>7FG4%Ys`R8UL~5U?Hi!CvBWfx3Em_2i(DP}4~B?sV%?+v z%0{?9u)n7I?J_MS?W@p0D(e6L{09~r`^a6NE}#63_qOg7}D zI%)$}froM@__0sc@XcbCE{s;~ZgVvWM?#TFA^o%L;UumqQi zK*};iCmLI$x;&=#WW$yN-_F$a)uF4=Z};ENI1imYENdv_53YWw2^fpXLxnw%P0$bB z1=B$wcA;^aIcFQ!JHl5Z%)1MwTZhEMKkw>uH>4w$Vh6)f7BCMDJtinI^Fb`L*6f=2 z=JP*olF@JXUyYmXIzO5IyDw5M3ewIQP<^ViWF&)Mlq+vo2A+$9Mp~J@68~y_`@i}8 zf6mK{uMJaEUb5O5Foja5>-aBsgJUyXk!3UCZ`$DMduuwDp7sieb_$(^jvyc5`NXiT z*BtuSIR9N{$#MRV`M1_RV*G?;uSfg+C)oebI{rjFwhfxqyJ?x_zRVu&R&s1HBj7dG231Chvqzv>p=J*Y>|M;=8kh5kCCm#wX7R|@!zt#@P} zy{~U%uirihs^Q4rLj0^As9rzD)5KB<- z_uYJA4!mbF-ZP{(=S)WFsD>U{rhPW(V$QF?p5IrXOiui8cbS88(=VbvMc<^q>E zQUR%eR6r^q6_5%@1*8H}0jYpgKq?>=kP1izqyi06fGO^Z_;YZBOg3onJ>K`$UD~#D z`wy4~0n{_t$JgSfw7$EZsZG+|#$WsuRjhwc;x~!Ho{dQT#herCGXFehPSf Eryx Playground - + - - + @@ -48,6 +47,10 @@

Control Structures

  • If/Else Statements
    • +

      Comments

      +
        +
      • Single Line Comments
      • +

      Example

       const timenow = time();
      @@ -55,8 +58,8 @@ 

      Example

      func add (x, y) { let result = x + y; - func sub (a, b) { - a - b + func sub(a, b) { + return a - b; } let result2 = sub(y, x); @@ -66,33 +69,35 @@

      Example

      y }; - let five = 10 - 5; + let five; + five = 10 - 5; const neg_num = -1000; - let float = 69.420; - const str = "Strings work!!!!"; + let flt = 69.420; + const string = "Strings work!!!!"; let obj = { five, neg_num, - float, + flt, result2, timenow, result, object: obj2, - str + string, + arr: [1, 2, 3, 4, 5] }; - obj + return obj; } print(add(5, 7)) func makeAdder(offset) { func add(x, y) { - x + y + offset + return x + y + offset; } - add + return add; } let adder = makeAdder(10); diff --git a/eryx/runtime/interpreter.py b/eryx/runtime/interpreter.py index b3df80b..fdda66b 100644 --- a/eryx/runtime/interpreter.py +++ b/eryx/runtime/interpreter.py @@ -279,14 +279,13 @@ def eval_call_expression( function_argument, arguments[i], False ) - result = NullValue() # Evaluate the function body statement by statement for statement in func.body: if isinstance(statement, ReturnStatement): return evaluate(statement, function_environment) evaluate(statement, function_environment) - return result + return NullValue() raise RuntimeError("Cannot call a non-function value.") diff --git a/eryx/utils/errors.py b/eryx/utils/errors.py new file mode 100644 index 0000000..1a64a9b --- /dev/null +++ b/eryx/utils/errors.py @@ -0,0 +1,42 @@ +"""Stuff for handling errors.""" + +import sys +from colorama import Fore + +def position_to_line_column(source_code: str, position: int) -> tuple[int, int]: + """Convert a position to a line and column number.""" + # Get the substring up to the given position + substring = source_code[:position] + + # Count the number of newline characters to determine the line + line = substring.count("\n") + 1 + + # Find the column by looking for the last newline + last_newline_pos = substring.rfind("\n") + column = position - last_newline_pos if last_newline_pos != -1 else position + 1 + + return (line, column) + + +def get_line_strings(source_code: str, line: int) -> str: + """Get the line string from the source code.""" + lines = source_code.split("\n") + + return lines[line - 10: line] + +def syntax_error(source_code: str, pos: int, error_message: str) -> None: + """Handle a syntax error.""" + current_line, current_col = position_to_line_column( + source_code, pos + ) + lines = get_line_strings(source_code, current_line) + print() + for n, line in enumerate(lines): + print(f"{Fore.CYAN}{str(current_line - (len(lines) - n) + 1).rjust(3)}:{Fore.WHITE} {line}") + print( + Fore.YELLOW + + "^".rjust(current_col + len(str(current_line).rjust(3)) + 2) + + Fore.WHITE + ) + print(f"{Fore.RED}SyntaxError{Fore.WHITE}: {error_message}") + sys.exit(1)