├── lpp ├── __init__.py ├── builtins.py ├── repl.py ├── token.py ├── object.py ├── lexer.py ├── ast.py ├── evaluator.py └── parser.py ├── tests ├── __init__.py ├── ast_test.py ├── lexer_test.py ├── evaluator_test.py └── parser_test.py ├── requirements.txt ├── .gitignore ├── main.py ├── LICENSE.txt └── README.md /lpp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | nose==1.3.7 2 | mypy==0.782 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | 4 | .mypy_cache/ 5 | __pycache__/ 6 | venv/ 7 | 8 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from lpp.repl import start_repl 2 | 3 | 4 | def main() -> None: 5 | print('Bienvenido al Lenguaje de Programación Platzi.') 6 | print('Escribe un comando para comenzar.') 7 | 8 | start_repl() 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /lpp/builtins.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | cast, 3 | Dict, 4 | ) 5 | 6 | from lpp.object import ( 7 | Builtin, 8 | Error, 9 | Integer, 10 | Object, 11 | String, 12 | ) 13 | 14 | 15 | _UNSUPPORTED_ARGUMENT_TYPE = 'argumento para longitud sin soporte, se recibió {}' 16 | _WRONG_NUMBER_OF_ARGS = 'número incorrecto de argumentos para longitud, se recibieron {}, se requieren {}' 17 | 18 | 19 | def longitud(*args: Object) -> Object: 20 | if len(args) != 1: 21 | return Error(_WRONG_NUMBER_OF_ARGS.format(len(args), 1)) 22 | elif type(args[0]) == String: 23 | argument = cast(String, args[0]) 24 | return Integer(len(argument.value)) 25 | else: 26 | return Error(_UNSUPPORTED_ARGUMENT_TYPE.format(args[0].type().name)) 27 | 28 | 29 | BUILTINS: Dict[str, Builtin] = { 30 | 'longitud': Builtin(fn=longitud) 31 | } 32 | -------------------------------------------------------------------------------- /lpp/repl.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from lpp.ast import Program 4 | from lpp.evaluator import evaluate 5 | from lpp.lexer import Lexer 6 | from lpp.object import Environment 7 | from lpp.parser import Parser 8 | from lpp.token import ( 9 | Token, 10 | TokenType 11 | ) 12 | 13 | 14 | EOF_TOKEN: Token = Token(TokenType.EOF, '') 15 | 16 | 17 | def _print_parse_errors(errors: List[str]): 18 | for error in errors: 19 | print(error) 20 | 21 | 22 | def start_repl() -> None: 23 | scanned: List[str] = [] 24 | 25 | while (source := input('>> ')) != 'salir()': 26 | scanned.append(source) 27 | lexer: Lexer = Lexer(' '.join(scanned)) 28 | parser: Parser = Parser(lexer) 29 | program: Program = parser.parse_program() 30 | env: Environment = Environment() 31 | 32 | if len(parser.errors) > 0: 33 | _print_parse_errors(parser.errors) 34 | continue 35 | 36 | evaluated = evaluate(program, env) 37 | 38 | if evaluated is not None: 39 | print(evaluated.inspect()) 40 | 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Platzi, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | If you enjoy this software please consider subscribing to Platzi at https://platzi.com . 10 | 11 | -------------------------------------------------------------------------------- /tests/ast_test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from lpp.ast import ( 4 | Identifier, 5 | LetStatement, 6 | Program, 7 | ReturnStatement, 8 | ) 9 | from lpp.token import ( 10 | Token, 11 | TokenType, 12 | ) 13 | 14 | class ASTTest(TestCase): 15 | 16 | def test_let_statement(self) -> None: 17 | program: Program = Program(statements=[ 18 | LetStatement( 19 | token=Token(TokenType.LET, literal='variable'), 20 | name=Identifier( 21 | token=Token(TokenType.IDENT, literal='mi_var'), 22 | value='mi_var' 23 | ), 24 | value=Identifier( 25 | token=Token(TokenType.IDENT, literal='otra_var'), 26 | value='otra_var' 27 | ) 28 | ), 29 | ]) 30 | 31 | program_str = str(program) 32 | 33 | self.assertEquals(program_str, 'variable mi_var = otra_var;') 34 | 35 | def test_return_statement(self) -> None: 36 | program: Program = Program(statements=[ 37 | ReturnStatement( 38 | token=Token(TokenType.RETURN, literal='regresa'), 39 | return_value=Identifier( 40 | token=Token(TokenType.IDENT, literal='mi_var'), 41 | value='mi_var' 42 | ) 43 | ), 44 | ]) 45 | 46 | program_str = str(program) 47 | 48 | self.assertEquals(program_str, 'regresa mi_var;') 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lenguaje de programación Platzi 2 | 3 | Source code for the Interpreters course at Platzi. 4 | 5 | # Install dependencies 6 | 7 | 1. Create a virtual environment: 8 | ```bash 9 | python3.8 -m venv venv 10 | ``` 11 | 12 | 2. Activate the virtual environment: 13 | ```bash 14 | source venv/bin/activate 15 | ``` 16 | 17 | 3. Install dependencies 18 | ```bash 19 | pip3 install -r requirements.txt 20 | ``` 21 | 22 | # Run type checker and test suite 23 | 24 | To run the type checker and and the test suite run the following command from 25 | the root directory. 26 | 27 | ```bash 28 | mypy . && nosetests 29 | ``` 30 | 31 | # Run the interpreter 32 | ```bash 33 | python3.8 main.py 34 | ``` 35 | 36 | # A sneak peak of the language 37 | ``` 38 | Bienvenido al Lenguaje de Programación Platzi. 39 | Escribe un oración para comenzar. 40 | >> variable a = 5; 41 | >> variable b = 10; 42 | >> a + b; 43 | 15 44 | >> variable mayor_de_edad = procedimiento(edad) { 45 | si(edad > 18) { 46 | regresa verdadero; 47 | } si_no { 48 | regresa falso; 49 | } 50 | }; 51 | >> mayor_de_edad(20); 52 | verdadero 53 | >> mayor_de_edad(15); 54 | falso 55 | >> variable sumador = procedimiento(x) { 56 | regresa procedimiento(y) { 57 | regresa x + y; 58 | }; 59 | }; 60 | >> variable suma_dos = sumador(2); 61 | >> suma_dos(5); 62 | 7 63 | >> variable suma_cinco = sumador(5); 64 | >> suma_cinco(20); 65 | 25 66 | >> mayor_de_edad(suma_cinco(20)); 67 | verdadero 68 | ``` 69 | -------------------------------------------------------------------------------- /lpp/token.py: -------------------------------------------------------------------------------- 1 | from enum import ( 2 | auto, 3 | Enum, 4 | unique, 5 | ) 6 | from typing import ( 7 | Dict, 8 | NamedTuple 9 | ) 10 | 11 | 12 | @unique 13 | class TokenType(Enum): 14 | ASSIGN = auto() 15 | COMMA = auto() 16 | DIVISION = auto() 17 | ELSE = auto() 18 | EOF = auto() 19 | EQ = auto() 20 | FALSE = auto() 21 | FUNCTION = auto() 22 | GT = auto() 23 | IDENT = auto() 24 | IF = auto() 25 | ILLEGAL = auto() 26 | INT = auto() 27 | LBRACE = auto() 28 | LET = auto() 29 | LPAREN = auto() 30 | LT = auto() 31 | MINUS = auto() 32 | MULTIPLICATION = auto() 33 | NEGATION = auto() 34 | NOT_EQ = auto() 35 | PLUS = auto() 36 | RETURN = auto() 37 | RPAREN = auto() 38 | RBRACE = auto() 39 | SEMICOLON = auto() 40 | STRING = auto() 41 | TRUE = auto() 42 | 43 | 44 | class Token(NamedTuple): 45 | token_type: TokenType 46 | literal: str 47 | 48 | def __str__(self) -> str: 49 | return f'Type: {self.token_type}, Literal: {self.literal}' 50 | 51 | 52 | def lookup_token_type(literal: str) -> TokenType: 53 | keywords: Dict[str, TokenType] = { 54 | 'falso': TokenType.FALSE, 55 | 'procedimiento': TokenType.FUNCTION, 56 | 'regresa': TokenType.RETURN, 57 | 'si': TokenType.IF, 58 | 'si_no': TokenType.ELSE, 59 | 'variable': TokenType.LET, 60 | 'verdadero': TokenType.TRUE, 61 | } 62 | 63 | return keywords.get(literal, TokenType.IDENT) 64 | -------------------------------------------------------------------------------- /lpp/object.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod, ABC 2 | from enum import auto, Enum 3 | from typing import ( 4 | Dict, 5 | List, 6 | Optional, 7 | ) 8 | from typing_extensions import Protocol 9 | 10 | from lpp.ast import ( 11 | Block, 12 | Identifier, 13 | ) 14 | 15 | 16 | class ObjectType(Enum): 17 | BOOLEAN = auto() 18 | BUILTIN = auto() 19 | ERROR = auto() 20 | FUNCTION = auto() 21 | INTEGER = auto() 22 | NULL = auto() 23 | RETURN = auto() 24 | STRING = auto() 25 | 26 | 27 | class Object(ABC): 28 | 29 | @abstractmethod 30 | def type(self) -> ObjectType: 31 | pass 32 | 33 | @abstractmethod 34 | def inspect(self) -> str: 35 | pass 36 | 37 | 38 | class Integer(Object): 39 | 40 | def __init__(self, value: int) -> None: 41 | self.value = value 42 | 43 | def type(self) -> ObjectType: 44 | return ObjectType.INTEGER 45 | 46 | def inspect(self) -> str: 47 | return str(self.value) 48 | 49 | 50 | class Boolean(Object): 51 | 52 | def __init__(self, value: bool) -> None: 53 | self.value = value 54 | 55 | def type(self) -> ObjectType: 56 | return ObjectType.BOOLEAN 57 | 58 | def inspect(self) -> str: 59 | return 'verdadero' if self.value else 'falso' 60 | 61 | 62 | class Null(Object): 63 | 64 | def type(self) -> ObjectType: 65 | return ObjectType.NULL 66 | 67 | def inspect(self) -> str: 68 | return 'nulo' 69 | 70 | 71 | class Return(Object): 72 | 73 | def __init__(self, value: Object): 74 | self.value = value 75 | 76 | def type(self) -> ObjectType: 77 | return ObjectType.RETURN 78 | 79 | def inspect(self) -> str: 80 | return self.value.inspect() 81 | 82 | 83 | class Error(Object): 84 | 85 | def __init__(self, message: str) -> None: 86 | self.message = message 87 | 88 | def type(self) -> ObjectType: 89 | return ObjectType.ERROR 90 | 91 | def inspect(self) -> str: 92 | return f'Error: {self.message}' 93 | 94 | 95 | class Environment(Dict): 96 | 97 | def __init__(self, outer = None): 98 | self._store: Dict = dict() 99 | self._outer = outer 100 | 101 | def __getitem__(self, key): 102 | try: 103 | return self._store[key] 104 | except KeyError as e: 105 | if self._outer is not None: 106 | return self._outer[key] 107 | 108 | raise e 109 | 110 | def __setitem__(self, key, value): 111 | self._store[key] = value 112 | 113 | def __delitem__(self, key): 114 | del self._store[key] 115 | 116 | 117 | class Function(Object): 118 | 119 | def __init__(self, 120 | parameters: List[Identifier], 121 | body: Block, 122 | env: Environment) -> None: 123 | self.parameters = parameters 124 | self.body = body 125 | self.env = env 126 | 127 | def type(self) -> ObjectType: 128 | return ObjectType.FUNCTION 129 | 130 | def inspect(self) -> str: 131 | params: str = ', '.join([str(param) for param in self.parameters]) 132 | 133 | return 'procedimiento({}) {{\n{}\n}}'.format(params, str(self.body)) 134 | 135 | 136 | class String(Object): 137 | 138 | def __init__(self, value: str) -> None: 139 | self.value = value 140 | 141 | def type(self) -> ObjectType: 142 | return ObjectType.STRING 143 | 144 | def inspect(self) -> str: 145 | return self.value 146 | 147 | 148 | class BuiltinFunction(Protocol): 149 | 150 | def __call__(self, *args: Object) -> Object: ... 151 | 152 | 153 | class Builtin(Object): 154 | 155 | def __init__(self, fn: BuiltinFunction) -> None: 156 | self.fn = fn 157 | 158 | def type(self) -> ObjectType: 159 | return ObjectType.BUILTIN 160 | 161 | def inspect(self) -> str: 162 | return 'builtin function' 163 | 164 | -------------------------------------------------------------------------------- /lpp/lexer.py: -------------------------------------------------------------------------------- 1 | from re import match 2 | 3 | from lpp.token import ( 4 | lookup_token_type, 5 | Token, 6 | TokenType, 7 | ) 8 | 9 | class Lexer: 10 | 11 | def __init__(self, source: str) -> None: 12 | self._source: str = source 13 | self._position: int = 0 14 | self._read_position: int = 0 15 | self._character: str = '' 16 | 17 | self._read_character() 18 | 19 | def next_token(self) -> Token: 20 | self._skip_whitespace() 21 | 22 | if match(r'^=$', self._character): 23 | if self._peek_character() == '=': 24 | token = self._make_two_character_token(TokenType.EQ) 25 | else: 26 | token = Token(TokenType.ASSIGN, self._character) 27 | elif match(r'^\+$', self._character): 28 | token = Token(TokenType.PLUS, self._character) 29 | elif match(r'^$', self._character): 30 | token = Token(TokenType.EOF, self._character) 31 | elif match(r'^\($', self._character): 32 | token = Token(TokenType.LPAREN, self._character) 33 | elif match(r'^\)$', self._character): 34 | token = Token(TokenType.RPAREN, self._character) 35 | elif match(r'^{$', self._character): 36 | token = Token(TokenType.LBRACE, self._character) 37 | elif match(r'^}$', self._character): 38 | token = Token(TokenType.RBRACE, self._character) 39 | elif match(r'^,$', self._character): 40 | token = Token(TokenType.COMMA, self._character) 41 | elif match(r'^;$', self._character): 42 | token = Token(TokenType.SEMICOLON, self._character) 43 | elif match(r'^-$', self._character): 44 | token = Token(TokenType.MINUS, self._character) 45 | elif match(r'^/$', self._character): 46 | token = Token(TokenType.DIVISION, self._character) 47 | elif match(r'^\*$', self._character): 48 | token = Token(TokenType.MULTIPLICATION, self._character) 49 | elif match(r'^<$', self._character): 50 | token = Token(TokenType.LT, self._character) 51 | elif match(r'^>$', self._character): 52 | token = Token(TokenType.GT, self._character) 53 | elif match(r'^!$', self._character): 54 | if self._peek_character() == '=': 55 | token = self._make_two_character_token(TokenType.NOT_EQ) 56 | else: 57 | token = Token(TokenType.NEGATION, self._character) 58 | elif self._is_letter(self._character): 59 | literal = self._read_identifier() 60 | token_type = lookup_token_type(literal) 61 | 62 | return Token(token_type, literal) 63 | elif self._is_number(self._character): 64 | literal = self._read_number() 65 | 66 | return Token(TokenType.INT, literal) 67 | elif match(r'^"$', self._character): 68 | literal = self._read_string() 69 | 70 | return Token(TokenType.STRING, literal) 71 | else: 72 | token = Token(TokenType.ILLEGAL, self._character) 73 | 74 | self._read_character() 75 | 76 | return token 77 | 78 | def _is_letter(self, character: str) -> bool: 79 | return bool(match(r'^[a-záéíóúA-ZÁÉÍÓÚñÑ_]$', character)) 80 | 81 | def _is_number(self, character: str) -> bool: 82 | return bool(match(r'^\d$', character)) 83 | 84 | def _make_two_character_token(self, token_type: TokenType) -> Token: 85 | prefix = self._character 86 | self._read_character() 87 | suffix = self._character 88 | 89 | return Token(token_type, f'{prefix}{suffix}') 90 | 91 | def _peek_character(self) -> str: 92 | if self._read_position >= len(self._source): 93 | return '' 94 | 95 | return self._source[self._read_position] 96 | 97 | def _read_character(self) -> None: 98 | if self._read_position >= len(self._source): 99 | self._character = '' 100 | else: 101 | self._character = self._source[self._read_position] 102 | 103 | self._position = self._read_position 104 | self._read_position += 1 105 | 106 | def _read_identifier(self) -> str: 107 | initial_position = self._position 108 | 109 | is_first_letter = True 110 | while self._is_letter(self._character) or \ 111 | (not is_first_letter and self._is_number(self._character)): 112 | self._read_character() 113 | is_first_letter = False 114 | 115 | return self._source[initial_position:self._position] 116 | 117 | def _read_number(self) -> str: 118 | initial_position = self._position 119 | 120 | while self._is_number(self._character): 121 | self._read_character() 122 | 123 | return self._source[initial_position:self._position] 124 | 125 | def _read_string(self) -> str: 126 | self._read_character() 127 | 128 | initial_position = self._position 129 | 130 | while self._character != '"' \ 131 | and self._read_position <= len(self._source): 132 | self._read_character() 133 | 134 | string = self._source[initial_position:self._position] 135 | 136 | self._read_character() 137 | 138 | return string 139 | 140 | def _skip_whitespace(self) -> None: 141 | while match(r'^\s$', self._character): 142 | self._read_character() 143 | 144 | -------------------------------------------------------------------------------- /lpp/ast.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod, 4 | ) 5 | from typing import ( 6 | List, 7 | Optional, 8 | ) 9 | 10 | from lpp.token import Token 11 | 12 | 13 | class ASTNode(ABC): 14 | 15 | @abstractmethod 16 | def token_literal(self) -> str: 17 | pass 18 | 19 | @abstractmethod 20 | def __str__(self) -> str: 21 | pass 22 | 23 | 24 | class Statement(ASTNode): 25 | 26 | def __init__(self, token: Token) -> None: 27 | self.token = token 28 | 29 | def token_literal(self) -> str: 30 | return self.token.literal 31 | 32 | 33 | class Expression(ASTNode): 34 | 35 | def __init__(self, token: Token) -> None: 36 | self.token = token 37 | 38 | def token_literal(self) -> str: 39 | return self.token.literal 40 | 41 | 42 | class Program(ASTNode): 43 | 44 | def __init__(self, statements: List[Statement]) -> None: 45 | self.statements = statements 46 | 47 | def token_literal(self) -> str: 48 | if len(self.statements) > 0: 49 | return self.statements[0].token_literal() 50 | 51 | return '' 52 | 53 | def __str__(self) -> str: 54 | out: List[str] = [str(statement) for statement in self.statements] 55 | 56 | return ''.join(out) 57 | 58 | 59 | class Identifier(Expression): 60 | 61 | def __init__(self, 62 | token: Token, 63 | value: str) -> None: 64 | super().__init__(token) 65 | self.value = value 66 | 67 | def __str__(self) -> str: 68 | return self.value 69 | 70 | 71 | class LetStatement(Statement): 72 | 73 | def __init__(self, 74 | token: Token, 75 | name: Optional[Identifier] = None, 76 | value: Optional[Expression] = None) -> None: 77 | super().__init__(token) 78 | self.name = name 79 | self.value = value 80 | 81 | def __str__(self) -> str: 82 | return f'{self.token_literal()} {str(self.name)} = {str(self.value)};' 83 | 84 | 85 | class ReturnStatement(Statement): 86 | 87 | def __init__(self, 88 | token: Token, 89 | return_value: Optional[Expression] = None) -> None: 90 | super().__init__(token) 91 | self.return_value = return_value 92 | 93 | def __str__(self) -> str: 94 | return f'{self.token_literal()} {str(self.return_value)};' 95 | 96 | 97 | class ExpressionStatement(Statement): 98 | 99 | def __init__(self, 100 | token: Token, 101 | expression: Optional[Expression] = None) -> None: 102 | super().__init__(token) 103 | self.expression = expression 104 | 105 | def __str__(self) -> str: 106 | return str(self.expression) 107 | 108 | 109 | class Integer(Expression): 110 | 111 | def __init__(self, 112 | token: Token, 113 | value: Optional[int] = None) -> None: 114 | super().__init__(token) 115 | self.value = value 116 | 117 | def __str__(self) -> str: 118 | return str(self.value) 119 | 120 | 121 | class Prefix(Expression): 122 | 123 | def __init__(self, 124 | token: Token, 125 | operator: str, 126 | right: Optional[Expression] = None) -> None: 127 | super().__init__(token) 128 | self.operator = operator 129 | self.right = right 130 | 131 | def __str__(self) -> str: 132 | return f'({self.operator}{str(self.right)})' 133 | 134 | 135 | class Infix(Expression): 136 | 137 | def __init__(self, 138 | token: Token, 139 | left: Expression, 140 | operator: str, 141 | right: Optional[Expression] = None) -> None: 142 | super().__init__(token) 143 | self.left = left 144 | self.operator = operator 145 | self.right = right 146 | 147 | def __str__(self) -> str: 148 | return f'({str(self.left)} {self.operator} {str(self.right)})' 149 | 150 | 151 | class Boolean(Expression): 152 | 153 | def __init__(self, 154 | token: Token, 155 | value: Optional[bool] = None) -> None: 156 | super().__init__(token) 157 | self.value = value 158 | 159 | def __str__(self) -> str: 160 | return self.token_literal() 161 | 162 | 163 | class Block(Statement): 164 | 165 | def __init__(self, 166 | token: Token, 167 | statements: List[Statement]) -> None: 168 | super().__init__(token) 169 | self.statements = statements 170 | 171 | def __str__(self) -> str: 172 | out: List[str] = [str(statement) for statement in self.statements] 173 | 174 | return ''.join(out) 175 | 176 | 177 | class If(Expression): 178 | 179 | def __init__(self, 180 | token: Token, 181 | condition: Optional[Expression] = None, 182 | consequence: Optional[Block] = None, 183 | alternative: Optional[Block] = None) -> None: 184 | super().__init__(token) 185 | self.condition = condition 186 | self.consequence = consequence 187 | self.alternative = alternative 188 | 189 | def __str__(self) -> str: 190 | out: str = f'si {str(self.condition)} {str(self.consequence)}' 191 | 192 | if self.alternative: 193 | out += f'si_no {str(self.alternative)}' 194 | 195 | return out 196 | 197 | 198 | class Function(Expression): 199 | 200 | def __init__(self, 201 | token: Token, 202 | parameters: List[Identifier] = [], 203 | body: Optional[Block] = None) -> None: 204 | super().__init__(token) 205 | self.parameters = parameters 206 | self.body = body 207 | 208 | def __str__(self) -> str: 209 | param_list: List[str] = [str(parameter) for parameter in self.parameters] 210 | params: str = ', '.join(param_list) 211 | 212 | return f'{self.token_literal()}({params}) {str(self.body)}' 213 | 214 | 215 | class Call(Expression): 216 | 217 | def __init__(self, 218 | token: Token, 219 | function: Expression, 220 | arguments: Optional[List[Expression]] = None) -> None: 221 | super().__init__(token) 222 | self.function = function 223 | self.arguments = arguments 224 | 225 | def __str__(self) -> str: 226 | assert self.arguments is not None 227 | arg_list: List[str] = [str(argument) for argument in self.arguments] 228 | args: str = ', '.join(arg_list) 229 | 230 | return f'{str(self.function)}({args})' 231 | 232 | 233 | class StringLiteral(Expression): 234 | 235 | def __init__(self, 236 | token: Token, 237 | value: str) -> None: 238 | super().__init__(token) 239 | self.value = value 240 | 241 | def __str__(self) -> str: 242 | return super().__str__() 243 | 244 | -------------------------------------------------------------------------------- /tests/lexer_test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from typing import List 3 | 4 | from lpp.token import ( 5 | Token, 6 | TokenType, 7 | ) 8 | from lpp.lexer import Lexer 9 | 10 | 11 | class LexerTest(TestCase): 12 | 13 | def test_illegal(self) -> None: 14 | source: str = '@¡¿' 15 | lexer: Lexer = Lexer(source) 16 | 17 | tokens: List[Token] = [] 18 | for i in range(len(source)): 19 | tokens.append(lexer.next_token()) 20 | 21 | expected_tokens: List[Token] = [ 22 | Token(TokenType.ILLEGAL, '@'), 23 | Token(TokenType.ILLEGAL, '¡'), 24 | Token(TokenType.ILLEGAL, '¿'), 25 | ] 26 | 27 | self.assertEquals(tokens, expected_tokens) 28 | 29 | def test_one_character_operators(self) -> None: 30 | source: str = '=+-/*<>!' 31 | lexer: Lexer = Lexer(source) 32 | 33 | tokens: List[Token] = [] 34 | for i in range(len(source)): 35 | tokens.append(lexer.next_token()) 36 | 37 | expected_tokens: List[Token] = [ 38 | Token(TokenType.ASSIGN, '='), 39 | Token(TokenType.PLUS, '+'), 40 | Token(TokenType.MINUS, '-'), 41 | Token(TokenType.DIVISION, '/'), 42 | Token(TokenType.MULTIPLICATION, '*'), 43 | Token(TokenType.LT, '<'), 44 | Token(TokenType.GT, '>'), 45 | Token(TokenType.NEGATION, '!'), 46 | ] 47 | 48 | self.assertEquals(tokens, expected_tokens) 49 | 50 | def test_eof(self) -> None: 51 | source: str = '+' 52 | lexer: Lexer = Lexer(source) 53 | 54 | tokens: List[Token] = [] 55 | for i in range(len(source) + 1): 56 | tokens.append(lexer.next_token()) 57 | 58 | expected_tokens: List[Token] = [ 59 | Token(TokenType.PLUS, '+'), 60 | Token(TokenType.EOF, ''), 61 | ] 62 | 63 | self.assertEquals(tokens, expected_tokens) 64 | 65 | def test_delimiters(self) -> None: 66 | source: str = '(){},;' 67 | lexer: Lexer = Lexer(source) 68 | 69 | tokens: List[Token] = [] 70 | for i in range(len(source)): 71 | tokens.append(lexer.next_token()) 72 | 73 | expected_tokens: List[Token] = [ 74 | Token(TokenType.LPAREN, '('), 75 | Token(TokenType.RPAREN, ')'), 76 | Token(TokenType.LBRACE, '{'), 77 | Token(TokenType.RBRACE, '}'), 78 | Token(TokenType.COMMA, ','), 79 | Token(TokenType.SEMICOLON, ';'), 80 | ] 81 | 82 | self.assertEquals(tokens, expected_tokens) 83 | 84 | def test_assignment(self) -> None: 85 | source: str = 'variable cinco = 5;' 86 | lexer: Lexer = Lexer(source) 87 | 88 | tokens: List[Token] = [] 89 | for i in range(5): 90 | tokens.append(lexer.next_token()) 91 | 92 | expected_tokens: List[Token] = [ 93 | Token(TokenType.LET, 'variable'), 94 | Token(TokenType.IDENT, 'cinco'), 95 | Token(TokenType.ASSIGN, '='), 96 | Token(TokenType.INT, '5'), 97 | Token(TokenType.SEMICOLON, ';'), 98 | ] 99 | 100 | self.assertEquals(tokens, expected_tokens) 101 | 102 | def test_function_declaration(self) -> None: 103 | source: str = ''' 104 | variable suma = procedimiento(x, y) { 105 | x + y; 106 | }; 107 | ''' 108 | lexer: Lexer = Lexer(source) 109 | 110 | tokens: List[Token] = [] 111 | for i in range(16): 112 | tokens.append(lexer.next_token()) 113 | 114 | expected_tokens: List[Token] = [ 115 | Token(TokenType.LET, 'variable'), 116 | Token(TokenType.IDENT, 'suma'), 117 | Token(TokenType.ASSIGN, '='), 118 | Token(TokenType.FUNCTION, 'procedimiento'), 119 | Token(TokenType.LPAREN, '('), 120 | Token(TokenType.IDENT, 'x'), 121 | Token(TokenType.COMMA, ','), 122 | Token(TokenType.IDENT, 'y'), 123 | Token(TokenType.RPAREN, ')'), 124 | Token(TokenType.LBRACE, '{'), 125 | Token(TokenType.IDENT, 'x'), 126 | Token(TokenType.PLUS, '+'), 127 | Token(TokenType.IDENT, 'y'), 128 | Token(TokenType.SEMICOLON, ';'), 129 | Token(TokenType.RBRACE, '}'), 130 | Token(TokenType.SEMICOLON, ';'), 131 | ] 132 | 133 | self.assertEquals(tokens, expected_tokens) 134 | 135 | def test_function_call(self) -> None: 136 | source: str = 'variable resultado = suma(dos, tres);' 137 | lexer: Lexer = Lexer(source) 138 | 139 | tokens: List[Token] = [] 140 | for i in range(10): 141 | tokens.append(lexer.next_token()) 142 | 143 | expected_tokens: List[Token] = [ 144 | Token(TokenType.LET, 'variable'), 145 | Token(TokenType.IDENT, 'resultado'), 146 | Token(TokenType.ASSIGN, '='), 147 | Token(TokenType.IDENT, 'suma'), 148 | Token(TokenType.LPAREN, '('), 149 | Token(TokenType.IDENT, 'dos'), 150 | Token(TokenType.COMMA, ','), 151 | Token(TokenType.IDENT, 'tres'), 152 | Token(TokenType.RPAREN, ')'), 153 | Token(TokenType.SEMICOLON, ';'), 154 | ] 155 | 156 | self.assertEquals(tokens, expected_tokens) 157 | 158 | def test_control_statement(self) -> None: 159 | source: str = ''' 160 | si (5 < 10) { 161 | regresa verdadero; 162 | } si_no { 163 | regresa falso; 164 | } 165 | ''' 166 | lexer: Lexer = Lexer(source) 167 | 168 | tokens: List[Token] = [] 169 | for i in range(17): 170 | tokens.append(lexer.next_token()) 171 | 172 | expected_tokens: List[Token] = [ 173 | Token(TokenType.IF, 'si'), 174 | Token(TokenType.LPAREN, '('), 175 | Token(TokenType.INT, '5'), 176 | Token(TokenType.LT, '<'), 177 | Token(TokenType.INT, '10'), 178 | Token(TokenType.RPAREN, ')'), 179 | Token(TokenType.LBRACE, '{'), 180 | Token(TokenType.RETURN, 'regresa'), 181 | Token(TokenType.TRUE, 'verdadero'), 182 | Token(TokenType.SEMICOLON, ';'), 183 | Token(TokenType.RBRACE, '}'), 184 | Token(TokenType.ELSE, 'si_no'), 185 | Token(TokenType.LBRACE, '{'), 186 | Token(TokenType.RETURN, 'regresa'), 187 | Token(TokenType.FALSE, 'falso'), 188 | Token(TokenType.SEMICOLON, ';'), 189 | Token(TokenType.RBRACE, '}'), 190 | ] 191 | 192 | self.assertEquals(tokens, expected_tokens) 193 | 194 | def test_two_character_operator(self) -> None: 195 | source: str = ''' 196 | 10 == 10; 197 | 10 != 9; 198 | ''' 199 | lexer: Lexer = Lexer(source) 200 | 201 | tokens: List[Token] = [] 202 | for i in range(8): 203 | tokens.append(lexer.next_token()) 204 | 205 | expected_tokens: List[Token] = [ 206 | Token(TokenType.INT, '10'), 207 | Token(TokenType.EQ, '=='), 208 | Token(TokenType.INT, '10'), 209 | Token(TokenType.SEMICOLON, ';'), 210 | Token(TokenType.INT, '10'), 211 | Token(TokenType.NOT_EQ, '!='), 212 | Token(TokenType.INT, '9'), 213 | Token(TokenType.SEMICOLON, ';'), 214 | ] 215 | 216 | self.assertEquals(tokens, expected_tokens) 217 | 218 | def test_strings(self) -> None: 219 | source: str = ''' 220 | "foo"; 221 | "Platzi es la mejor escuela online"; 222 | ''' 223 | lexer: Lexer = Lexer(source) 224 | 225 | tokens: List[Token] = [] 226 | for i in range(4): 227 | tokens.append(lexer.next_token()) 228 | 229 | print(tokens) 230 | expected_tokens: List[Token] = [ 231 | Token(TokenType.STRING, 'foo'), 232 | Token(TokenType.SEMICOLON, ';'), 233 | Token(TokenType.STRING, 'Platzi es la mejor escuela online'), 234 | Token(TokenType.SEMICOLON, ';'), 235 | ] 236 | 237 | self.assertEquals(tokens, expected_tokens) 238 | 239 | -------------------------------------------------------------------------------- /lpp/evaluator.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any, 3 | cast, 4 | List, 5 | Optional, 6 | Type, 7 | ) 8 | 9 | import lpp.ast as ast 10 | from lpp.builtins import BUILTINS 11 | from lpp.object import ( 12 | Boolean, 13 | Builtin, 14 | Environment, 15 | Error, 16 | Function, 17 | Integer, 18 | Null, 19 | Object, 20 | ObjectType, 21 | Return, 22 | String, 23 | ) 24 | 25 | 26 | TRUE = Boolean(True) 27 | FALSE = Boolean(False) 28 | NULL = Null() 29 | 30 | 31 | _NOT_A_FUNCTION = 'No es una función: {}' 32 | _TYPE_MISMATCH = 'Discrepancia de tipos: {} {} {}' 33 | _UNKNOWN_PREFIX_OPERATOR = 'Operador desconocido: {}{}' 34 | _UNKNOWN_INFIX_OPERATOR = 'Operador desconocido: {} {} {}' 35 | _UNKNOWN_IDENTIFIER = 'Identificador no encontrado: {}' 36 | 37 | 38 | def evaluate(node: ast.ASTNode, env: Environment) -> Optional[Object]: 39 | node_type: Type = type(node) 40 | 41 | if node_type == ast.Program: 42 | node = cast(ast.Program, node) 43 | 44 | return _evaluate_program(node, env) 45 | elif node_type == ast.ExpressionStatement: 46 | node = cast(ast.ExpressionStatement, node) 47 | 48 | assert node.expression is not None 49 | return evaluate(node.expression, env) 50 | elif node_type == ast.Integer: 51 | node = cast(ast.Integer, node) 52 | 53 | assert node.value is not None 54 | return Integer(node.value) 55 | elif node_type == ast.Boolean: 56 | node = cast(ast.Boolean, node) 57 | 58 | assert node.value is not None 59 | return _to_boolean_object(node.value) 60 | elif node_type == ast.Prefix: 61 | node = cast(ast.Prefix, node) 62 | 63 | assert node.right is not None 64 | right = evaluate(node.right, env) 65 | 66 | assert right is not None 67 | return _evaluate_prefix_expression(node.operator, right) 68 | elif node_type == ast.Infix: 69 | node = cast(ast.Infix, node) 70 | 71 | assert node.left is not None and node.right is not None 72 | left = evaluate(node.left, env) 73 | right = evaluate(node.right, env) 74 | 75 | assert right is not None and left is not None 76 | return _evaluate_infix_expression(node.operator, left, right) 77 | elif node_type == ast.Block: 78 | node = cast(ast.Block, node) 79 | 80 | return _evaluate_block_statement(node, env) 81 | elif node_type == ast.If: 82 | node = cast(ast.If, node) 83 | 84 | return _evaluate_if_expression(node, env) 85 | elif node_type == ast.ReturnStatement: 86 | node = cast(ast.ReturnStatement, node) 87 | 88 | assert node.return_value is not None 89 | value = evaluate(node.return_value, env) 90 | 91 | assert value is not None 92 | return Return(value) 93 | elif node_type == ast.LetStatement: 94 | node = cast(ast.LetStatement, node) 95 | 96 | assert node.value is not None 97 | value = evaluate(node.value, env) 98 | 99 | assert node.name is not None 100 | env[node.name.value] = value 101 | elif node_type == ast.Identifier: 102 | node = cast(ast.Identifier, node) 103 | 104 | return _evaluate_identifier(node, env) 105 | elif node_type == ast.Function: 106 | node = cast(ast.Function, node) 107 | 108 | assert node.body is not None 109 | return Function(node.parameters, 110 | node.body, 111 | env) 112 | elif node_type == ast.Call: 113 | node = cast(ast.Call, node) 114 | 115 | function = evaluate(node.function, env) 116 | 117 | assert node.arguments is not None 118 | args = _evaluate_expression(node.arguments, env) 119 | 120 | assert function is not None 121 | return _apply_function(function, args) 122 | elif node_type == ast.StringLiteral: 123 | node = cast(ast.StringLiteral, node) 124 | 125 | return String(node.value) 126 | 127 | return None 128 | 129 | 130 | def _apply_function(fn: Object, args: List[Object]) -> Object: 131 | if type(fn) == Function: 132 | fn = cast(Function, fn) 133 | 134 | extended_environment = _extend_function_environment(fn, args) 135 | evaluated = evaluate(fn.body, extended_environment) 136 | 137 | assert evaluated is not None 138 | return _unwrap_return_value(evaluated) 139 | elif type(fn) == Builtin: 140 | fn = cast(Builtin, fn) 141 | 142 | return fn.fn(*args) 143 | else: 144 | return _new_error(_NOT_A_FUNCTION, [fn.type().name]) 145 | 146 | 147 | def _extend_function_environment(fn: Function, args: List[Object]) -> Environment: 148 | env = Environment(outer=fn.env) 149 | 150 | for idx, param in enumerate(fn.parameters): 151 | env[param.value] = args[idx - 1] 152 | 153 | return env 154 | 155 | def _unwrap_return_value(obj: Object) -> Object: 156 | if type(obj) == Return: 157 | obj = cast(Return, obj) 158 | return obj.value 159 | 160 | return obj 161 | 162 | 163 | def _evaluate_program(program: ast.Program, env: Environment) -> Optional[Object]: 164 | result: Optional[Object] = None 165 | 166 | for statement in program.statements: 167 | result = evaluate(statement, env) 168 | 169 | if type(result) == Return: 170 | result = cast(Return, result) 171 | return result.value 172 | elif type(result) == Error: 173 | return result 174 | 175 | return result 176 | 177 | 178 | def _evaluate_bang_operator_expression(right: Object) -> Object: 179 | if right is TRUE: 180 | return FALSE 181 | elif right is FALSE: 182 | return TRUE 183 | elif right is NULL: 184 | return TRUE 185 | else: 186 | return FALSE 187 | 188 | 189 | def _evaluate_block_statement(block: ast.Block, env: Environment) -> Optional[Object]: 190 | result: Optional[Object] = None 191 | 192 | for statement in block.statements: 193 | result = evaluate(statement, env) 194 | 195 | if result is not None and \ 196 | (result.type() == ObjectType.RETURN or result.type() == ObjectType.ERROR): 197 | return result 198 | 199 | return result 200 | 201 | 202 | def _evaluate_expression(expressions: List[ast.Expression], env: Environment) -> List[Object]: 203 | result: List[Object] = [] 204 | 205 | for expression in expressions: 206 | evaluated = evaluate(expression, env) 207 | 208 | assert evaluated is not None 209 | result.append(evaluated) 210 | 211 | return result 212 | 213 | 214 | def _evaluate_identifier(node: ast.Identifier, env: Environment) -> Object: 215 | try: 216 | return env[node.value] 217 | except KeyError: 218 | return BUILTINS.get(node.value, 219 | _new_error(_UNKNOWN_IDENTIFIER, [node.value])) 220 | 221 | 222 | def _evaluate_if_expression(if_expression: ast.If, env: Environment) -> Optional[Object]: 223 | assert if_expression.condition is not None 224 | condition = evaluate(if_expression.condition, env) 225 | 226 | assert condition is not None 227 | if _is_truthy(condition): 228 | assert if_expression.consequence is not None 229 | return evaluate(if_expression.consequence, env) 230 | elif if_expression.alternative is not None: 231 | return evaluate(if_expression.alternative, env) 232 | else: 233 | return NULL 234 | 235 | 236 | def _evaluate_infix_expression(operator: str, 237 | left: Object, 238 | right: Object) -> Object: 239 | if left.type() == ObjectType.INTEGER \ 240 | and right.type() == ObjectType.INTEGER: 241 | return _evaluate_integer_infix_expression(operator, left, right) 242 | elif left.type() == ObjectType.STRING \ 243 | and right.type() == ObjectType.STRING: 244 | return _evaluate_string_infix_expression(operator, left, right) 245 | elif operator == '==': 246 | return _to_boolean_object(left is right) 247 | elif operator == '!=': 248 | return _to_boolean_object(left is not right) 249 | elif left.type() != right.type(): 250 | return _new_error(_TYPE_MISMATCH, [left.type().name, 251 | operator, 252 | right.type().name]) 253 | else: 254 | return _new_error(_UNKNOWN_INFIX_OPERATOR, [left.type().name, 255 | operator, 256 | right.type().name]) 257 | 258 | 259 | def _evaluate_integer_infix_expression(operator: str, 260 | left: Object, 261 | right: Object) -> Object: 262 | left_value: int = cast(Integer, left).value 263 | right_value: int = cast(Integer, right).value 264 | 265 | if operator == '+': 266 | return Integer(left_value + right_value) 267 | elif operator == '-': 268 | return Integer(left_value - right_value) 269 | elif operator == '*': 270 | return Integer(left_value * right_value) 271 | elif operator == '/': 272 | return Integer(left_value // right_value) 273 | elif operator == '<': 274 | return _to_boolean_object(left_value < right_value) 275 | elif operator == '>': 276 | return _to_boolean_object(left_value > right_value) 277 | elif operator == '==': 278 | return _to_boolean_object(left_value == right_value) 279 | elif operator == '!=': 280 | return _to_boolean_object(left_value != right_value) 281 | else: 282 | return _new_error(_UNKNOWN_INFIX_OPERATOR, [left.type().name, 283 | operator, 284 | right.type().name]) 285 | 286 | 287 | def _evaluate_minus_operator_expression(right: Object) -> Object: 288 | if type(right) != Integer: 289 | return _new_error(_UNKNOWN_PREFIX_OPERATOR, ['-', right.type().name]) 290 | 291 | right = cast(Integer, right) 292 | 293 | return Integer(-right.value) 294 | 295 | 296 | def _evaluate_prefix_expression(operator: str, right: Object) -> Object: 297 | if operator == '!': 298 | return _evaluate_bang_operator_expression(right) 299 | elif operator == '-': 300 | return _evaluate_minus_operator_expression(right) 301 | else: 302 | return _new_error(_UNKNOWN_PREFIX_OPERATOR, [operator, right.type().name]) 303 | 304 | 305 | def _evaluate_string_infix_expression(operator: str, 306 | left: Object, 307 | right: Object) -> Object: 308 | left_value: str = cast(String, left).value 309 | right_value: str = cast(String, right).value 310 | 311 | if operator == '+': 312 | return String(left_value + right_value) 313 | elif operator == '==': 314 | return _to_boolean_object(left_value == right_value) 315 | elif operator == '!=': 316 | return _to_boolean_object(left_value != right_value) 317 | else: 318 | return _new_error(_UNKNOWN_INFIX_OPERATOR, [left.type().name, 319 | operator, 320 | right.type().name]) 321 | 322 | def _is_truthy(obj: Object) -> bool: 323 | if obj is NULL: 324 | return False 325 | elif obj is TRUE: 326 | return True 327 | elif obj is FALSE: 328 | return False 329 | else: 330 | return True 331 | 332 | 333 | def _new_error(message: str, args: List[Any]) -> Error: 334 | return Error(message.format(*args)) 335 | 336 | 337 | def _to_boolean_object(value: bool) -> Boolean: 338 | return TRUE if value else FALSE 339 | 340 | -------------------------------------------------------------------------------- /tests/evaluator_test.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any, 3 | cast, 4 | List, 5 | Tuple, 6 | Optional, 7 | Union, 8 | ) 9 | from unittest import TestCase 10 | 11 | from lpp.evaluator import ( 12 | evaluate, 13 | NULL, 14 | ) 15 | from lpp.lexer import Lexer 16 | from lpp.object import ( 17 | Boolean, 18 | Environment, 19 | Error, 20 | Function, 21 | Integer, 22 | Object, 23 | String, 24 | ) 25 | from lpp.parser import Parser 26 | 27 | 28 | class EvaluatorTest(TestCase): 29 | 30 | def test_integer_evaluation(self) -> None: 31 | tests: List[Tuple[str, int]] = [ 32 | ('5', 5), 33 | ('10', 10), 34 | ('-5', -5), 35 | ('-10', -10), 36 | ('5 + 5', 10), 37 | ('5 - 10', -5), 38 | ('2 * 2 * 2 * 2', 16), 39 | ('2 * 5 - 3', 7), 40 | ('50 / 2', 25), 41 | ('2 * (5 - 3)', 4), 42 | ('(2 + 7) / 3', 3), 43 | ('50 / 2 * 2 + 10', 60), 44 | ('5 / 2', 2), 45 | ] 46 | 47 | for source, expected in tests: 48 | evaluated = self._evaluate_tests(source) 49 | self._test_integer_object(evaluated, expected) 50 | 51 | def test_boolean_evaluation(self) -> None: 52 | tests: List[Tuple[str, bool]] = [ 53 | ('verdadero', True), 54 | ('falso', False), 55 | ('1 < 2', True), 56 | ('1 > 2', False), 57 | ('1 < 1', False), 58 | ('1 > 1', False), 59 | ('1 == 1', True), 60 | ('1 != 1', False), 61 | ('1 == 2', False), 62 | ('1 != 2', True), 63 | ('verdadero == verdadero', True), 64 | ('falso == falso', True), 65 | ('verdadero == falso', False), 66 | ('verdadero != falso', True), 67 | ('(1 < 2) == verdadero', True), 68 | ('(1 < 2) == falso', False), 69 | ('(1 > 2) == verdadero', False), 70 | ('(1 > 2) == falso', True), 71 | ] 72 | 73 | for source, expected in tests: 74 | evaluated = self._evaluate_tests(source) 75 | self._test_boolean_object(evaluated, expected) 76 | 77 | def test_bang_operator(self) -> None: 78 | tests: List[Tuple[str, bool]] = [ 79 | ('!verdadero', False), 80 | ('!falso', True), 81 | ('!!verdadero', True), 82 | ('!!falso', False), 83 | ('!5', False), 84 | ('!!5', True), 85 | ] 86 | 87 | for source, expected in tests: 88 | evaluated = self._evaluate_tests(source) 89 | self._test_boolean_object(evaluated, expected) 90 | 91 | def test_if_else_evaluation(self) -> None: 92 | tests: List[Tuple[str, Any]] = [ 93 | ('si (verdadero) { 10 }', 10), 94 | ('si (falso) { 10 }', None), 95 | ('si (1) { 10 }', 10), 96 | ('si (1 < 2) { 10 }', 10), 97 | ('si (1 > 2) { 10 }', None), 98 | ('si (1 < 2) { 10 } si_no { 20 }', 10), 99 | ('si (1 > 2) { 10 } si_no { 20 }', 20), 100 | ] 101 | 102 | for source, expected in tests: 103 | evaluated = self._evaluate_tests(source) 104 | 105 | if type(expected) == int: 106 | self._test_integer_object(evaluated, expected) 107 | else: 108 | self._test_null_object(evaluated) 109 | 110 | def test_return_evaluation(self) -> None: 111 | tests: List[Tuple[str, Any]] = [ 112 | ('regresa 10;', 10), 113 | ('regresa 10; 9;', 10), 114 | ('regresa 2 * 5; 9;', 10), 115 | ('9; regresa 3 * 6; 9;', 18), 116 | (''' 117 | si (10 > 1) { 118 | si (20 > 10) { 119 | regresa 1; 120 | } 121 | 122 | regresa 0; 123 | } 124 | ''', 1) 125 | ] 126 | 127 | for source, expected in tests: 128 | evaluated = self._evaluate_tests(source) 129 | self._test_integer_object(evaluated, expected) 130 | 131 | def test_error_handling(self) -> None: 132 | tests: List[Tuple[str, str]] = [ 133 | ('5 + verdadero', 134 | 'Discrepancia de tipos: INTEGER + BOOLEAN'), 135 | ('5 + verdadero; 9;', 136 | 'Discrepancia de tipos: INTEGER + BOOLEAN'), 137 | ('-verdadero', 138 | 'Operador desconocido: -BOOLEAN'), 139 | ('verdadero + falso;', 140 | 'Operador desconocido: BOOLEAN + BOOLEAN'), 141 | ('5; verdadero - falso; 10;', 142 | 'Operador desconocido: BOOLEAN - BOOLEAN'), 143 | (''' 144 | si (10 > 7) { 145 | regresa verdadero + falso; 146 | } 147 | ''', 148 | 'Operador desconocido: BOOLEAN + BOOLEAN'), 149 | (''' 150 | si (10 > 1) { 151 | si (verdadero) { 152 | regresa verdadero * falso 153 | } 154 | 155 | regresa 1; 156 | } 157 | ''', 158 | 'Operador desconocido: BOOLEAN * BOOLEAN'), 159 | (''' 160 | si (5 < 2) { 161 | regresa 1; 162 | } si_no { 163 | regresa verdadero / falso; 164 | } 165 | ''', 166 | 'Operador desconocido: BOOLEAN / BOOLEAN'), 167 | ('foobar;', 168 | 'Identificador no encontrado: foobar'), 169 | ('"Foo" - "Bar";', 170 | 'Operador desconocido: STRING - STRING'), 171 | ] 172 | 173 | for source, expected in tests: 174 | evaluated = self._evaluate_tests(source) 175 | 176 | self._test_error_object(evaluated, expected) 177 | 178 | def test_assignment_statements(self) -> None: 179 | tests: List[Tuple[str, int]] = [ 180 | ('variable a = 5; a;', 5), 181 | ('variable a = 5 * 5; a;', 25), 182 | ('variable a = 5; variable b = a; b;', 5), 183 | ('variable a = 5; variable b = a; variable c = a + b + 5; c;', 15), 184 | ] 185 | 186 | for source, expected in tests: 187 | evaluated = self._evaluate_tests(source) 188 | self._test_integer_object(evaluated, expected) 189 | 190 | def test_function_evaluation(self) -> None: 191 | source: str = 'procedimiento(x) { x + 2; };' 192 | 193 | evaluated = self._evaluate_tests(source) 194 | 195 | self.assertIsInstance(evaluated, Function) 196 | 197 | evaluated = cast(Function, evaluated) 198 | 199 | self.assertEquals(len(evaluated.parameters), 1) 200 | self.assertEquals(str(evaluated.parameters[0]), 'x') 201 | self.assertEquals(str(evaluated.body), '(x + 2)') 202 | 203 | def test_function_calls(self) -> None: 204 | tests: List[Tuple[str, int]] = [ 205 | ('variable identidad = procedimiento(x) { x }; identidad(5);', 5), 206 | (''' 207 | variable identidad = procedimiento(x) { 208 | regresa x; 209 | }; 210 | identidad(5); 211 | ''', 5), 212 | (''' 213 | variable doble = procedimiento(x) { 214 | regresa 2 * x; 215 | }; 216 | doble(5); 217 | ''', 10), 218 | (''' 219 | variable suma = procedimiento(x, y) { 220 | regresa x + y; 221 | }; 222 | suma(3, 8); 223 | ''', 11), 224 | (''' 225 | variable suma = procedimiento(x, y) { 226 | regresa x + y; 227 | }; 228 | suma(5 + 5, suma(10, 10)); 229 | ''', 30), 230 | ('procedimiento(x) { x }(5)', 5), 231 | ] 232 | 233 | for source, expected in tests: 234 | evaluated = self._evaluate_tests(source) 235 | self._test_integer_object(evaluated, expected) 236 | 237 | def test_string_evaluation(self) -> None: 238 | tests: List[Tuple[str, str]] = [ 239 | ('"Hello world!"', 'Hello world!'), 240 | ('procedimiento() { regresa "Platzi is great"; }()', 241 | 'Platzi is great'), 242 | ] 243 | 244 | for source, expected in tests: 245 | evaluated = self._evaluate_tests(source) 246 | self._test_string_object(evaluated, expected) 247 | 248 | def test_string_concatenation(self) -> None: 249 | tests: List[Tuple[str, str]] = [ 250 | ('"Foo" + "bar";', 'Foobar'), 251 | ('"Hello," + " " + "world!"', 'Hello, world!'), 252 | (''' 253 | variable saludo = procedimiento(nombre) { 254 | regresa "Hola " + nombre + "!"; 255 | }; 256 | saludo("David"); 257 | ''', 258 | 'Hola David!'), 259 | ] 260 | 261 | for source, expected in tests: 262 | evaluated = self._evaluate_tests(source) 263 | self._test_string_object(evaluated, expected) 264 | 265 | def test_string_comparison(self) -> None: 266 | tests: List[Tuple[str, bool]] = [ 267 | ('"a" == "a"', True), 268 | ('"a" != "a"', False), 269 | ('"a" == "b"', False), 270 | ('"a" != "b"', True), 271 | ] 272 | 273 | for source, expected in tests: 274 | evaluated = self._evaluate_tests(source) 275 | self._test_boolean_object(evaluated, expected) 276 | 277 | def test_builtin_functions(self) -> None: 278 | tests: List[Tuple[str, Union[str, int]]] = [ 279 | ('longitud("");', 0), 280 | ('longitud("cuatro");', 6), 281 | ('longitud("Hola mundo");', 10), 282 | ('longitud(1);', 283 | 'argumento para longitud sin soporte, se recibió INTEGER'), 284 | ('longitud("uno", "dos");', 285 | 'número incorrecto de argumentos para longitud, se recibieron 2, se requieren 1'), 286 | ] 287 | 288 | for source, expected in tests: 289 | evaluated = self._evaluate_tests(source) 290 | 291 | if type(expected) == int: 292 | expected = cast(int, expected) 293 | self._test_integer_object(evaluated, expected) 294 | else: 295 | expected = cast(str, expected) 296 | self._test_error_object(evaluated, expected) 297 | 298 | def _evaluate_tests(self, source: str) -> Object: 299 | lexer: Lexer = Lexer(source) 300 | parser: Parser = Parser(lexer) 301 | program = parser.parse_program() 302 | env = Environment() 303 | 304 | evaluated = evaluate(program, env) 305 | 306 | assert evaluated is not None 307 | return evaluated 308 | 309 | def _test_boolean_object(self, evaluated: Object, expected: bool) -> None: 310 | self.assertIsInstance(evaluated, Boolean) 311 | 312 | evaluated = cast(Boolean, evaluated) 313 | self.assertEquals(evaluated.value, expected) 314 | 315 | def _test_error_object(self, evaluated: Object, expected: str) -> None: 316 | self.assertIsInstance(evaluated, Error) 317 | 318 | evaluated = cast(Error, evaluated) 319 | self.assertEquals(evaluated.message, expected) 320 | 321 | def _test_integer_object(self, evaluated: Object, expected: int) -> None: 322 | self.assertIsInstance(evaluated, Integer) 323 | 324 | evaluated = cast(Integer, evaluated) 325 | self.assertEquals(evaluated.value, expected) 326 | 327 | def _test_null_object(self, evaluated: Object) -> None: 328 | self.assertEquals(evaluated, NULL) 329 | 330 | def _test_string_object(self, evaluated: Object, expected: str) -> None: 331 | self.assertIsInstance(evaluated, String) 332 | 333 | evaluated = cast(String, evaluated) 334 | self.assertEquals(evaluated.value, expected) 335 | 336 | -------------------------------------------------------------------------------- /lpp/parser.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | from typing import ( 3 | Callable, 4 | Dict, 5 | List, 6 | Optional, 7 | ) 8 | 9 | from lpp.ast import ( 10 | Block, 11 | Boolean, 12 | Call, 13 | Expression, 14 | ExpressionStatement, 15 | Function, 16 | Identifier, 17 | If, 18 | Infix, 19 | Integer, 20 | LetStatement, 21 | Prefix, 22 | Program, 23 | ReturnStatement, 24 | Statement, 25 | StringLiteral, 26 | ) 27 | from lpp.lexer import Lexer 28 | from lpp.token import ( 29 | Token, 30 | TokenType, 31 | ) 32 | 33 | 34 | PrefixParseFn = Callable[[], Optional[Expression]] 35 | InfixParseFn = Callable[[Expression], Optional[Expression]] 36 | PrefixParseFns = Dict[TokenType, PrefixParseFn] 37 | InfixParseFns = Dict[TokenType, InfixParseFn] 38 | 39 | 40 | class Precedence(IntEnum): 41 | LOWEST = 1 42 | EQUALS = 2 43 | LESSGREATER = 3 44 | SUM = 4 45 | PRODUCT = 5 46 | PREFIX = 6 47 | CALL = 7 48 | 49 | 50 | PRECEDENCES: Dict[TokenType, Precedence] = { 51 | TokenType.EQ: Precedence.EQUALS, 52 | TokenType.NOT_EQ: Precedence.EQUALS, 53 | TokenType.LT: Precedence.LESSGREATER, 54 | TokenType.GT: Precedence.LESSGREATER, 55 | TokenType.PLUS: Precedence.SUM, 56 | TokenType.MINUS: Precedence.SUM, 57 | TokenType.DIVISION: Precedence.PRODUCT, 58 | TokenType.MULTIPLICATION: Precedence.PRODUCT, 59 | TokenType.LPAREN: Precedence.CALL, 60 | } 61 | 62 | 63 | class Parser: 64 | 65 | def __init__(self, lexer: Lexer) -> None: 66 | self._lexer = lexer 67 | self._current_token: Optional[Token] = None 68 | self._peek_token: Optional[Token] = None 69 | self._errors: List[str] = [] 70 | 71 | self._prefix_parse_fns: PrefixParseFns = self._register_prefix_fns() 72 | self._infix_parse_fns: InfixParseFns = self._register_infix_fns() 73 | 74 | self._advance_tokens() 75 | self._advance_tokens() 76 | 77 | @property 78 | def errors(self) -> List[str]: 79 | return self._errors 80 | 81 | def parse_program(self) -> Program: 82 | program = Program(statements=[]) 83 | 84 | assert self._current_token is not None 85 | while self._current_token.token_type != TokenType.EOF: 86 | statement = self._parse_statement() 87 | if statement is not None: 88 | program.statements.append(statement) 89 | 90 | self._advance_tokens() 91 | 92 | return program 93 | 94 | def _advance_tokens(self) -> None: 95 | self._current_token = self._peek_token 96 | self._peek_token = self._lexer.next_token() 97 | 98 | def _current_precedence(self) -> Precedence: 99 | assert self._current_token is not None 100 | try: 101 | return PRECEDENCES[self._current_token.token_type] 102 | except KeyError: 103 | return Precedence.LOWEST 104 | 105 | def _expected_token(self, token_type: TokenType) -> bool: 106 | assert self._peek_token is not None 107 | if self._peek_token.token_type == token_type: 108 | self._advance_tokens() 109 | 110 | return True 111 | 112 | self._expected_token_error(token_type) 113 | return False 114 | 115 | def _expected_token_error(self, token_type: TokenType) -> None: 116 | assert self._peek_token is not None 117 | error = f'Se esperaba que el siguiente token fuera {token_type} ' + \ 118 | f'pero se obtuvo {self._peek_token.token_type}' 119 | self._errors.append(error) 120 | 121 | def _parse_block(self) -> Block: 122 | assert self._current_token is not None 123 | block_statement = Block(token=self._current_token, 124 | statements=[]) 125 | 126 | self._advance_tokens() 127 | 128 | while not self._current_token.token_type == TokenType.RBRACE \ 129 | and not self._current_token.token_type == TokenType.EOF: 130 | statement = self._parse_statement() 131 | 132 | if statement: 133 | block_statement.statements.append(statement) 134 | 135 | self._advance_tokens() 136 | 137 | return block_statement 138 | 139 | def _parse_boolean(self) -> Boolean: 140 | assert self._current_token is not None 141 | 142 | return Boolean(self._current_token, 143 | self._current_token.token_type == TokenType.TRUE) 144 | 145 | def _parse_call(self, function: Expression) -> Call: 146 | assert self._current_token is not None 147 | call = Call(self._current_token, function) 148 | call.arguments = self._parse_call_arguments() 149 | 150 | return call 151 | 152 | def _parse_call_arguments(self) -> Optional[List[Expression]]: 153 | arguments: List[Expression] = [] 154 | 155 | assert self._peek_token is not None 156 | if self._peek_token.token_type == TokenType.RPAREN: 157 | self._advance_tokens() 158 | 159 | return arguments 160 | 161 | self._advance_tokens() 162 | if expression := self._parse_expression(Precedence.LOWEST): 163 | arguments.append(expression) 164 | 165 | while self._peek_token.token_type == TokenType.COMMA: 166 | self._advance_tokens() 167 | self._advance_tokens() 168 | 169 | if expression := self._parse_expression(Precedence.LOWEST): 170 | arguments.append(expression) 171 | 172 | if not self._expected_token(TokenType.RPAREN): 173 | return None 174 | 175 | return arguments 176 | 177 | def _parse_expression(self, precedence: Precedence) -> Optional[Expression]: 178 | assert self._current_token is not None 179 | try: 180 | prefix_parse_fn = self._prefix_parse_fns[self._current_token.token_type] 181 | except KeyError: 182 | message = f'No se encontro ninguna función para parsear {self._current_token.literal}' 183 | self._errors.append(message) 184 | 185 | return None 186 | 187 | left_expression = prefix_parse_fn() 188 | 189 | assert self._peek_token is not None 190 | while not self._peek_token.token_type == TokenType.SEMICOLON and \ 191 | precedence < self._peek_precedence(): 192 | try: 193 | infix_parse_fn = self._infix_parse_fns[self._peek_token.token_type] 194 | 195 | self._advance_tokens() 196 | 197 | assert left_expression is not None 198 | left_expression = infix_parse_fn(left_expression) 199 | except KeyError: 200 | return left_expression 201 | 202 | return left_expression 203 | 204 | def _parse_expression_statement(self) -> Optional[ExpressionStatement]: 205 | assert self._current_token is not None 206 | expression_statement = ExpressionStatement(token=self._current_token) 207 | 208 | expression_statement.expression = self._parse_expression(Precedence.LOWEST) 209 | 210 | assert self._peek_token is not None 211 | if self._peek_token.token_type == TokenType.SEMICOLON: 212 | self._advance_tokens() 213 | 214 | return expression_statement 215 | 216 | def _parse_function(self) -> Optional[Function]: 217 | assert self._current_token is not None 218 | function = Function(token=self._current_token) 219 | 220 | if not self._expected_token(TokenType.LPAREN): 221 | return None 222 | 223 | function.parameters = self._parse_function_parameters() 224 | 225 | if not self._expected_token(TokenType.LBRACE): 226 | return None 227 | 228 | function.body = self._parse_block() 229 | 230 | return function 231 | 232 | def _parse_function_parameters(self) -> List[Identifier]: 233 | params: List[Identifier] = [] 234 | 235 | assert self._peek_token is not None 236 | if self._peek_token.token_type == TokenType.RPAREN: 237 | self._advance_tokens() 238 | 239 | return params 240 | 241 | self._advance_tokens() 242 | 243 | assert self._current_token is not None 244 | identifier = Identifier(token=self._current_token, 245 | value=self._current_token.literal) 246 | params.append(identifier) 247 | 248 | while self._peek_token.token_type == TokenType.COMMA: 249 | self._advance_tokens() 250 | self._advance_tokens() 251 | 252 | identifier = Identifier(token=self._current_token, 253 | value=self._current_token.literal) 254 | params.append(identifier) 255 | 256 | if not self._expected_token(TokenType.RPAREN): 257 | return [] 258 | 259 | return params 260 | 261 | def _parse_identifier(self) -> Identifier: 262 | assert self._current_token is not None 263 | 264 | return Identifier(token=self._current_token, 265 | value=self._current_token.literal) 266 | 267 | def _parse_infix_expression(self, left: Expression) -> Infix: 268 | assert self._current_token is not None 269 | infix = Infix(token=self._current_token, 270 | operator=self._current_token.literal, 271 | left=left) 272 | 273 | precedence = self._current_precedence() 274 | 275 | self._advance_tokens() 276 | 277 | infix.right = self._parse_expression(precedence) 278 | 279 | return infix 280 | 281 | def _parse_integer(self) -> Optional[Integer]: 282 | assert self._current_token is not None 283 | integer = Integer(token=self._current_token) 284 | 285 | try: 286 | integer.value = int(self._current_token.literal) 287 | except ValueError: 288 | message = f'No se ha podido parsear {self._current_token.literal} ' + \ 289 | 'como entero.' 290 | self._errors.append(message) 291 | 292 | return None 293 | 294 | return integer 295 | 296 | def _parse_grouped_expression(self) -> Optional[Expression]: 297 | self._advance_tokens() 298 | 299 | expression = self._parse_expression(Precedence.LOWEST) 300 | 301 | if not self._expected_token(TokenType.RPAREN): 302 | return None 303 | 304 | return expression 305 | 306 | def _parse_if(self) -> Optional[If]: 307 | assert self._current_token is not None 308 | if_expression = If(self._current_token) 309 | 310 | if not self._expected_token(TokenType.LPAREN): 311 | return None 312 | 313 | self._advance_tokens() 314 | 315 | if_expression.condition = self._parse_expression(Precedence.LOWEST) 316 | 317 | if not self._expected_token(TokenType.RPAREN): 318 | return None 319 | 320 | if not self._expected_token(TokenType.LBRACE): 321 | return None 322 | 323 | if_expression.consequence = self._parse_block() 324 | 325 | assert self._peek_token is not None 326 | if self._peek_token.token_type == TokenType.ELSE: 327 | self._advance_tokens() 328 | 329 | if not self._expected_token(TokenType.LBRACE): 330 | return None 331 | 332 | if_expression.alternative = self._parse_block() 333 | 334 | return if_expression 335 | 336 | def _parse_let_statement(self) -> Optional[LetStatement]: 337 | assert self._current_token is not None 338 | let_statement = LetStatement(token=self._current_token) 339 | 340 | if not self._expected_token(TokenType.IDENT): 341 | return None 342 | 343 | let_statement.name = self._parse_identifier() 344 | 345 | if not self._expected_token(TokenType.ASSIGN): 346 | return None 347 | 348 | self._advance_tokens() 349 | 350 | let_statement.value = self._parse_expression(Precedence.LOWEST) 351 | 352 | assert self._peek_token is not None 353 | if self._peek_token.token_type == TokenType.SEMICOLON: 354 | self._advance_tokens() 355 | 356 | return let_statement 357 | 358 | def _parse_prefix_expression(self) -> Expression: 359 | assert self._current_token is not None 360 | prefix_expression = Prefix(token=self._current_token, 361 | operator=self._current_token.literal) 362 | 363 | self._advance_tokens() 364 | 365 | prefix_expression.right = self._parse_expression(Precedence.PREFIX) 366 | 367 | return prefix_expression 368 | 369 | def _parse_return_statement(self) -> Optional[ReturnStatement]: 370 | assert self._current_token is not None 371 | return_statement = ReturnStatement(token=self._current_token) 372 | 373 | self._advance_tokens() 374 | 375 | return_statement.return_value = self._parse_expression(Precedence.LOWEST) 376 | 377 | assert self._peek_token is not None 378 | if self._peek_token.token_type == TokenType.SEMICOLON: 379 | self._advance_tokens() 380 | 381 | return return_statement 382 | 383 | def _parse_statement(self) -> Optional[Statement]: 384 | assert self._current_token is not None 385 | if self._current_token.token_type == TokenType.LET: 386 | return self._parse_let_statement() 387 | elif self._current_token.token_type == TokenType.RETURN: 388 | return self._parse_return_statement() 389 | else: 390 | return self._parse_expression_statement() 391 | 392 | def _parse_string_literal(self) -> Expression: 393 | assert self._current_token is not None 394 | return StringLiteral(token=self._current_token, 395 | value=self._current_token.literal) 396 | 397 | def _peek_precedence(self) -> Precedence: 398 | assert self._peek_token is not None 399 | try: 400 | return PRECEDENCES[self._peek_token.token_type] 401 | except KeyError: 402 | return Precedence.LOWEST 403 | 404 | def _register_infix_fns(self) -> InfixParseFns: 405 | return { 406 | TokenType.PLUS: self._parse_infix_expression, 407 | TokenType.MINUS: self._parse_infix_expression, 408 | TokenType.DIVISION: self._parse_infix_expression, 409 | TokenType.MULTIPLICATION: self._parse_infix_expression, 410 | TokenType.EQ: self._parse_infix_expression, 411 | TokenType.NOT_EQ: self._parse_infix_expression, 412 | TokenType.LT: self._parse_infix_expression, 413 | TokenType.GT: self._parse_infix_expression, 414 | TokenType.LPAREN: self._parse_call, 415 | } 416 | 417 | def _register_prefix_fns(self) -> PrefixParseFns: 418 | return { 419 | TokenType.FALSE: self._parse_boolean, 420 | TokenType.IDENT: self._parse_identifier, 421 | TokenType.FUNCTION: self._parse_function, 422 | TokenType.IF: self._parse_if, 423 | TokenType.INT: self._parse_integer, 424 | TokenType.LPAREN:self._parse_grouped_expression, 425 | TokenType.MINUS: self._parse_prefix_expression, 426 | TokenType.NEGATION: self._parse_prefix_expression, 427 | TokenType.TRUE: self._parse_boolean, 428 | TokenType.STRING: self._parse_string_literal, 429 | } 430 | 431 | -------------------------------------------------------------------------------- /tests/parser_test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from typing import ( 3 | Any, 4 | cast, 5 | List, 6 | Tuple, 7 | Type, 8 | ) 9 | 10 | from lpp.ast import ( 11 | Block, 12 | Boolean, 13 | Call, 14 | Expression, 15 | ExpressionStatement, 16 | Function, 17 | Identifier, 18 | If, 19 | Infix, 20 | Integer, 21 | LetStatement, 22 | Prefix, 23 | Program, 24 | ReturnStatement, 25 | StringLiteral, 26 | ) 27 | from lpp.lexer import Lexer 28 | from lpp.parser import Parser 29 | 30 | 31 | class ParserTest(TestCase): 32 | 33 | def test_parse_program(self) -> None: 34 | source: str = 'variable x = 5;' 35 | lexer: Lexer = Lexer(source) 36 | parser: Parser = Parser(lexer) 37 | 38 | program: Program = parser.parse_program() 39 | 40 | self.assertIsNotNone(program) 41 | self.assertIsInstance(program, Program) 42 | 43 | def test_let_statements(self) -> None: 44 | source: str = ''' 45 | variable x = 5; 46 | variable y = 10; 47 | variable foo = 20; 48 | variable bar = verdadero; 49 | ''' 50 | lexer: Lexer = Lexer(source) 51 | parser: Parser = Parser(lexer) 52 | 53 | program: Program = parser.parse_program() 54 | 55 | self.assertEqual(len(program.statements), 4) 56 | 57 | expected_identifiers_and_values: List[Tuple[str, Any]] = [ 58 | ('x', 5), 59 | ('y', 10), 60 | ('foo', 20), 61 | ('bar', True), 62 | ] 63 | 64 | for statement, (expected_identifier, expected_value) in zip( 65 | program.statements, expected_identifiers_and_values): 66 | self.assertEqual(statement.token_literal(), 'variable') 67 | self.assertIsInstance(statement, LetStatement) 68 | 69 | let_statement = cast(LetStatement, statement) 70 | 71 | assert let_statement.name is not None 72 | self._test_identifier(let_statement.name, expected_identifier) 73 | 74 | assert let_statement.value is not None 75 | self._test_literal_expression(let_statement.value, expected_value) 76 | 77 | def test_names_in_let_statements(self) -> None: 78 | source: str = ''' 79 | variable x = 5; 80 | variable y = 10; 81 | variable foo = 20; 82 | ''' 83 | lexer: Lexer = Lexer(source) 84 | parser: Parser = Parser(lexer) 85 | 86 | program: Program = parser.parse_program() 87 | 88 | names: List[str] = [] 89 | for statement in program.statements: 90 | statement = cast(LetStatement, statement) 91 | assert statement.name is not None 92 | names.append(statement.name.value) 93 | 94 | expected_names: List[str] = ['x', 'y', 'foo'] 95 | 96 | self.assertEquals(names, expected_names) 97 | 98 | def test_parse_errors(self) -> None: 99 | source: str = 'variable x 5;' 100 | lexer: Lexer = Lexer(source) 101 | parser: Parser = Parser(lexer) 102 | 103 | program: Program = parser.parse_program() 104 | 105 | self.assertEquals(len(parser.errors), 1) 106 | 107 | def test_return_statements(self) -> None: 108 | source: str = ''' 109 | regresa 5; 110 | regresa foo; 111 | regresa verdadero; 112 | regresa falso; 113 | ''' 114 | lexer: Lexer = Lexer(source) 115 | parser: Parser = Parser(lexer) 116 | 117 | program: Program = parser.parse_program() 118 | 119 | expected_return_values: List[Any] = [ 120 | 5, 121 | 'foo', 122 | True, 123 | False, 124 | ] 125 | 126 | self.assertEqual(len(program.statements), 4) 127 | for statement, expected_return_value in zip( 128 | program.statements, expected_return_values): 129 | self.assertEqual(statement.token_literal(), 'regresa') 130 | self.assertIsInstance(statement, ReturnStatement) 131 | 132 | return_statement = cast(ReturnStatement, statement) 133 | 134 | assert return_statement.return_value is not None 135 | self._test_literal_expression(return_statement.return_value, 136 | expected_return_value) 137 | 138 | def test_identifier_expression(self) -> None: 139 | source: str = 'foobar;' 140 | lexer: Lexer = Lexer(source) 141 | parser: Parser = Parser(lexer) 142 | 143 | program = parser.parse_program() 144 | 145 | self._test_program_statements(parser, program) 146 | 147 | expression_statement = cast(ExpressionStatement, program.statements[0]) 148 | 149 | assert expression_statement.expression is not None 150 | self._test_literal_expression(expression_statement.expression, 'foobar') 151 | 152 | def test_integer_expression(self) -> None: 153 | source: str = '5;' 154 | lexer: Lexer = Lexer(source) 155 | parser: Parser = Parser(lexer) 156 | 157 | program: Program = parser.parse_program() 158 | 159 | self._test_program_statements(parser, program) 160 | 161 | expression_statement = cast(ExpressionStatement, program.statements[0]) 162 | 163 | assert expression_statement.expression is not None 164 | self._test_literal_expression(expression_statement.expression, 5) 165 | 166 | def test_prefix_expressions(self) -> None: 167 | source: str = '!5; -15; !verdadero; !falso;' 168 | lexer: Lexer = Lexer(source) 169 | parser: Parser = Parser(lexer) 170 | 171 | program: Program = parser.parse_program() 172 | 173 | self._test_program_statements(parser, program, expected_statement_count=4) 174 | 175 | for statement, (expected_operator, expected_value) in zip( 176 | program.statements, [('!', 5), ('-', 15), ('!', True), ('!', False)]): 177 | statement = cast(ExpressionStatement, statement) 178 | self.assertIsInstance(statement.expression, Prefix) 179 | 180 | prefix = cast(Prefix, statement.expression) 181 | self.assertEquals(prefix.operator, expected_operator) 182 | 183 | assert prefix.right is not None 184 | self._test_literal_expression(prefix.right, expected_value) 185 | 186 | def test_infix_expressions(self) -> None: 187 | source: str = ''' 188 | 5 + 5; 189 | 5 - 5; 190 | 5 * 5; 191 | 5 / 5; 192 | 5 > 5; 193 | 5 < 5; 194 | 5 == 5; 195 | 5 != 5; 196 | verdadero == verdadero; 197 | verdadero != falso; 198 | falso == falso; 199 | ''' 200 | lexer: Lexer = Lexer(source) 201 | parser: Parser = Parser(lexer) 202 | 203 | program: Program = parser.parse_program() 204 | 205 | self._test_program_statements(parser, program, expected_statement_count=11) 206 | 207 | expected_operators_and_values: List[Tuple[Any, str, Any]] = [ 208 | (5, '+', 5), 209 | (5, '-', 5), 210 | (5, '*', 5), 211 | (5, '/', 5), 212 | (5, '>', 5), 213 | (5, '<', 5), 214 | (5, '==', 5), 215 | (5, '!=', 5), 216 | (True, '==', True), 217 | (True, '!=', False), 218 | (False, '==', False), 219 | ] 220 | 221 | for statement, (expected_left, expected_operator, expected_right) in zip( 222 | program.statements, expected_operators_and_values): 223 | statement = cast(ExpressionStatement, statement) 224 | assert statement.expression is not None 225 | self.assertIsInstance(statement.expression, Infix) 226 | self._test_infix_expression(statement.expression, 227 | expected_left, 228 | expected_operator, 229 | expected_right) 230 | 231 | def test_operator_precedence(self) -> None: 232 | test_sources: List[Tuple[str, str, int]] = [ 233 | ('-a * b;', '((-a) * b)', 1), 234 | ('!-a;', '(!(-a))', 1), 235 | ('a + b + c;', '((a + b) + c)', 1), 236 | ('a + b - c;', '((a + b) - c)', 1), 237 | ('a * b * c;', '((a * b) * c)', 1), 238 | ('a * b / c;', '((a * b) / c)', 1), 239 | ('a + b / c;', '(a + (b / c))', 1), 240 | ('a + b * c + d / e - f;', '(((a + (b * c)) + (d / e)) - f)', 1), 241 | ('3 + 4; -5 * 5;', '(3 + 4)((-5) * 5)', 2), 242 | ('5 > 4 == 3 < 4;', '((5 > 4) == (3 < 4))', 1), 243 | ('5 < 4 != 3 > 4;', '((5 < 4) != (3 > 4))', 1), 244 | ('3 + 4 * 5 == 3 * 1 + 4 * 5;', '((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))', 1), 245 | ('verdadero;', 'verdadero', 1), 246 | ('falso;', 'falso', 1), 247 | ('3 > 5 == verdadero;', '((3 > 5) == verdadero)', 1), 248 | ('3 < 5 == falso;', '((3 < 5) == falso)', 1), 249 | ('1 + (2 + 3) + 4;', '((1 + (2 + 3)) + 4)', 1), 250 | ('(5 + 5) * 2;', '((5 + 5) * 2)', 1), 251 | ('2 / (5 + 5);', '(2 / (5 + 5))', 1), 252 | ('-(5 + 5);', '(-(5 + 5))', 1), 253 | ('!(verdadero == verdadero);', '(!(verdadero == verdadero))', 1), 254 | ('a + suma(b * c) + d;', '((a + suma((b * c))) + d)', 1), 255 | ('suma(a, b, 1, 2 * 3, 4 + 5, suma(6, 7 * 8));', 256 | 'suma(a, b, 1, (2 * 3), (4 + 5), suma(6, (7 * 8)))', 1), 257 | ('suma(a + b + c * d / f + g);', 'suma((((a + b) + ((c * d) / f)) + g))', 1), 258 | ] 259 | 260 | for source, expected_result, expected_statement_count in test_sources: 261 | lexer: Lexer = Lexer(source) 262 | parser: Parser = Parser(lexer) 263 | 264 | program: Program = parser.parse_program() 265 | 266 | self._test_program_statements(parser, program, expected_statement_count) 267 | self.assertEquals(str(program), expected_result) 268 | 269 | def test_boolean_expression(self) -> None: 270 | source: str = 'verdadero; falso;' 271 | lexer: Lexer = Lexer(source) 272 | parser: Parser = Parser(lexer) 273 | 274 | program: Program = parser.parse_program() 275 | 276 | self._test_program_statements(parser, program, expected_statement_count=2) 277 | 278 | for statement, expected_value in zip(program.statements, [True, False]): 279 | expression_statement = cast(ExpressionStatement, statement) 280 | assert expression_statement.expression is not None 281 | self._test_literal_expression(expression_statement.expression, expected_value) 282 | 283 | def test_if_expression(self): 284 | source: str = 'si (x < y) { z }' 285 | lexer: Lexer = Lexer(source) 286 | parser: Parser = Parser(lexer) 287 | 288 | program: Program = parser.parse_program() 289 | 290 | self._test_program_statements(parser, program) 291 | 292 | # Test correct node type 293 | if_expression = cast(If, cast(ExpressionStatement, program.statements[0]).expression) 294 | self.assertIsInstance(if_expression, If) 295 | 296 | # Test condition 297 | assert if_expression.condition is not None 298 | self._test_infix_expression(if_expression.condition, 'x', '<', 'y') 299 | 300 | # Test consequence 301 | assert if_expression.consequence is not None 302 | self.assertIsInstance(if_expression.consequence, Block) 303 | self.assertEquals(len(if_expression.consequence.statements), 1) 304 | 305 | consequence_statement = cast(ExpressionStatement, if_expression.consequence.statements[0]) 306 | assert consequence_statement.expression is not None 307 | self._test_identifier(consequence_statement.expression, 'z') 308 | 309 | # Test alternative 310 | self.assertIsNone(if_expression.alternative) 311 | 312 | def test_if_else_expression(self) -> None: 313 | source: str = 'si (x != y) { x } si_no { y }' 314 | lexer: Lexer = Lexer(source) 315 | parser: Parser = Parser(lexer) 316 | 317 | program: Program = parser.parse_program() 318 | 319 | self._test_program_statements(parser, program) 320 | 321 | # Test correct node type 322 | if_expression = cast(If, cast(ExpressionStatement, program.statements[0]).expression) 323 | self.assertIsInstance(if_expression, If) 324 | 325 | # Test condition 326 | assert if_expression.condition is not None 327 | self._test_infix_expression(if_expression.condition, 'x', '!=', 'y') 328 | 329 | # Test consequence 330 | assert if_expression.consequence is not None 331 | self.assertIsInstance(if_expression.consequence, Block) 332 | self.assertEquals(len(if_expression.consequence.statements), 1) 333 | 334 | consequence_statement = cast(ExpressionStatement, if_expression.consequence.statements[0]) 335 | assert consequence_statement.expression is not None 336 | self._test_identifier(consequence_statement.expression, 'x') 337 | 338 | # Test alternative 339 | assert if_expression.alternative is not None 340 | self.assertIsInstance(if_expression.alternative, Block) 341 | self.assertEquals(len(if_expression.alternative.statements), 1) 342 | 343 | alternative_statement = cast(ExpressionStatement, if_expression.alternative.statements[0]) 344 | assert alternative_statement.expression is not None 345 | self._test_identifier(alternative_statement.expression, 'y') 346 | 347 | def test_function_literal(self) -> None: 348 | source: str = 'procedimiento(x, y) { x + y }' 349 | lexer: Lexer = Lexer(source) 350 | parser: Parser = Parser(lexer) 351 | 352 | program: Program = parser.parse_program() 353 | 354 | self._test_program_statements(parser, program) 355 | 356 | # Test correct node type 357 | function_literal = cast(Function, cast(ExpressionStatement, program.statements[0]).expression) 358 | self.assertIsInstance(function_literal, Function) 359 | 360 | # Test params 361 | self.assertEquals(len(function_literal.parameters), 2) 362 | self._test_literal_expression(function_literal.parameters[0], 'x') 363 | self._test_literal_expression(function_literal.parameters[1], 'y') 364 | 365 | # Test body 366 | assert function_literal.body is not None 367 | self.assertEquals(len(function_literal.body.statements), 1) 368 | 369 | body = cast(ExpressionStatement, function_literal.body.statements[0]) 370 | assert body.expression is not None 371 | self._test_infix_expression(body.expression, 'x', '+', 'y') 372 | 373 | def test_function_parameters(self) -> None: 374 | tests = [ 375 | {'input': 'procedimiento() {};', 'expected_params': []}, 376 | {'input': 'procedimiento(x) {};', 'expected_params': ['x']}, 377 | {'input': 'procedimiento(x, y, z) {};', 'expected_params': ['x', 'y', 'z']}, 378 | ] 379 | 380 | for test in tests: 381 | lexer: Lexer = Lexer(test['input']) # type: ignore 382 | parser: Parser = Parser(lexer) 383 | 384 | program: Program = parser.parse_program() 385 | 386 | function = cast(Function, cast(ExpressionStatement, program.statements[0]).expression) 387 | 388 | self.assertEquals(len(function.parameters), len(test['expected_params'])) 389 | 390 | for idx, param in enumerate(test['expected_params']): 391 | self._test_literal_expression(function.parameters[idx], param) 392 | 393 | def test_call_expression(self) -> None: 394 | source: str = 'suma(1, 2 * 3, 4 + 5);' 395 | lexer: Lexer = Lexer(source) 396 | parser: Parser = Parser(lexer) 397 | 398 | program: Program = parser.parse_program() 399 | 400 | self._test_program_statements(parser, program) 401 | 402 | call = cast(Call, cast(ExpressionStatement, program.statements[0]).expression) 403 | self.assertIsInstance(call, Call) 404 | self._test_identifier(call.function, 'suma') 405 | 406 | # Test arguments 407 | assert call.arguments is not None 408 | self.assertEquals(len(call.arguments), 3) 409 | self._test_literal_expression(call.arguments[0], 1) 410 | self._test_infix_expression(call.arguments[1], 2, '*', 3) 411 | self._test_infix_expression(call.arguments[2], 4, '+', 5) 412 | 413 | def test_string_literal_expressions(self) -> None: 414 | source: str = '"hello world!"' 415 | lexer: Lexer = Lexer(source) 416 | parser: Parser = Parser(lexer) 417 | 418 | program: Program = parser.parse_program() 419 | 420 | expression_statement = cast(ExpressionStatement, program.statements[0]) 421 | string_literal = cast(StringLiteral, expression_statement.expression) 422 | 423 | self.assertIsInstance(string_literal, StringLiteral) 424 | self.assertEquals(string_literal.value, 'hello world!') 425 | 426 | def _test_boolean(self, 427 | expression: Expression, 428 | expected_value: bool) -> None: 429 | self.assertIsInstance(expression, Boolean) 430 | 431 | expression = cast(Boolean, expression) 432 | self.assertEquals(expression.value, expected_value) 433 | self.assertEquals(expression.token.literal, 'verdadero' if expected_value else 'falso') 434 | 435 | def _test_infix_expression(self, 436 | expression: Expression, 437 | expected_left: Any, 438 | expected_operator: str, 439 | expected_right: Any) -> None: 440 | infix = cast(Infix, expression) 441 | 442 | assert infix.left is not None 443 | self._test_literal_expression(infix.left, expected_left) 444 | 445 | self.assertEquals(infix.operator, expected_operator) 446 | 447 | assert infix.right is not None 448 | self._test_literal_expression(infix.right, expected_right) 449 | 450 | def _test_program_statements(self, 451 | parser: Parser, 452 | program: Program, 453 | expected_statement_count: int = 1) -> None: 454 | if parser.errors: 455 | print(parser.errors) 456 | 457 | self.assertEquals(len(parser.errors), 0) 458 | self.assertEquals(len(program.statements), expected_statement_count) 459 | self.assertIsInstance(program.statements[0], ExpressionStatement) 460 | 461 | def _test_literal_expression(self, 462 | expression: Expression, 463 | expected_value: Any) -> None: 464 | value_type: Type = type(expected_value) 465 | 466 | if value_type == str: 467 | self._test_identifier(expression, expected_value) 468 | elif value_type == int: 469 | self._test_integer(expression, expected_value) 470 | elif value_type == bool: 471 | self._test_boolean(expression, expected_value) 472 | else: 473 | self.fail(f'Unhandled type of expression. Got={value_type}') 474 | 475 | def _test_identifier(self, 476 | expression: Expression, 477 | expected_value: str) -> None: 478 | self.assertIsInstance(expression, Identifier) 479 | 480 | expression = cast(Identifier, expression) 481 | self.assertEquals(expression.value, expected_value) 482 | self.assertEquals(expression.token.literal, expected_value) 483 | 484 | def _test_integer(self, 485 | expression: Expression, 486 | expected_value: int) -> None: 487 | self.assertIsInstance(expression, Integer) 488 | 489 | expression = cast(Integer, expression) 490 | self.assertEquals(expression.value, expected_value) 491 | self.assertEquals(expression.token.literal, str(expected_value)) 492 | 493 | --------------------------------------------------------------------------------