diff --git a/.circleci/config.yml b/.circleci/config.yml index dbea0cf..7d9f967 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,8 @@ version: 2 jobs: build: + branches: + ignore: gh-pages docker: - image: circleci/python:3.7.2 steps: diff --git a/.hooks/pre-commit.sh b/.hooks/pre-commit.sh index 6c1953a..bdd36d0 100755 --- a/.hooks/pre-commit.sh +++ b/.hooks/pre-commit.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # Pytest echo "Running pre-commit hooks" diff --git a/README.md b/README.md index 36b7419..184ab0e 100644 --- a/README.md +++ b/README.md @@ -4,28 +4,31 @@ ___ [![License: GPL v3](https://img.shields.io/badge/license-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) -[![Version](https://img.shields.io/badge/version-0.3.0-brightgreen.svg)](https://github.com/hassanalinali/Lesma/blob/master/LICENSE.md) +[![Version](https://img.shields.io/badge/version-0.4.0-brightgreen.svg)](https://github.com/hassanalinali/Lesma/blob/master/LICENSE.md) [![CircleCI](https://circleci.com/gh/hassanalinali/Lesma/tree/master.svg?style=shield)](https://circleci.com/gh/hassanalinali/Lesma/tree/master) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/90fcc06be70d4dd98f54f1bb2713d70c)](https://www.codacy.com/app/hassanalinali/Lesma?utm_source=github.com&utm_medium=referral&utm_content=hassanalinali/Lesma&utm_campaign=Badge_Grade) -**Lesma** is a compiled, gradually typed, imperative and object oriented programming language with a focused on expressiveness, elegancy, and simplicity, while not sacrificing on performance. The compiler is written in Python using LLVM as a backend. +**Lesma** is a compiled, statically typed, imperative and object-oriented programming language with a focus on expressiveness, elegancy, and simplicity, while not sacrificing on performance. The compiler is written in Python (for now, C++ coming) using LLVM as a backend. Currently an early Work in Progress, and **many thanks** to [Ayehavgunne](https://github.com/Ayehavgunne) and his [Mythril](https://github.com/Ayehavgunne/Mythril) project for helping me by showcasing advanced examples of llvmlite and providing a base code. ## Features -- **it's fast**, because it should be so, but it won't ever oblige you to make an extra effort for the sake of performance -- **it's compiled**, so you can finally distribute your projects without dependencies, and because binary size also matters, a Hello World example would be around 8kb +- **it's fast**, because it should be so, together with LLVM's state of the art optimizations, but it won't ever oblige you to make an extra effort from your side just for the sake of performance +- **it's compiled** both AOT and JIT, so you can finally decide if you just want to run it or compile it and distribute your project without dependencies, and because binary size also matters, a Hello World example would be around 8kb - **it's statically typed** so you don't need to guess the type of the variable if your coworker didn't spend the time to use meaningful names and you can make use of compile-time checks, autocomplete and more - **it's simple and expressive** because the code should be easily readable and it shouldn't make you guess what it does ## Influences - Python +- Swift - Typescript - Lua -- Swift ## Installing -You can pick up the latest release in the [Release tab](https://github.com/hassanalinali/Lesma/releases) and start using it. Lesma is currently being tested and provides binaries only on Unix. Compatibility between operating systems and architectures is not hard to achieve, but simply not a priority. +You can pick up the latest release in [**Releases**](https://github.com/hassanalinali/Lesma/releases) and start using it. Lesma is currently being tested and provides binaries only for Unix. Compatibility between operating systems and architectures is not hard to achieve, but simply not a priority at the moment. + +Windows is also supported but you need to do additional work if you want to compile Lesma code (need to install clang, but this is not properly tested at the moment) and there are issues with Unicode characters, but all the tests pass and everything else seems to work. + In the case that your platform is not oficially supported, you need to build it on your own. ## Documentation @@ -35,7 +38,7 @@ In the case that your platform is not oficially supported, you need to build it ## Build -In order to build Lesma, you need to have [Python 3.7](https://www.python.org/) installed. It's currently tested only on Linux. It makes use of clang to compile the resulting object file currently, so you need it installed, but only running a file doesn't require clang. +In order to build Lesma, you need to have at least [Python 3.5](https://www.python.org/) installed. It's currently tested only on Linux. It makes use of clang to compile the resulting object file currently, so you need it installed, but only running a file doesn't require clang. Clone the repo: ```bash @@ -50,8 +53,8 @@ pip install -r requirements.txt Done! Now you can run the compiler or the interpreter, make a test file and hack your way around. Remember there are examples in the documentation. ```bash -python les.py run test.les -python les.py compile test.les +python src/les.py run test.les +python src/les.py compile test.les ``` Or install pytest and run the unit tests yourself @@ -61,5 +64,5 @@ pytest For advanced usage or help, consult the CLI help menu ```bash -python les.py -h +python src/les.py -h ``` \ No newline at end of file diff --git a/build_dist.sh b/build_dist.sh index fabc75b..2fbb0b3 100755 --- a/build_dist.sh +++ b/build_dist.sh @@ -1 +1,2 @@ -python -m nuitka src/les.py --include-package=lesma --standalone --nofollow-imports --remove-output --python-flag=no_site --plugin-disable=pylint-warnings \ No newline at end of file +#!/usr/bin/env bash +pyinstaller src/les.py -D -n lesma \ No newline at end of file diff --git a/docs/TODO.md b/docs/TODO.md index b7ef1d7..f5ccb51 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,36 +1,25 @@ # TODO ## Fixes -- [ ] Fix Type declaration not expecting square brackets (for lists) -- [ ] Fix input function -- [ ] Fix not being able to return user-defined structs and classes -- [ ] Fix not being able to overload operators on user-defined structs and classes -- [ ] Unicode doesn't print properly on Windows platforms -- [ ] Fix string and list type declaration not working - [ ] Fix base unary operators being applied before user defined ones +- [ ] Fix unicode on windows ## Improvements -- [ ] Allow any type for lists/tuples (currently only int) -- [ ] Allow more operators on different types such as strings - [ ] Improve warning messages - [ ] Add indentation related errors -- [x] Add docs for as and is +- [ ] Add docs for mixed arithmetic - [ ] Remove clang as a dependency - [ ] Move error messages from source files to typechecker -- [ ] Fix array types not working and empty lists -- [ ] Catch struct/class used parameters that are not initialized - [ ] Add support for functions with same name but different parameters - [ ] Fix local - global variable behaviour, currently there's an implicit main func -- [ ] Allow strings to be used in equalities +- [ ] Use dataclasses and static typing as much as possible in source code +- [ ] String/Lists are currently unsupported on: input function, operators, etc. +- [ ] Find a way to use pointers and null for FFI but restrict or disallow it in normal Lesma ## Features -- [ ] Implement Null (maybe someday) -- [x] Implement Tuples +- [ ] Implement Class inheritance - [ ] Implement Dictionary - [ ] Implement 'in' as a boolean result -- [x] Implement anonymous functions - [ ] Implement Closure - [ ] Implement string interpolation -- [ ] Implement Enums -- [x] Implement defer keyword -- [x] Implement fallthrough and change switch's behaviour \ No newline at end of file +- [ ] Implement lambda functions \ No newline at end of file diff --git a/docs/examples.md b/docs/examples.md index 861afe1..8b24d37 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -15,17 +15,24 @@ print('夜のコンサートは最高でした。') a_number: int # Initialize an Integer +# Binary, Hex and Octal numbers supported +bin_num = 0b101010 +octo_num = 0o1272 +hex_num = 0x1272 + π: float = 3.14 # Support for utf-8 variable names number = 23 # Type Inference, int in this case number = number + 5 // 2 ^ 3 # Number operations number+=5 # Operating Assignment +still_inf = inf - 999999 # Still infinity + question = 'what\'s going on' # Escaping -things = [1, 2, 3] # List, homogeneous -other_things = (1, 'Hello') # Tuple, non-homogeneous +things = [1, 2, 3] # List, mutable +other_things = (1.5, 9.5) # Tuple, immutable stuff = {'first_name': 'Samus', 'last_name': 'Aran'} # Dictionary -other_stuff: int[] = [] # Empty Array of Ints +other_stuff: list[int] = [] # Empty Array of ints print(things[1 + 1]) @@ -113,7 +120,7 @@ if my_var is int64 else if my_var as int64 is int64 print("That works") -# Type Aliasing +# Type Declaration type fInt = func[int] -> int def do_stuff(x: int, callback: fInt) -> int @@ -178,39 +185,43 @@ def defer_demo() defer_demo() # prints Hello World! # Enums -enum Colors +enum Color GREEN RED BLUE YELLOW +x: Colors = Color.GREEN +print(x == Color.GREEN) + # Structs struct Circle radius: int x: int - y: int + y: int = 4 -cir: Circle = Circle(radius=5, x=2, y=4) +cir: Circle = Circle(radius=5, x=2) print(cir.radius) # Classes class Vehicle # Constructor - new(year: int, color: str) + def new(year: int, color: str) this.year = year this._color = color # Inheritance class Car(Vehicle) - new(year: int, color='green', hatchback=false) + def new(year: int, color='green', hatchback=false) this.hatchback = hatchback super.Vehicle(year, color) - print_year() -> void - print('This car was made in {this.year}') + def print_year() -> void + print('This car was made in {self.year}') ford = Car(1992) print(ford.hatchback) +ford.print_year() ``` diff --git a/docs/features/classes.md b/docs/features/classes.md new file mode 100644 index 0000000..6403669 --- /dev/null +++ b/docs/features/classes.md @@ -0,0 +1,27 @@ +**Classes** are objects that bundle fields and methods to provide additional functionality that you can further use as a type for any variable, which can then be further expanded using operator overloading and other type-specific behaviour. Classes members can be accessed using a dot `.`. + +Classes **require a constructor to be specified**. + +## Example +```py +class Vehicle + # Constructor + def new(year: int, color: str) + this.year = year + this._color = color + # Privatising the color field, won't be accessible from outside + +# Inheritance +class Car(Vehicle) + def new(year: int, color='green', hatchback=false) + this.hatchback = hatchback + super.Vehicle(year, color) + + def print_year() -> void + print('This car was made in {self.year}') + +ford = Car(1992) + +print(ford.hatchback) +ford.print_year() +``` \ No newline at end of file diff --git a/docs/features/enums.md b/docs/features/enums.md new file mode 100644 index 0000000..53f6d45 --- /dev/null +++ b/docs/features/enums.md @@ -0,0 +1,12 @@ +**Enums** (enumerations) are a set of symbolic names bound to unique, constant values. Enum members can be accessed using the dot `.`. + +## Example +```py +enum Color + Red + White + +white: Color = Color.White # Enum members are accessed using a dot +red: Color = Color.Red +print(white != red) +``` \ No newline at end of file diff --git a/docs/features/keywords.md b/docs/features/keywords.md index 18ecb80..01cdf18 100644 --- a/docs/features/keywords.md +++ b/docs/features/keywords.md @@ -4,12 +4,17 @@ For reference, here are all the keywords categorized by type. | | | | | |---|---|---|---| -| struct | class | true | false | -| if | else | for | while | -| switch | case | default | def | -| const | break | continue | pass | -| void | alias | extern | operator | -| fallthrough | defer +| struct | class | if | else | +| for | while | switch | case | +| default | def | const | break | +| continue | pass | type | extern | +| operator | fallthrough | defer | self | + +## Constant keywords + +| | | | | +|---|---|---|---| +| true | false | inf | ## Operator keywords @@ -26,3 +31,4 @@ For reference, here are all the keywords categorized by type. | int64 | int128 | uint | uint8 | | uint16 | uint32 | uint64 | uint128 | | double | float | str | bool | +| func | list | tuple | void | diff --git a/docs/features/structs.md b/docs/features/structs.md new file mode 100644 index 0000000..5933060 --- /dev/null +++ b/docs/features/structs.md @@ -0,0 +1,15 @@ +**Structs** fill a similar role to Python's dataclasses, which you can describe as "mutable namedtuples with defaults", but they're stricter in the sense that **they're not disguised classes**, you can not define methods or other class-specific behaviour, but they can still be extended by overloading operators and they are still defined as a type in Lesma. Struct members can be accessed using the dot `.`. + +All the fields of a struct are required arguments on initialization unless a default is provided. + +## Example +```py +struct Circle + radius: int + x: int + y: int = 4 + +cir: Circle = Circle(radius=5, x=2) + +print(cir.radius) # Members of a struct are accessed using a dot. +``` \ No newline at end of file diff --git a/docs/features/syntax.md b/docs/features/syntax.md index 811f142..0dfd0d6 100644 --- a/docs/features/syntax.md +++ b/docs/features/syntax.md @@ -1,5 +1,5 @@ - Whitespace is signifiant, indentation uses either tabs or exactly 4 spaces -- Flow control statements, structs, classes and functions require indentation +- Flow control statements, structs, enums, classes and functions require indentation - Lesma's Checker will report any invalid syntax or unrecommended behaviour, such incompatible types for operations, or unused variables. - `_` variable name is used as an ignored result, and is treated differently by the compiler (similar to golang) @@ -29,7 +29,8 @@ struct thing z: double class Example + x: int new(x: int) - this.x = x + self.x = x ``` diff --git a/docs/features/types.md b/docs/features/types.md index 60dcb3e..34bd83a 100644 --- a/docs/features/types.md +++ b/docs/features/types.md @@ -2,10 +2,10 @@ Types are optional in Lesma, you can choose whether you specify them or not. Uns Operations between different types will either be casted to the larger type if the two types are compatible, or give an error otherwise. Two types are compatible if they are different sizes of the same group type (such as ints or floating points). -The type must be either a user-defined alias, a struct or class, or a built-in type. +The type must be either a user-defined type, a struct, enum, class, or a built-in type. !!! warning - Types that are not specified are inferred, this is fundamentally different to dynamic types! + Types that are not specified are inferred, this is fundamentally different in comparison to dynamic types! --- @@ -21,7 +21,7 @@ x = "Hey there" ``` !!! warning - Any not implemented yet! + Any type is not implemented yet! ### Int There are multiple types of ints available based on width and signed/unsigned. They can get a minimum of 8 bits width and a maximum of 128. If the width is not specified, it's by default 64. @@ -49,6 +49,7 @@ Double is double-precision floating-point real number. ```py x: double = 172312.41923 +my_inf = inf # inf is infinity as specified by IEEE 754 ``` ### Str @@ -67,27 +68,24 @@ x: bool = true ``` ### List -Lists are mutable by default, are declared using square paranthesis, have dynamic size, start from 0, and the members are accessed using the their index around square paranthesis. +Lists are mutable by default, are declared using square paranthesis, have dynamic size, start from 0, and the members are accessed using the their index around square paranthesis. The element can be declared between square braces. If the type is omitted, the list will have the type of the elements contained. ```py x = [1,2,3,4] +y: list[double] = [1.5,5.5] print(x[2]) ``` -!!! warning - Lists currently only support integers, no other types! - ### Tuple Tuples are like lists, but immutable, and declared using round paranthesis. +The element type can be declared between square braces. If the type is omitted, the list will have the type of the elements contained. ```py x = (1,5,20) +y: tuple[double] = (1.5,5.5) print(x[0]) ``` -!!! warning - Tuples are not yet implemented! - ### Dict Dictionaries are lists of key-value pairs (similar to hashmaps), and they're mutable @@ -113,6 +111,39 @@ for x in 0..100 ---- +### Func +Lesma supports first class functions, meaning that variables can be assigned functions, and the type func is meant to annotate the type of the function. +Parameter types are included between square braces `[]` separated by comma `,`, and the return type with arrow `->`. + +```py +x: func[int, int] -> int = def (x: int, y: int) -> int + if x > y + return x + y + else + return x * y + +y = def (x: int, y: int) -> int + if x > y + return x + y + else + return x * y + +z: func = def() + print(5) +``` + +### Void +Void is used only for function return types and are used to illustrate that the function does not return a value. If the return type of a function is omitted, void will be used by default. + +```py +def example() -> void + pass + +def example2() # Here the return type is omitted + pass + +``` + ## Type Operations ### Is diff --git a/docs/index.md b/docs/index.md index b95d841..7bbeb75 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,18 +3,18 @@

___ -**Lesma** is a compiled, gradually typed, imperative and object oriented programming language with a focused on expressiveness, elegancy, and simplicity, while not sacrificing on performance. The compiler is written in Python at the moment, using LLVM as a backend. +**Lesma** is a compiled, statically typed, imperative and object oriented programming language with a focus on expressiveness, elegancy, and simplicity, while not sacrificing on performance. The compiler is written in Python (for now, C++ coming) using LLVM as a backend. -Currently an early Work in Progress, and many thanks to [Ayehavgunne](https://github.com/Ayehavgunne) and his [Mythril](https://github.com/Ayehavgunne/Mythril) project for helping me by showcasing advanced examples of llvmlite and providing a base code. +Currently an early Work in Progress, and **many thanks** to [Ayehavgunne](https://github.com/Ayehavgunne) and his [Mythril](https://github.com/Ayehavgunne/Mythril) project for helping me by showcasing advanced examples of llvmlite and providing a base code. ## Features -- **it's fast**, because it should be so, but it won't ever oblige you to make an extra effort for the sake of performance -- **it's compiled**, so you can finally distribute your projects without dependencies, and because binary size also matters, a Hello World example would be around 8kb +- **it's fast**, because it should be so, together with LLVM's state of the art optimizations, but it won't ever oblige you to make an extra effort from your side just for the sake of performance +- **it's compiled** both AOT and JIT, so you can finally decide if you just want to run it or compile it and distribute your project without dependencies, and because binary size also matters, a Hello World example would be around 8kb - **it's statically typed** so you don't need to guess the type of the variable if your coworker didn't spend the time to use meaningful names and you can make use of compile-time checks, autocomplete and more - **it's simple and expressive** because the code should be easily readable and it shouldn't make you guess what it does ## Influences - Python +- Swift - Typescript - Lua -- Swift \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index b9cd03e..b15261e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,9 +14,12 @@ nav: - Features: - Syntax: features/syntax.md - Keywords: features/keywords.md + - Flow Control: features/flow_control.md + - Structs: features/structs.md + - Classes: features/classes.md + - Enums: features/enums.md - Types: features/types.md - Operators: features/operators.md - - Flow Control: features/flow_control.md - TODO: TODO.md - License: license.md diff --git a/setup_env.sh b/setup_env.sh index be7b466..cb164bc 100755 --- a/setup_env.sh +++ b/setup_env.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # Setup Git Hooks cd .git/hooks/ diff --git a/src/les.py b/src/les.py index a4d830b..6bfa1ed 100644 --- a/src/les.py +++ b/src/les.py @@ -68,7 +68,7 @@ def _compile(arg_list): if __name__ == "__main__": - args = docopt(__doc__, version='v0.3.0') + args = docopt(__doc__, version='v0.4.0') if args['compile']: _compile(args) diff --git a/src/lesma/ast.py b/src/lesma/ast.py index 35c5174..23b368b 100644 --- a/src/lesma/ast.py +++ b/src/lesma/ast.py @@ -111,20 +111,27 @@ def __init__(self, value, line_num): self.line_num = line_num -class StructDeclaration(AST): +class EnumDeclaration(AST): def __init__(self, name, fields, line_num): self.name = name self.fields = fields self.line_num = line_num +class StructDeclaration(AST): + def __init__(self, name, fields, defaults, line_num): + self.name = name + self.fields = fields + self.line_num = line_num + self.defaults = defaults + + class ClassDeclaration(AST): - def __init__(self, name, base=None, constructor=None, methods=None, class_fields=None, instance_fields=None): + def __init__(self, name, base=None, methods=None, fields=None, instance_fields=None): self.name = name - self.constructor = constructor self.base = base self.methods = methods - self.class_fields = class_fields + self.fields = fields self.instance_fields = instance_fields @@ -300,7 +307,7 @@ def __init__(self, value, line_num, func_params=None, func_ret_type=None): self.line_num = line_num -class AliasDeclaration(AST): +class TypeDeclaration(AST): def __init__(self, name, collection, line_num): self.name = name self.collection = collection diff --git a/src/lesma/compiler/__init__.py b/src/lesma/compiler/__init__.py index 720572d..2cd8769 100644 --- a/src/lesma/compiler/__init__.py +++ b/src/lesma/compiler/__init__.py @@ -42,32 +42,3 @@ "double": ir.DoubleType(), "float": ir.FloatType(), } - -type_map2 = { - ANY: Any(), - BOOL: Bool(), - UINT: UInt(), - UINT8: UInt8(), - UINT16: UInt16(), - UINT32: UInt32(), - UINT64: UInt64(), - UINT128: UInt128(), - INT: Int(), - INT8: Int8(), - INT16: Int16(), - INT32: Int32(), - INT64: Int64(), - INT128: Int128(), - DOUBLE: Double(), - FLOAT: Float(), - COMPLEX: Complex(), - LIST: List(), - STR: Str(), - TUPLE: Tuple(), - SET: Set(), - DICT: Dict(), - ENUM: Enum(), - STRUCT: Struct(), - FUNC: Func(), - CLASS: Class(), -} diff --git a/src/lesma/compiler/builtins.py b/src/lesma/compiler/builtins.py index 94a291b..1855fcc 100644 --- a/src/lesma/compiler/builtins.py +++ b/src/lesma/compiler/builtins.py @@ -1,5 +1,5 @@ from llvmlite import ir -from lesma.compiler import type_map +from lesma.compiler import type_map, llvm_type_map from lesma.grammar import * import lesma.compiler.llvmlite_custom @@ -14,54 +14,66 @@ one_32 = ir.Constant(type_map[INT32], 1) two_32 = ir.Constant(type_map[INT32], 2) +array_types = [str(type_map[INT])] -def define_builtins(compiler): + +def define_builtins(self): # 0: int size # 1: int capacity # 2: int *data str_struct = ir.LiteralStructType([type_map[INT], type_map[INT], type_map[INT].as_pointer()]) - compiler.define('Dynamic_Array', str_struct) - # compiler.define('Str', str_struct) + self.define('Str', str_struct) str_struct_ptr = str_struct.as_pointer() - compiler.define('Str_ptr', str_struct_ptr) + self.define('Str_ptr', str_struct_ptr) type_map[STR] = str_struct + lint = str(type_map[INT]) + + dynamic_array_init(self, str_struct_ptr, lint) + dynamic_array_double_if_full(self, str_struct_ptr, lint) + dynamic_array_append(self, str_struct_ptr, lint) + dynamic_array_get(self, str_struct_ptr, lint) + dynamic_array_set(self, str_struct_ptr, lint) + dynamic_array_length(self, str_struct_ptr, lint) + + define_create_range(self, str_struct_ptr, lint) + + define_int_to_str(self, str_struct_ptr) + define_bool_to_str(self, str_struct_ptr) + define_print(self, str_struct_ptr) + + +def create_dynamic_array_methods(self, array_type): + if array_type in array_types: + return + array = self.search_scopes('{}_Array'.format(array_type)) + array_ptr = array.as_pointer() + + current_block = self.builder.block + + dynamic_array_init(self, array_ptr, array_type) + dynamic_array_double_if_full(self, array_ptr, array_type) + dynamic_array_append(self, array_ptr, array_type) + dynamic_array_get(self, array_ptr, array_type) + dynamic_array_set(self, array_ptr, array_type) + dynamic_array_length(self, array_ptr, array_type) + + array_types.append(array_type) + + self.position_at_end(current_block) + - dynamic_array_init(compiler, str_struct_ptr) - dynamic_array_double_if_full(compiler, str_struct_ptr) - dynamic_array_append(compiler, str_struct_ptr) - dynamic_array_get(compiler, str_struct_ptr) - dynamic_array_set(compiler, str_struct_ptr) - dynamic_array_length(compiler, str_struct_ptr) - define_create_range(compiler, str_struct_ptr) - define_int_to_str(compiler, str_struct_ptr) - define_bool_to_str(compiler, str_struct_ptr) - define_print(compiler, str_struct_ptr) - - -def create_dynamic_array_methods(compiler, array_type): - array_ptr = compiler.search_scopes('{}_Array'.format(array_type)) - - dynamic_array_init(compiler, array_ptr) - dynamic_array_double_if_full(compiler, array_ptr) - dynamic_array_append(compiler, array_ptr) - dynamic_array_get(compiler, array_ptr) - dynamic_array_set(compiler, array_ptr) - dynamic_array_length(compiler, array_ptr) - define_create_range(compiler, array_ptr) - - -def define_create_range(compiler, dyn_array_struct_ptr): - create_range_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr, type_map[INT], type_map[INT]]) - create_range = ir.Function(compiler.module, create_range_type, 'create_range') +def define_create_range(self, dyn_array_ptr, array_type): + create_range_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, type_map[INT], type_map[INT]]) + create_range = ir.Function(self.module, create_range_type, 'create_range') create_range_entry = create_range.append_basic_block('entry') builder = ir.IRBuilder(create_range_entry) - compiler.builder = builder + self.builder = builder create_range_test = create_range.append_basic_block('test') create_range_body = create_range.append_basic_block('body') create_range_exit = create_range.append_basic_block('exit') builder.position_at_end(create_range_entry) - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(create_range.args[0], array_ptr) start_ptr = builder.alloca(type_map[INT]) builder.store(create_range.args[1], start_ptr) @@ -77,7 +89,7 @@ def define_create_range(compiler, dyn_array_struct_ptr): builder.cbranch(cond, create_range_body, create_range_exit) builder.position_at_end(create_range_body) - builder.call(compiler.module.get_global('dyn_array_append'), [builder.load(array_ptr), builder.load(num_ptr)]) + builder.call(self.module.get_global('{}_array_append'.format(array_type)), [builder.load(array_ptr), builder.load(num_ptr)]) builder.store(builder.add(one, builder.load(num_ptr)), num_ptr) builder.branch(create_range_test) @@ -86,16 +98,16 @@ def define_create_range(compiler, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_init(compiler, dyn_array_struct_ptr): +def dynamic_array_init(self, dyn_array_ptr, array_type): # START - dyn_array_init_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr]) - dyn_array_init = ir.Function(compiler.module, dyn_array_init_type, 'dyn_array_init') + dyn_array_init_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr]) + dyn_array_init = ir.Function(self.module, dyn_array_init_type, '{}_array_init'.format(array_type)) dyn_array_init_entry = dyn_array_init.append_basic_block('entry') builder = ir.IRBuilder(dyn_array_init_entry) - compiler.builder = builder + self.builder = builder dyn_array_init_exit = dyn_array_init.append_basic_block('exit') builder.position_at_end(dyn_array_init_entry) - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(dyn_array_init.args[0], array_ptr) # BODY @@ -107,8 +119,8 @@ def dynamic_array_init(compiler, dyn_array_struct_ptr): data_ptr = builder.gep(builder.load(array_ptr), [zero_32, two_32], inbounds=True) size_of = builder.mul(builder.load(capacity_ptr), eight) - mem_alloc = builder.call(compiler.module.get_global('malloc'), [size_of]) - mem_alloc = builder.bitcast(mem_alloc, type_map[INT].as_pointer()) + mem_alloc = builder.call(self.module.get_global('malloc'), [size_of]) + mem_alloc = builder.bitcast(mem_alloc, llvm_type_map[array_type].as_pointer()) builder.store(mem_alloc, data_ptr) builder.branch(dyn_array_init_exit) @@ -118,17 +130,17 @@ def dynamic_array_init(compiler, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_double_if_full(compiler, dyn_array_struct_ptr): +def dynamic_array_double_if_full(self, dyn_array_ptr, array_type): # START - dyn_array_double_capacity_if_full_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr]) - dyn_array_double_capacity_if_full = ir.Function(compiler.module, dyn_array_double_capacity_if_full_type, 'dyn_array_double_capacity_if_full') + dyn_array_double_capacity_if_full_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr]) + dyn_array_double_capacity_if_full = ir.Function(self.module, dyn_array_double_capacity_if_full_type, '{}_array_double_capacity_if_full'.format(array_type)) dyn_array_double_capacity_if_full_entry = dyn_array_double_capacity_if_full.append_basic_block('entry') builder = ir.IRBuilder(dyn_array_double_capacity_if_full_entry) - compiler.builder = builder + self.builder = builder dyn_array_double_capacity_if_full_exit = dyn_array_double_capacity_if_full.append_basic_block('exit') dyn_array_double_capacity_block = dyn_array_double_capacity_if_full.append_basic_block('double_capacity') builder.position_at_end(dyn_array_double_capacity_if_full_entry) - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(dyn_array_double_capacity_if_full.args[0], array_ptr) # BODY @@ -152,8 +164,8 @@ def dynamic_array_double_if_full(compiler, dyn_array_struct_ptr): size_of = builder.mul(capacity_val, eight) data_ptr_8 = builder.bitcast(builder.load(data_ptr), type_map[INT8].as_pointer()) - re_alloc = builder.call(compiler.module.get_global('realloc'), [data_ptr_8, size_of]) - re_alloc = builder.bitcast(re_alloc, type_map[INT].as_pointer()) + re_alloc = builder.call(self.module.get_global('realloc'), [data_ptr_8, size_of]) + re_alloc = builder.bitcast(re_alloc, llvm_type_map[array_type].as_pointer()) builder.store(re_alloc, data_ptr) builder.branch(dyn_array_double_capacity_if_full_exit) @@ -163,22 +175,22 @@ def dynamic_array_double_if_full(compiler, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_append(compiler, dyn_array_struct_ptr): +def dynamic_array_append(self, dyn_array_ptr, array_type): # START - dyn_array_append_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr, type_map[INT]]) - dyn_array_append = ir.Function(compiler.module, dyn_array_append_type, 'dyn_array_append') + dyn_array_append_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, llvm_type_map[array_type]]) + dyn_array_append = ir.Function(self.module, dyn_array_append_type, '{}_array_append'.format(array_type)) dyn_array_append_entry = dyn_array_append.append_basic_block('entry') builder = ir.IRBuilder(dyn_array_append_entry) - compiler.builder = builder + self.builder = builder dyn_array_append_exit = dyn_array_append.append_basic_block('exit') builder.position_at_end(dyn_array_append_entry) - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(dyn_array_append.args[0], array_ptr) - value_ptr = builder.alloca(type_map[INT]) + value_ptr = builder.alloca(llvm_type_map[array_type]) builder.store(dyn_array_append.args[1], value_ptr) # BODY - builder.call(compiler.module.get_global('dyn_array_double_capacity_if_full'), [builder.load(array_ptr)]) + builder.call(self.module.get_global('{}_array_double_capacity_if_full'.format(array_type)), [builder.load(array_ptr)]) size_ptr = builder.gep(builder.load(array_ptr), [zero_32, zero_32], inbounds=True) size_val = builder.load(size_ptr) @@ -199,20 +211,20 @@ def dynamic_array_append(compiler, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_get(compiler, dyn_array_struct_ptr): +def dynamic_array_get(self, dyn_array_ptr, array_type): # START - dyn_array_get_type = ir.FunctionType(type_map[INT], [dyn_array_struct_ptr, type_map[INT]]) - dyn_array_get = ir.Function(compiler.module, dyn_array_get_type, 'dyn_array_get') + dyn_array_get_type = ir.FunctionType(llvm_type_map[array_type], [dyn_array_ptr, type_map[INT]]) + dyn_array_get = ir.Function(self.module, dyn_array_get_type, '{}_array_get'.format(array_type)) dyn_array_get_entry = dyn_array_get.append_basic_block('entry') builder = ir.IRBuilder(dyn_array_get_entry) - compiler.builder = builder + self.builder = builder dyn_array_get_exit = dyn_array_get.append_basic_block('exit') dyn_array_get_index_out_of_bounds = dyn_array_get.append_basic_block('index_out_of_bounds') dyn_array_get_is_index_less_than_zero = dyn_array_get.append_basic_block('is_index_less_than_zero') dyn_array_get_negative_index = dyn_array_get.append_basic_block('negative_index') dyn_array_get_block = dyn_array_get.append_basic_block('get') builder.position_at_end(dyn_array_get_entry) - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(dyn_array_get.args[0], array_ptr) index_ptr = builder.alloca(type_map[INT]) builder.store(dyn_array_get.args[1], index_ptr) @@ -227,8 +239,8 @@ def dynamic_array_get(compiler, dyn_array_struct_ptr): builder.cbranch(compare_index_to_size, dyn_array_get_index_out_of_bounds, dyn_array_get_is_index_less_than_zero) builder.position_at_end(dyn_array_get_index_out_of_bounds) - compiler.print_string('Array index out of bounds') - builder.call(compiler.module.get_global('exit'), [one_32]) + self.print_string('Array index out of bounds') + builder.call(self.module.get_global('exit'), [one_32]) builder.unreachable() builder.position_at_end(dyn_array_get_is_index_less_than_zero) @@ -259,24 +271,24 @@ def dynamic_array_get(compiler, dyn_array_struct_ptr): builder.ret(builder.load(data_element_ptr)) -def dynamic_array_set(compiler, dyn_array_struct_ptr): +def dynamic_array_set(self, dyn_array_ptr, array_type): # START - dyn_array_set_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr, type_map[INT], type_map[INT]]) - dyn_array_set = ir.Function(compiler.module, dyn_array_set_type, 'dyn_array_set') + dyn_array_set_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, type_map[INT], llvm_type_map[array_type]]) + dyn_array_set = ir.Function(self.module, dyn_array_set_type, '{}_array_set'.format(array_type)) dyn_array_set_entry = dyn_array_set.append_basic_block('entry') builder = ir.IRBuilder(dyn_array_set_entry) - compiler.builder = builder + self.builder = builder dyn_array_set_exit = dyn_array_set.append_basic_block('exit') dyn_array_set_index_out_of_bounds = dyn_array_set.append_basic_block('index_out_of_bounds') dyn_array_set_is_index_less_than_zero = dyn_array_set.append_basic_block('is_index_less_than_zero') dyn_array_set_negative_index = dyn_array_set.append_basic_block('negative_index') dyn_array_set_block = dyn_array_set.append_basic_block('set') builder.position_at_end(dyn_array_set_entry) - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(dyn_array_set.args[0], array_ptr) index_ptr = builder.alloca(type_map[INT]) builder.store(dyn_array_set.args[1], index_ptr) - value_ptr = builder.alloca(type_map[INT]) + value_ptr = builder.alloca(llvm_type_map[array_type]) builder.store(dyn_array_set.args[2], value_ptr) # BODY @@ -290,8 +302,8 @@ def dynamic_array_set(compiler, dyn_array_struct_ptr): builder.cbranch(compare_index_to_size, dyn_array_set_index_out_of_bounds, dyn_array_set_is_index_less_than_zero) builder.position_at_end(dyn_array_set_index_out_of_bounds) - compiler.print_string('Array index out of bounds') - builder.call(compiler.module.get_global('exit'), [one_32]) + self.print_string('Array index out of bounds') + builder.call(self.module.get_global('exit'), [one_32]) builder.unreachable() builder.position_at_end(dyn_array_set_is_index_less_than_zero) @@ -325,15 +337,15 @@ def dynamic_array_set(compiler, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_length(compiler, dyn_array_struct_ptr): +def dynamic_array_length(self, dyn_array_ptr, array_type): # START - dyn_array_length_type = ir.FunctionType(type_map[INT], [dyn_array_struct_ptr]) - dyn_array_length = ir.Function(compiler.module, dyn_array_length_type, 'dyn_array_length') + dyn_array_length_type = ir.FunctionType(type_map[INT], [dyn_array_ptr]) + dyn_array_length = ir.Function(self.module, dyn_array_length_type, '{}_array_length'.format(array_type)) dyn_array_length_entry = dyn_array_length.append_basic_block('entry') builder = ir.IRBuilder(dyn_array_length_entry) - compiler.builder = builder + self.builder = builder builder.position_at_end(dyn_array_length_entry) - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(dyn_array_length.args[0], array_ptr) size_ptr = builder.gep(builder.load(array_ptr), [zero_32, zero_32], inbounds=True) @@ -353,25 +365,25 @@ def dynamic_array_length(compiler, dyn_array_struct_ptr): # reverse() -def define_print(compiler, dyn_array_struct_ptr): +def define_print(self, dyn_array_ptr): # START - func_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr]) - func = ir.Function(compiler.module, func_type, 'print') + func_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr]) + func = ir.Function(self.module, func_type, 'print') entry_block = func.append_basic_block('entry') builder = ir.IRBuilder(entry_block) - compiler.builder = builder + self.builder = builder builder.position_at_end(entry_block) zero_length_check_block = func.append_basic_block('zero_length_check') non_zero_length_block = func.append_basic_block('non_zero_length') cond_block = func.append_basic_block('check_if_done') body_block = func.append_basic_block('print_it') exit_block = func.append_basic_block('exit') - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(func.args[0], array_ptr) # BODY builder.position_at_end(entry_block) - length = builder.call(compiler.module.get_global('dyn_array_length'), [builder.load(array_ptr)]) + length = builder.call(self.module.get_global('i64_array_length'), [builder.load(array_ptr)]) builder.branch(zero_length_check_block) builder.position_at_end(zero_length_check_block) @@ -388,28 +400,28 @@ def define_print(compiler, dyn_array_struct_ptr): builder.cbranch(cond, body_block, exit_block) builder.position_at_end(body_block) - char = builder.call(compiler.module.get_global('dyn_array_get'), [builder.load(array_ptr), builder.load(position_ptr)]) - builder.call(compiler.module.get_global('putchar'), [char]) + char = builder.call(self.module.get_global('i64_array_get'), [builder.load(array_ptr), builder.load(position_ptr)]) + builder.call(self.module.get_global('putchar'), [char]) add_one = builder.add(one, builder.load(position_ptr)) builder.store(add_one, position_ptr) builder.branch(cond_block) # CLOSE builder.position_at_end(exit_block) - builder.call(compiler.module.get_global('putchar'), [ten]) + builder.call(self.module.get_global('putchar'), [ten]) builder.ret_void() -def define_int_to_str(compiler, dyn_array_struct_ptr): +def define_int_to_str(self, dyn_array_ptr): # START - func_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr, type_map[INT]]) - func = ir.Function(compiler.module, func_type, 'int_to_str') + func_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, type_map[INT]]) + func = ir.Function(self.module, func_type, 'int_to_str') entry_block = func.append_basic_block('entry') builder = ir.IRBuilder(entry_block) - compiler.builder = builder + self.builder = builder builder.position_at_end(entry_block) exit_block = func.append_basic_block('exit') - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(func.args[0], array_ptr) n_addr = builder.alloca(type_map[INT]) builder.store(func.args[1], n_addr) @@ -423,10 +435,10 @@ def define_int_to_str(compiler, dyn_array_struct_ptr): mod_ten = builder.srem(builder.trunc(builder.load(n_addr), type_map[INT]), ten) builder.store(mod_ten, x_addr) with builder.if_then(greater_than_zero): - builder.call(compiler.module.get_global('int_to_str'), [builder.load(array_ptr), div_ten]) + builder.call(self.module.get_global('int_to_str'), [builder.load(array_ptr), div_ten]) char = builder.add(fourtyeight, builder.load(x_addr)) - builder.call(compiler.module.get_global('dyn_array_append'), [builder.load(array_ptr), char]) + builder.call(self.module.get_global('i64_array_append'), [builder.load(array_ptr), char]) builder.branch(exit_block) # CLOSE @@ -434,20 +446,20 @@ def define_int_to_str(compiler, dyn_array_struct_ptr): builder.ret_void() -def define_bool_to_str(compiler, dyn_array_struct_ptr): +def define_bool_to_str(self, dyn_array_ptr): # START - func_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr, type_map[BOOL]]) - func = ir.Function(compiler.module, func_type, 'bool_to_str') + func_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, type_map[BOOL]]) + func = ir.Function(self.module, func_type, 'bool_to_str') entry_block = func.append_basic_block('entry') builder = ir.IRBuilder(entry_block) - compiler.builder = builder + self.builder = builder exit_block = func.append_basic_block('exit') - array_ptr = builder.alloca(dyn_array_struct_ptr) + array_ptr = builder.alloca(dyn_array_ptr) builder.store(func.args[0], array_ptr) # BODY equalszero = builder.icmp_signed(EQUALS, func.args[1], ir.Constant(type_map[BOOL], 0)) - dyn_array_append = compiler.module.get_global('dyn_array_append') + dyn_array_append = self.module.get_global('i64_array_append') with builder.if_else(equalszero) as (then, otherwise): with then: diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 5637490..1d41192 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -3,13 +3,16 @@ from time import time import llvmlite.binding as llvm import os +from math import inf import subprocess from llvmlite import ir from lesma.grammar import * from lesma.ast import CollectionAccess, DotAccess, Input, VarDecl, Str -from lesma.compiler import RET_VAR, type_map +from lesma.compiler import RET_VAR, type_map, llvm_type_map from lesma.compiler.operations import unary_op, binary_op, cast_ops from lesma.compiler.builtins import define_builtins +from lesma.compiler.builtins import create_dynamic_array_methods +from lesma.compiler.builtins import array_types from lesma.type_checker import types_compatible import lesma.compiler.llvmlite_custom from lesma.visitor import NodeVisitor @@ -23,7 +26,7 @@ def __init__(self, file_name): self.module = ir.Module() self.builder = None self._add_builtins() - func_ty = ir.FunctionType(ir.VoidType(), [type_map[INT32], type_map[INT8].as_pointer().as_pointer()]) + func_ty = ir.FunctionType(ir.IntType(64), []) # [type_map[INT32], type_map[INT8].as_pointer().as_pointer()]) func = ir.Function(self.module, func_ty, 'main') entry_block = func.append_basic_block('entry') exit_block = func.append_basic_block('exit') @@ -42,10 +45,9 @@ def __init__(self, file_name): llvm.initialize_native_asmprinter() self.anon_counter = 0 - # Add argv and argc to main - for i in range(2): - func.args[i].name = '.argc' if i == 0 else '.argv' - self.alloc_define_store(func.args[i], func.args[i].name[1:], func.args[i].type) + # for i in range(2): + # func.args[i].name = '.argc' if i == 0 else '.argv' + # self.alloc_define_store(func.args[i], func.args[i].name[1:], func.args[i].type) def __str__(self): return str(self.module) @@ -56,7 +58,7 @@ def visit_program(self, node): self.visit(stat) self.branch(self.exit_blocks[0]) self.position_at_end(self.exit_blocks[0]) - self.builder.ret_void() + self.builder.ret(self.const(0)) @staticmethod def visit_num(node): @@ -76,11 +78,11 @@ def visit_defer(self, node): def visit_anonymousfunc(self, node): self.anon_counter += 1 - self.funcdecl('anon_func.{}'.format(self.anon_counter), node) + self.funcdef('anon_func.{}'.format(self.anon_counter), node, "private") return self.search_scopes('anon_func.{}'.format(self.anon_counter)) def visit_funcdecl(self, node): - self.funcdecl(node.name, node) + self.funcdef(node.name, node) def visit_externfuncdecl(self, node): self.externfuncdecl(node.name, node) @@ -102,11 +104,34 @@ def externfuncdecl(self, name, node): func = ir.Function(self.module, func_type, name) self.define(name, func, 1) - def funcdecl(self, name, node): - func = self.start_function(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs) + def funcdecl(self, name, node, linkage=None): + self.func_decl(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs, linkage) + + def funcimpl(self, name, node): + self.implement_func_body(name) for i, arg in enumerate(self.current_function.args): arg.name = list(node.parameters.keys())[i] - self.alloc_define_store(arg, arg.name, arg.type) + + # TODO: a bit hacky, cannot handle pointers atm but we need them for class reference + if arg.name == SELF and isinstance(arg.type, ir.PointerType): + self.define(arg.name, arg) + else: + self.alloc_define_store(arg, arg.name, arg.type) + if self.current_function.function_type.return_type != type_map[VOID]: + self.alloc_and_define(RET_VAR, self.current_function.function_type.return_type) + ret = self.visit(node.body) + self.end_function(ret) + + def funcdef(self, name, node, linkage=None): + self.start_function(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs, linkage) + for i, arg in enumerate(self.current_function.args): + arg.name = list(node.parameters.keys())[i] + + # TODO: a bit hacky, cannot handle pointers atm but we need them for class reference + if arg.name == SELF and isinstance(arg.type, ir.PointerType): + self.define(arg.name, arg) + else: + self.alloc_define_store(arg, arg.name, arg.type) if self.current_function.function_type.return_type != type_map[VOID]: self.alloc_and_define(RET_VAR, self.current_function.function_type.return_type) ret = self.visit(node.body) @@ -120,10 +145,57 @@ def visit_return(self, node): self.branch(self.exit_blocks[-1]) return True + def visit_methodcall(self, node): + obj = self.search_scopes(node.obj) + method = self.search_scopes(obj.type.pointee.name + '.' + node.name) + return self.methodcall(node, method, obj) + + def methodcall(self, node, func, obj): + func_type = func.function_type + if len(node.arguments) + 1 < len(func_type.args): + args = [] + args_supplied = [] + arg_names = [] + + for i in func_type.parameters: + arg_names.append(i) + + for x, arg in enumerate(func_type.args): + if x == 0: + continue + if x < len(node.arguments): + args.append(self.visit(node.arguments[x])) + else: + if node.named_arguments and arg_names[x] in node.named_arguments: + args.append(self.comp_cast( + self.visit(node.named_arguments[arg_names[x]]), + self.visit(func_type.parameters[arg_names[x]]), + node + )) + else: + if set(node.named_arguments.keys()) & set(args_supplied): + raise TypeError('got multiple values for argument(s) {}'.format(set(node.named_arguments.keys()) & set(args_supplied))) + + args.append(self.comp_cast( + self.visit(func_type.parameter_defaults[arg_names[x]]), + self.visit(func_type.parameters[arg_names[x]]), + node + )) + args_supplied.append(arg) + elif len(node.arguments) + len(node.named_arguments) > len(func_type.args) and func_type.var_arg is None: + raise SyntaxError('Unexpected arguments') + else: + args = [] + for i, arg in enumerate(node.arguments): + args.append(self.comp_cast(self.visit(arg), func_type.args[i], node)) + + args.insert(0, obj) + return self.builder.call(func, args) + def visit_funccall(self, node): func_type = self.search_scopes(node.name) isFunc = False - if type(func_type) == ir.AllocaInstr: + if isinstance(func_type, ir.AllocaInstr): name = self.load(func_type) func_type = name.type.pointee isFunc = True @@ -132,7 +204,12 @@ def visit_funccall(self, node): name = self.search_scopes(node.name) name = name.name elif isinstance(func_type, ir.IdentifiedStructType): - return self.struct_assign(node) + typ = self.search_scopes(node.name) + if typ.type == STRUCT: + return self.struct_assign(node) + elif typ.type == CLASS: + return self.class_assign(node) + error("Unexpected Identified Struct Type") else: name = node.name @@ -189,6 +266,14 @@ def visit_compound(self, node): ret = temp return ret + def visit_enumdeclaration(self, node): + enum = self.module.context.get_identified_type(node.name) + enum.fields = [field for field in node.fields] + enum.name = node.name + enum.type = ENUM + enum.set_body([ir.IntType(8, signed=False)]) + self.define(node.name, enum) + def visit_structdeclaration(self, node): fields = [] for field in node.fields.values(): @@ -196,7 +281,9 @@ def visit_structdeclaration(self, node): struct = self.module.context.get_identified_type(node.name) struct.fields = [field for field in node.fields.keys()] - struct.name = 'struct.' + node.name + struct.defaults = node.defaults + struct.name = node.name + struct.type = STRUCT struct.set_body([field for field in fields]) self.define(node.name, struct) @@ -204,29 +291,34 @@ def visit_classdeclaration(self, node): self.in_class = True fields = [] - for field in node.class_fields.values(): + for field in node.fields.values(): fields.append(type_map[field.value]) classdecl = self.module.context.get_identified_type(node.name) - classdecl.fields = [field for field in node.class_fields.keys()] - classdecl.name = 'class.' + node.name + classdecl.fields = [field for field in node.fields.keys()] + classdecl.name = node.name + classdecl.type = CLASS classdecl.set_body([field for field in fields]) + self.define(node.name, classdecl) + for method in node.methods: + self.funcdecl(method.name, method) + + for method in node.methods: + self.funcimpl(method.name, method) + classdecl.methods = [self.search_scopes(method.name) for method in node.methods] - # self.funcdecl('class.{}.new'.format(node.name), node.constructor) self.in_class = False self.define(node.name, classdecl) - def visit_typedeclaration(self, node): - raise NotImplementedError - def visit_incrementassign(self, node): collection_access = None key = None if isinstance(node.left, CollectionAccess): collection_access = True var_name = self.search_scopes(node.left.collection.value) + array_type = str(var_name.type.pointee.elements[-1].pointee) key = self.const(node.left.key.value) - var = self.call('dyn_array_get', [var_name, key]) + var = self.call('{}_array_get'.format(array_type), [var_name, key]) pointee = var.type else: var_name = node.left.value @@ -249,28 +341,35 @@ def visit_incrementassign(self, node): raise NotImplementedError() if collection_access: - self.call('dyn_array_set', [var_name, key, res]) + self.call('{}_array_set'.format(array_type), [var_name, key, res]) else: self.store(res, var_name) - def visit_aliasdeclaration(self, node): + @staticmethod + def visit_typedeclaration(node): type_map[node.name] = type_map[node.collection.value] - return ALIAS + return TYPE def visit_vardecl(self, node): - typ = type_map[node.type.value] + typ = type_map[node.type.value] if node.type.value in type_map else self.search_scopes(node.type.value) if node.type.value == FUNC: - func_ret_type = type_map[node.type.func_ret_type.value] + if node.type.func_ret_type.value in type_map: + func_ret_type = type_map[node.type.func_ret_type.value] + else: + func_ret_type = self.search_scopes(node.type.func_ret_type.value) func_parameters = self.get_args(node.type.func_params) func_ty = ir.FunctionType(func_ret_type, func_parameters, None).as_pointer() typ = func_ty - var_addr = self.allocate(typ, name=node.value.value) - self.define(node.value.value, var_addr) - self.store(self.visit(node.value), node.value.value) + self.alloc_and_define(node.value.value, typ) + elif node.type.value in (LIST, TUPLE): + array_type = node.type.func_params['0'].value + typ = ir.LiteralStructType([type_map[INT], type_map[INT], type_map[array_type].as_pointer()]) + self.alloc_and_define(node.value.value, typ) + else: + self.alloc_and_define(node.value.value, typ) - @staticmethod - def visit_type(node): - return type_map[node.value] + def visit_type(self, node): + return type_map[node.value] if node.value in type_map else self.search_scopes(node.value) def visit_if(self, node): start_block = self.add_block('if.start') @@ -337,7 +436,7 @@ def visit_for(self, node): iterator = self.visit(node.iterator) else: iterator = self.search_scopes(node.iterator.value) - stop = self.call('dyn_array_length', [iterator]) + stop = self.call('i64_array_length', [iterator]) self.branch(zero_length_block) self.position_at_end(zero_length_block) @@ -346,7 +445,7 @@ def visit_for(self, node): self.position_at_end(non_zero_length_block) varname = node.elements[0].value - val = self.call('dyn_array_get', [iterator, zero]) + val = self.call('i64_array_get', [iterator, zero]) self.alloc_define_store(val, varname, iterator.type.pointee.elements[2].pointee) position = self.alloc_define_store(zero, 'position', type_map[INT]) self.branch(cond_block) @@ -356,7 +455,7 @@ def visit_for(self, node): self.cbranch(cond, body_block, end_block) self.position_at_end(body_block) - self.store(self.call('dyn_array_get', [iterator, self.load(position)]), varname) + self.store(self.call('i64_array_get', [iterator, self.load(position)]), varname) self.store(self.builder.add(one, self.load(position)), position) self.visit(node.block) if not self.is_break: @@ -429,32 +528,42 @@ def visit_unaryop(self, node): def visit_range(self, node): start = self.visit(node.left) stop = self.visit(node.right) - array_ptr = self.create_array(INT) # TODO: This should be changed to allow any type + array_ptr = self.create_array(INT) self.call('create_range', [array_ptr, start, stop]) return array_ptr - def visit_assign(self, node): # TODO: Simplify this, it just keeps getting worse - if hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), ir.IdentifiedStructType): - self.define(node.left.value.value, self.visit(node.right)) + def visit_assign(self, node): + if isinstance(node.right, DotAccess) and self.search_scopes(node.right.obj).type == ENUM or \ + hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), ir.IdentifiedStructType): + var_name = node.left.value if isinstance(node.left.value, str) else node.left.value.value + self.define(var_name, self.visit(node.right)) elif hasattr(node.right, 'value') and isinstance(self.search_scopes(node.right.value), ir.Function): self.define(node.left.value, self.search_scopes(node.right.value)) else: if isinstance(node.right, Input): - node.right.type = node.left.type_node.value + if hasattr(node.left, 'type'): + node.right.type = node.left.type + else: + node.right.type = str var = self.visit(node.right) if not var: return if isinstance(node.left, VarDecl): var_name = node.left.value.value - var_type = type_map[node.left.type.value] - if not var.type.is_pointer: - casted_value = cast_ops(self, var, var_type, node) - self.alloc_define_store(casted_value, var_name, var_type) - else: # TODO: Not able currently to deal with pointers, such as functions + if node.left.type.value in (LIST, TUPLE): + # TODO: Currently only supporting one type for lists and tuples + var_type = type_map[list(node.left.type.func_params.items())[0][1].value] self.alloc_define_store(var, var_name, var.type) + else: + var_type = type_map[node.left.type.value] + if not var.type.is_pointer: + casted_value = cast_ops(self, var, var_type, node) + self.alloc_define_store(casted_value, var_name, var_type) + else: # TODO: Not able currently to deal with pointers, such as functions + self.alloc_define_store(var, var_name, var.type) elif isinstance(node.left, DotAccess): obj = self.search_scopes(node.left.obj) - obj_type = self.search_scopes(obj.struct_name) + obj_type = self.search_scopes(obj.type.pointee.name.split('.')[-1]) idx = -1 for i, v in enumerate(obj_type.fields): if v == node.left.field: @@ -465,7 +574,8 @@ def visit_assign(self, node): # TODO: Simplify this, it just keeps getting wors self.builder.store(self.visit(node.right), elem) elif isinstance(node.left, CollectionAccess): right = self.visit(node.right) - self.call('dyn_array_set', [self.search_scopes(node.left.collection.value), self.const(node.left.key.value), right]) + array_type = str(self.search_scopes(node.left.collection.value).type.pointee.elements[-1].pointee) + self.call('{}_array_set'.format(array_type), [self.search_scopes(node.left.collection.value), self.const(node.left.key.value), right]) else: var_name = node.left.value var_value = self.top_scope.get(var_name) @@ -480,25 +590,55 @@ def visit_assign(self, node): # TODO: Simplify this, it just keeps getting wors def visit_fieldassignment(self, node): obj = self.search_scopes(node.obj) - obj_type = self.search_scopes(obj.struct_name) + obj_type = self.search_scopes(obj.name) return self.builder.extract_value(self.load(node.obj), obj_type.fields.index(node.field)) + def class_assign(self, node): + class_type = self.search_scopes(node.name) + _class = self.builder.alloca(class_type) + + for func in class_type.methods: + if func.name.split(".")[-1] == 'new': + self.methodcall(node, func, _class) + + return _class + def struct_assign(self, node): struct_type = self.search_scopes(node.name) struct = self.builder.alloca(struct_type) - fields = [] - for field in node.named_arguments.values(): - fields.append(self.visit(field)) - elem = self.builder.gep(struct, [self.const(0, width=INT32), self.const(len(fields) - 1, width=INT32)], inbounds=True) - self.builder.store(fields[len(fields) - 1], elem) - - struct.struct_name = node.name + fields = set() + for index, field in struct_type.defaults.items(): + val = self.visit(field) + pos = struct_type.fields.index(index) + fields.add(index) + elem = self.builder.gep(struct, [self.const(0, width=INT32), self.const(pos, width=INT32)], inbounds=True) + self.builder.store(val, elem) + + for index, field in enumerate(node.named_arguments.values()): + val = self.visit(field) + pos = struct_type.fields.index(list(node.named_arguments.keys())[index]) + fields.add((list(node.named_arguments.keys())[index])) + elem = self.builder.gep(struct, [self.const(0, width=INT32), self.const(pos, width=INT32)], inbounds=True) + self.builder.store(val, elem) + + if len(fields) < len(struct_type.fields): + error('file={} line={} Syntax Error: struct declaration doesn\'t initialize all fields ({})'.format( + self.file_name, node.line_num, ','.join(fields.symmetric_difference(set(struct_type.fields))))) + + struct.name = node.name return struct def visit_dotaccess(self, node): obj = self.search_scopes(node.obj) - obj_type = self.search_scopes(obj.struct_name) + if obj.type == ENUM: + enum = self.builder.alloca(obj) + idx = obj.fields.index(node.field) + val = self.builder.gep(enum, [self.const(0, width=INT32), self.const(0, width=INT32)], inbounds=True) + self.builder.store(self.const(idx, width=INT8), val) + return enum + + obj_type = self.search_scopes(obj.type.pointee.name.split('.')[-1]) return self.builder.extract_value(self.load(node.obj), obj_type.fields.index(node.field)) def visit_opassign(self, node): @@ -508,8 +648,9 @@ def visit_opassign(self, node): if isinstance(node.left, CollectionAccess): collection_access = True var_name = self.search_scopes(node.left.collection.value) + array_type = str(self.search_scopes(node.left.collection.value).type.pointee.elements[-1].pointee) key = self.const(node.left.key.value) - var = self.call('dyn_array_get', [var_name, key]) + var = self.call('{}_array_get'.format(array_type), [var_name, key]) pointee = var.type else: var_name = node.left.value @@ -586,7 +727,7 @@ def visit_opassign(self, node): raise NotImplementedError() if collection_access: - self.call('dyn_array_set', [var_name, key, res]) + self.call('{}_array_set'.format(array_type), [var_name, key, res]) else: self.store(res, var_name) @@ -595,6 +736,8 @@ def visit_constant(self, node): return self.const(1, BOOL) elif node.value == FALSE: return self.const(0, BOOL) + elif node.value == INF: + return self.const(inf, DOUBLE) else: raise NotImplementedError('file={} line={}'.format(self.file_name, node.line_num)) @@ -610,28 +753,27 @@ def visit_collection(self, node): raise NotImplementedError def define_array(self, node, elements): - array_ptr = self.create_array(node.items[0].val_type) + array_type = node.items[0].val_type + array_ptr = self.create_array(array_type) for element in elements: - self.call('dyn_array_append', [array_ptr, element]) + self.call('{}_array_append'.format(str(type_map[array_type])), [array_ptr, element]) return self.load(array_ptr) def create_array(self, array_type): - dyn_array_type = self.search_scopes('Dynamic_Array') - array = dyn_array_type([self.const(0), self.const(0), self.const(0).inttoptr(type_map[INT].as_pointer())]) - # ptr = self.const(0).inttoptr(type_map[INT].as_pointer()) - # ptr = self.builder.bitcast(ptr, type_map[array_type].as_pointer()) - # dyn_array_type = ir.LiteralStructType([type_map[INT], type_map[INT], type_map[array_type].as_pointer()]) - # self.define('{}_Array'.format(array_type), dyn_array_type) - # array = dyn_array_type([self.const(0), self.const(0), ptr]) + array_type = str(type_map[array_type]) + dyn_array_type = ir.LiteralStructType([type_map[INT], type_map[INT], llvm_type_map[array_type].as_pointer()]) + self.define('{}_Array'.format(array_type), dyn_array_type) + array = dyn_array_type([self.const(0), self.const(0), self.const(0).inttoptr(llvm_type_map[array_type].as_pointer())]) array = self.alloc_and_store(array, dyn_array_type) - # self.call('{}_array_init'.format(array_type), [array]) - self.call('dyn_array_init', [array]) + create_dynamic_array_methods(self, array_type) + self.call('{}_array_init'.format(array_type), [array]) return array def define_tuple(self, node, elements): - array_ptr = self.create_array(node.items[0].val_type) + array_type = node.items[0].val_type + array_ptr = self.create_array(array_type) for element in elements: - self.call('dyn_array_append', [array_ptr, element]) + self.call('{}_array_append'.format(str(type_map[array_type])), [array_ptr, element]) return self.load(array_ptr) def visit_hashmap(self, node): @@ -640,8 +782,9 @@ def visit_hashmap(self, node): def visit_collectionaccess(self, node): key = self.visit(node.key) collection = self.search_scopes(node.collection.value) - if collection.type.pointee == self.search_scopes('Dynamic_Array'): - return self.call('dyn_array_get', [collection, key]) + for typ in array_types: + if collection.type.pointee == self.search_scopes('{}_Array'.format(typ)): + return self.call('{}_array_get'.format(typ), [collection, key]) return self.builder.extract_value(self.load(collection.name), [key]) @@ -649,7 +792,7 @@ def visit_str(self, node): array = self.create_array(INT) string = node.value.encode('utf-8') for char in string: - self.call('dyn_array_append', [array, self.const(char)]) + self.call('i64_array_append', [array, self.const(char)]) return array def visit_print(self, node): @@ -660,7 +803,7 @@ def visit_print(self, node): return if isinstance(val.type, ir.IntType): if val.type.width == 1: - array = self.create_array(BOOL) + array = self.create_array(INT) self.call('bool_to_str', [array, val]) val = array else: @@ -702,40 +845,100 @@ def print_num(self, num_format, num): self.call('printf', [percent_d, num]) self.call('putchar', [ir.Constant(type_map[INT], 10)]) + @staticmethod + def typeToFormat(typ): + fmt = None + + if isinstance(typ, ir.IntType): + if int(str(typ).split("i")[1]) == 8: + fmt = "%c" + elif typ.signed: + if int(str(typ).split("i")[1]) <= 32: + fmt = "%d" + else: + fmt = "%lld" + else: + if int(str(typ).split("i")[1]) <= 32: + fmt = "%u" + else: + fmt = "%llu" + elif isinstance(typ, ir.FloatType): + fmt = "%f" + elif isinstance(typ, ir.DoubleType): + fmt = "%lf" + else: + fmt = "%s" + + return fmt + def visit_input(self, node): - # Print text if it exists - if isinstance(node.value, Str): - stringz = self.stringz(node.value.value) - str_ptr = self.alloc_and_store(stringz, ir.ArrayType(stringz.type.element, stringz.type.count)) - str_ptr = self.gep(str_ptr, [self.const(0), self.const(0)]) - str_ptr = self.builder.bitcast(str_ptr, type_map[INT].as_pointer()) - self.call('puts', [str_ptr]) - - percent_d = self.stringz('%d') + if isinstance(node.value, Str): # Print text if it exists + self.print_string(node.value.value) + + percent_d = self.stringz(self.typeToFormat(type_map[node.type.value])) percent_ptr = self.alloc_and_store(percent_d, ir.ArrayType(percent_d.type.element, percent_d.type.count)) percent_ptr_gep = self.gep(percent_ptr, [self.const(0), self.const(0)]) percent_ptr_gep = self.builder.bitcast(percent_ptr_gep, type_map[INT8].as_pointer()) - return self.call('scanf', [percent_ptr_gep, self.allocate(type_map[INT])]) + var = self.allocate(type_map[node.type.value]) + self.call('scanf', [percent_ptr_gep, var]) + return self.builder.load(var) def get_args(self, parameters): args = [] for param in parameters.values(): if param.value == FUNC: - func_ret_type = type_map[param.func_ret_type.value] + if param.func_ret_type.value in type_map: + func_ret_type = type_map[param.func_ret_type.value] + elif self.search_scopes(param.func_ret_type.value) is not None: + func_ret_type = self.search_scopes(param.func_ret_type.value).as_pointer() func_parameters = self.get_args(param.func_params) func_ty = ir.FunctionType(func_ret_type, func_parameters, None).as_pointer() args.append(func_ty) else: - args.append(type_map[param.value]) + if param.value in type_map: + args.append(type_map[param.value]) + elif list(parameters.keys())[list(parameters.values()).index(param)] == SELF: + args.append(self.search_scopes(param.value).as_pointer()) + elif self.search_scopes(param.value) is not None: + args.append(self.search_scopes(param.value)) + else: + error("Parameter type not recognized: {}".format(param.value)) return args - def start_function(self, name, return_type, parameters, parameter_defaults=None, varargs=None): + def func_decl(self, name, return_type, parameters, parameter_defaults=None, varargs=None, linkage=None): + ret_type = type_map[return_type.value] + args = self.get_args(parameters) + func_type = ir.FunctionType(ret_type, args, varargs) + func_type.parameters = parameters + if parameter_defaults: + func_type.parameter_defaults = parameter_defaults + if hasattr(return_type, 'func_ret_type') and return_type.func_ret_type: + func_type.return_type = func_type.return_type(type_map[return_type.func_ret_type.value], [return_type.func_ret_type.value]).as_pointer() + func = ir.Function(self.module, func_type, name) + func.linkage = linkage + self.define(name, func, 1) + + def implement_func_body(self, name): self.function_stack.append(self.current_function) self.block_stack.append(self.builder.block) self.new_scope() self.defer_stack.append([]) - ret_type = type_map[return_type.value] + for f in self.module.functions: + if f.name == name: + func = f + break + self.current_function = func + entry = self.add_block('entry') + self.exit_blocks.append(self.add_block('exit')) + self.position_at_end(entry) + + def start_function(self, name, return_type, parameters, parameter_defaults=None, varargs=None, linkage=None): + self.function_stack.append(self.current_function) + self.block_stack.append(self.builder.block) + self.new_scope() + self.defer_stack.append([]) + ret_type = type_map[return_type.value] if return_type.value in type_map else self.search_scopes(return_type.value) args = self.get_args(parameters) func_type = ir.FunctionType(ret_type, args, varargs) func_type.parameters = parameters @@ -744,6 +947,7 @@ def start_function(self, name, return_type, parameters, parameter_defaults=None, if hasattr(return_type, 'func_ret_type') and return_type.func_ret_type: func_type.return_type = func_type.return_type(type_map[return_type.func_ret_type.value], [return_type.func_ret_type.value]).as_pointer() func = ir.Function(self.module, func_type, name) + func.linkage = linkage self.define(name, func, 1) self.current_function = func entry = self.add_block('entry') @@ -803,9 +1007,7 @@ def const(self, val, width=None): raise NotImplementedError def allocate(self, typ, name=''): - saved_block = self.builder.block - var_addr = self.create_entry_block_alloca(name, typ) - self.builder.position_at_end(saved_block) + var_addr = self.builder.alloca(typ, name=name) return var_addr def alloc_and_store(self, val, typ, name=''): @@ -826,10 +1028,6 @@ def alloc_define_store(self, val, name, typ): self.builder.store(val, var_addr) return var_addr - def create_entry_block_alloca(self, name, typ): - self.builder.position_at_start(self.builder.function.entry_basic_block) - return self.builder.alloca(typ, size=None, name=name) - def store(self, value, name): if isinstance(name, str): self.builder.store(value, self.search_scopes(name)) @@ -872,7 +1070,7 @@ def _add_builtins(self): printf_ty = ir.FunctionType(type_map[INT32], [type_map[INT8].as_pointer()], var_arg=True) ir.Function(self.module, printf_ty, 'printf') - scanf_ty = ir.FunctionType(type_map[INT], [type_map[INT8].as_pointer(), type_map[INT].as_pointer()], var_arg=True) + scanf_ty = ir.FunctionType(type_map[INT], [type_map[INT8].as_pointer()], var_arg=True) ir.Function(self.module, scanf_ty, 'scanf') getchar_ty = ir.FunctionType(ir.IntType(8), []) @@ -902,7 +1100,7 @@ def add_debug_info(self, optimize, filename): di_module = self.module.add_debug_info("DICompileUnit", { "language": ir.DIToken("DW_LANG_Python"), "file": di_file, - "producer": "Lesma v0.3.0", + "producer": "Lesma v0.4.0", "runtimeVersion": 1, "isOptimized": optimize, }, is_distinct=True) diff --git a/src/lesma/compiler/llvmlite_custom.py b/src/lesma/compiler/llvmlite_custom.py index 2b4e749..c4bde10 100644 --- a/src/lesma/compiler/llvmlite_custom.py +++ b/src/lesma/compiler/llvmlite_custom.py @@ -137,7 +137,7 @@ def wrap_constant_value(self, values): if not isinstance(values, (list, tuple)): if isinstance(values, ir.Constant): if values.type != self.elementtype: - raise TypeError("expected % for %" + raise TypeError("expected %s for %s" % (self.elementtype, values.type)) return (values, ) * self.count return (ir.Constant(self.elementtype, values), ) * self.count diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 1da9291..c9b9b65 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -3,86 +3,123 @@ from lesma.compiler import NUM_TYPES from lesma.compiler import type_map, llvm_type_map from lesma.grammar import * +from lesma.utils import error import lesma.compiler.llvmlite_custom # TODO: Determine size using a comparison function int_types = ('i1', 'i8', 'i16', 'i32', 'i64', 'i128') float_types = ('float', 'double') -false = ir.Constant(type_map[BOOL], 0) -true = ir.Constant(type_map[BOOL], 1) - -def hasFunction(compiler, func_name): - for func in compiler.module.functions: +def hasFunction(self, func_name): + for func in self.module.functions: if func.name == func_name: return True + return False + def userdef_unary_str(op, expr): - return 'operator' + '.' + op + '.' + str(expr.type) + # Check first if it's an built in type, then user defined type + type_name = expr.type.name if hasattr(expr.type, 'name') else str(expr.type) + return OPERATOR + '.' + op + '.' + type_name -# Hacky way of checking if it's an expression or type def userdef_binary_str(op, left, right): - try: - return 'operator' + '.' + op + '.' + str(left.type) + '.' + str(right.type) - except Exception: - return 'operator' + '.' + op + '.' + str(left.type) + '.' + str(right) + # Check first if it's a type, then if it's an built in type, then user defined type + ltype_name = str(left) if not hasattr(left, 'type') else (str(left.type) if not hasattr(left.type, 'name') else left.type.name) + rtype_name = str(right) if not hasattr(right, 'type') else (str(right.type) if not hasattr(right.type, 'name') else right.type.name) + return OPERATOR + '.' + op + '.' + ltype_name + '.' + rtype_name -def unary_op(compiler, node): +def unary_op(self, node): op = node.op - expr = compiler.visit(node.expr) - if hasFunction(compiler, userdef_unary_str(op, expr)): - return compiler.builder.call(compiler.module.get_global(userdef_unary_str(op, expr)), - [expr], "unop") + expr = self.visit(node.expr) + if hasFunction(self, userdef_unary_str(op, expr)): + return self.builder.call(self.module.get_global(userdef_unary_str(op, expr)), + [expr], "unop") elif op == MINUS: if isinstance(expr.type, ir.IntType): - return compiler.builder.neg(expr) + return self.builder.neg(expr) elif isinstance(expr.type, (ir.FloatType, ir.DoubleType)): - return compiler.builder.fsub(ir.Constant(ir.DoubleType(), 0), expr) + return self.builder.fsub(ir.Constant(ir.DoubleType(), 0), expr) elif op == NOT: if isinstance(expr.type, ir.IntType) and str(expr.type).split("i")[1] == '1': - return compiler.builder.not_(expr) + return self.builder.not_(expr) elif op == BINARY_ONES_COMPLIMENT: if isinstance(expr.type, ir.IntType): - return compiler.builder.not_(expr) + return self.builder.not_(expr) + else: + error('file={} line={}: Unknown operator {} for {}'.format( + self.file_name, + node.line_num, + op, + expr + )) -def binary_op(compiler, node): +def binary_op(self, node): op = node.op - left = compiler.visit(node.left) - right = compiler.visit(node.right) - if hasFunction(compiler, userdef_binary_str(op, left, right)): - return compiler.builder.call(compiler.module.get_global(userdef_binary_str(op, left, right)), - (left, right), "binop") + left = self.visit(node.left) + right = self.visit(node.right) + + if hasFunction(self, userdef_binary_str(op, left, right)): + return self.builder.call(self.module.get_global(userdef_binary_str(op, left, right)), + (left, right), "binop") elif op == CAST: - return cast_ops(compiler, left, right, node) + return cast_ops(self, left, right, node) elif op in (IS, IS_NOT): - return is_ops(compiler, op, left, right, node) + return is_ops(self, op, left, right, node) elif isinstance(left.type, ir.IntType) and isinstance(right.type, ir.IntType): - return int_ops(compiler, op, left, right, node) + return int_ops(self, op, left, right, node) elif type(left.type) in NUM_TYPES and type(right.type) in NUM_TYPES: if isinstance(left.type, ir.IntType): - left = cast_ops(compiler, left, right.type, node) + left = cast_ops(self, left, right.type, node) elif isinstance(right.type, ir.IntType): - left = cast_ops(compiler, right, left.type, node) - return float_ops(compiler, op, left, right, node) + right = cast_ops(self, right, left.type, node) + return float_ops(self, op, left, right, node) + elif is_enum(left.type) and is_enum(right.type): + return enum_ops(self, op, left, right, node) + else: + error('file={} line={}: Unknown operator {} for {} and {}'.format( + self.file_name, + node.line_num, + op, node.left, node.right + )) + +def is_enum(typ): + if typ.is_pointer: + typ = typ.pointee + return hasattr(typ, 'type') and typ.type == ENUM -def is_ops(compiler, op, left, right, node): + +def is_ops(self, op, left, right, node): orig = str(left.type) compare = str(right) if op == IS: - return compiler.const(orig == compare, BOOL) + return self.const(orig == compare, BOOL) elif op == IS_NOT: - return compiler.const(orig != compare, BOOL) + return self.const(orig != compare, BOOL) else: raise SyntaxError('Unknown identity operator', node.op) -def int_ops(compiler, op, left, right, node): +def enum_ops(self, op, left, right, node): + if left.type.is_pointer: + left = self.builder.load(left) + if right.type.is_pointer: + right = self.builder.load(right) + + if op == EQUALS: + left_val = self.builder.extract_value(left, 0) + right_val = self.builder.extract_value(right, 0) + return self.builder.icmp_unsigned(op, left_val, right_val, 'cmptmp') + else: + raise SyntaxError('Unknown binary operator', node.op) + + +def int_ops(self, op, left, right, node): # Cast values if they're different but compatible if str(left.type) in int_types and \ str(right.type) in int_types and \ @@ -90,59 +127,59 @@ def int_ops(compiler, op, left, right, node): width_left = int(str(left.type).split("i")[1]) width_right = int(str(right.type).split("i")[1]) if width_left > width_right: - right = cast_ops(compiler, right, left.type, node) + right = cast_ops(self, right, left.type, node) else: - left = cast_ops(compiler, left, right.type, node) + left = cast_ops(self, left, right.type, node) if op == PLUS: - return compiler.builder.add(left, right, 'addtmp') + return self.builder.add(left, right, 'addtmp') elif op == MINUS: - return compiler.builder.sub(left, right, 'subtmp') + return self.builder.sub(left, right, 'subtmp') elif op == MUL: - return compiler.builder.mul(left, right, 'multmp') + return self.builder.mul(left, right, 'multmp') elif op == FLOORDIV: if left.type.signed: - return compiler.builder.sdiv(left, right, 'divtmp') + return self.builder.sdiv(left, right, 'divtmp') else: - return compiler.builder.udiv(left, right, 'divtmp') + return self.builder.udiv(left, right, 'divtmp') elif op == DIV: - return (compiler.builder.fdiv(cast_ops(compiler, left, type_map[DOUBLE], node), - cast_ops(compiler, right, type_map[DOUBLE], node), 'fdivtmp')) + return (self.builder.fdiv(cast_ops(self, left, type_map[DOUBLE], node), + cast_ops(self, right, type_map[DOUBLE], node), 'fdivtmp')) elif op == MOD: if left.type.signed: - return compiler.builder.srem(left, right, 'modtmp') + return self.builder.srem(left, right, 'modtmp') else: - return compiler.builder.urem(left, right, 'modtmp') + return self.builder.urem(left, right, 'modtmp') elif op == POWER: - temp = compiler.builder.alloca(type_map[INT]) - compiler.builder.store(left, temp) + temp = self.builder.alloca(type_map[INT]) + self.builder.store(left, temp) for _ in range(node.right.value - 1): - res = compiler.builder.mul(compiler.builder.load(temp), left) - compiler.builder.store(res, temp) - return compiler.builder.load(temp) + res = self.builder.mul(self.builder.load(temp), left) + self.builder.store(res, temp) + return self.builder.load(temp) elif op == AND: - return compiler.builder.and_(left, right) + return self.builder.and_(left, right) elif op == OR: - return compiler.builder.or_(left, right) + return self.builder.or_(left, right) elif op == XOR: - return compiler.builder.xor(left, right) + return self.builder.xor(left, right) elif op == ARITHMATIC_LEFT_SHIFT or op == BINARY_LEFT_SHIFT: - return compiler.builder.shl(left, right) + return self.builder.shl(left, right) elif op == ARITHMATIC_RIGHT_SHIFT: - return compiler.builder.ashr(left, right) + return self.builder.ashr(left, right) elif op == BINARY_RIGHT_SHIFT: - return compiler.builder.lshr(left, right) + return self.builder.lshr(left, right) elif op in (EQUALS, NOT_EQUALS, LESS_THAN, LESS_THAN_OR_EQUAL_TO, GREATER_THAN, GREATER_THAN_OR_EQUAL_TO): if left.type.signed: - cmp_res = compiler.builder.icmp_signed(op, left, right, 'cmptmp') + cmp_res = self.builder.icmp_signed(op, left, right, 'cmptmp') else: - cmp_res = compiler.builder.icmp_unsigned(op, left, right, 'cmptmp') - return compiler.builder.uitofp(cmp_res, type_map[BOOL], 'booltmp') + cmp_res = self.builder.icmp_unsigned(op, left, right, 'cmptmp') + return self.builder.uitofp(cmp_res, type_map[BOOL], 'booltmp') else: raise SyntaxError('Unknown binary operator', node.op) -def float_ops(compiler, op, left, right, node): +def float_ops(self, op, left, right, node): # Cast values if they're different but compatible if str(left.type) in float_types and \ str(right.type) in float_types and \ @@ -150,38 +187,38 @@ def float_ops(compiler, op, left, right, node): width_left = 0 if str(left.type) == 'float' else 1 width_right = 0 if str(right.type) == 'float' else 1 if width_left > width_right: - right = cast_ops(compiler, right, left.type, node) + right = cast_ops(self, right, left.type, node) else: - left = cast_ops(compiler, left, right.type, node) + left = cast_ops(self, left, right.type, node) if op == PLUS: - return compiler.builder.fadd(left, right, 'faddtmp') + return self.builder.fadd(left, right, 'faddtmp') elif op == MINUS: - return compiler.builder.fsub(left, right, 'fsubtmp') + return self.builder.fsub(left, right, 'fsubtmp') elif op == MUL: - return compiler.builder.fmul(left, right, 'fmultmp') + return self.builder.fmul(left, right, 'fmultmp') elif op == FLOORDIV: - return (compiler.builder.sdiv(cast_ops(compiler, left, ir.IntType(64), node), - cast_ops(compiler, right, ir.IntType(64), node), 'ffloordivtmp')) + return (self.builder.sdiv(cast_ops(self, left, ir.IntType(64), node), + cast_ops(self, right, ir.IntType(64), node), 'ffloordivtmp')) elif op == DIV: - return compiler.builder.fdiv(left, right, 'fdivtmp') + return self.builder.fdiv(left, right, 'fdivtmp') elif op == MOD: - return compiler.builder.frem(left, right, 'fmodtmp') + return self.builder.frem(left, right, 'fmodtmp') elif op == POWER: - temp = compiler.builder.alloca(type_map[DOUBLE]) - compiler.builder.store(left, temp) + temp = self.builder.alloca(type_map[DOUBLE]) + self.builder.store(left, temp) for _ in range(node.right.value - 1): - res = compiler.builder.fmul(compiler.builder.load(temp), left) - compiler.builder.store(res, temp) - return compiler.builder.load(temp) + res = self.builder.fmul(self.builder.load(temp), left) + self.builder.store(res, temp) + return self.builder.load(temp) elif op in (EQUALS, NOT_EQUALS, LESS_THAN, LESS_THAN_OR_EQUAL_TO, GREATER_THAN, GREATER_THAN_OR_EQUAL_TO): - cmp_res = compiler.builder.fcmp_ordered(op, left, right, 'cmptmp') - return compiler.builder.uitofp(cmp_res, type_map[BOOL], 'booltmp') + cmp_res = self.builder.fcmp_ordered(op, left, right, 'cmptmp') + return self.builder.uitofp(cmp_res, type_map[BOOL], 'booltmp') else: raise SyntaxError('Unknown binary operator', node.op) -def cast_ops(compiler, left, right, node): +def cast_ops(self, left, right, node): orig_type = str(left.type) cast_type = str(right) @@ -197,45 +234,45 @@ def cast_ops(compiler, left, right, node): elif cast_type in int_types: # int if orig_type in float_types: # from float if right.signed: - return compiler.builder.fptosi(left, llvm_type_map[cast_type]) + return self.builder.fptosi(left, llvm_type_map[cast_type]) else: - return compiler.builder.fptoui(left, llvm_type_map[cast_type]) + return self.builder.fptoui(left, llvm_type_map[cast_type]) elif orig_type in int_types: # from signed int width_cast = int(cast_type.split("i")[1]) width_orig = int(orig_type.split("i")[1]) if width_cast > width_orig: if left.type.signed: - return compiler.builder.sext(left, llvm_type_map[cast_type]) + return self.builder.sext(left, llvm_type_map[cast_type]) else: - return compiler.builder.zext(left, llvm_type_map[cast_type]) + return self.builder.zext(left, llvm_type_map[cast_type]) elif width_orig > width_cast: - return compiler.builder.trunc(left, llvm_type_map[cast_type]) + return self.builder.trunc(left, llvm_type_map[cast_type]) elif cast_type in float_types: # float if orig_type in int_types: # from signed int if left.type.signed: - return compiler.builder.sitofp(left, type_map[cast_type]) + return self.builder.sitofp(left, type_map[cast_type]) else: - return compiler.builder.uitofp(left, type_map[cast_type]) + return self.builder.uitofp(left, type_map[cast_type]) elif orig_type in float_types: # from float if cast_type == 'double' and orig_type == 'float': - return compiler.builder.fpext(left, llvm_type_map[cast_type]) + return self.builder.fpext(left, llvm_type_map[cast_type]) elif cast_type == 'float' and orig_type == 'double': - return compiler.builder.fptrunc(left, llvm_type_map[cast_type]) + return self.builder.fptrunc(left, llvm_type_map[cast_type]) - elif cast_type == STR: + elif cast_type == str(type_map[STR]): raise NotImplementedError - elif cast_type in (ANY, FUNC, ENUM, DICT, TUPLE): + elif cast_type in (ANY, FUNC, STRUCT, CLASS, ENUM, DICT, LIST, TUPLE): raise TypeError('file={} line={}: Cannot cast from {} to type {}'.format( - compiler.file_name, + self.file_name, node.line_num, orig_type, cast_type )) raise TypeError('file={} line={}: Unknown cast from {} to {}'.format( - compiler.file_name, + self.file_name, node.line_num, orig_type, cast_type diff --git a/src/lesma/compiler/types.py b/src/lesma/compiler/types.py index 5d3ad2a..a820232 100644 --- a/src/lesma/compiler/types.py +++ b/src/lesma/compiler/types.py @@ -34,7 +34,8 @@ def __init__(self): super().__init__() self.name = INT - def type(self): + @staticmethod + def type(): return ir.IntType(64) @@ -93,7 +94,8 @@ def __init__(self): super().__init__() self.name = UINT - def type(self): + @staticmethod + def type(): return ir.IntType(64, False) diff --git a/src/lesma/grammar.py b/src/lesma/grammar.py index 87effb3..e32da0c 100644 --- a/src/lesma/grammar.py +++ b/src/lesma/grammar.py @@ -74,7 +74,7 @@ TUPLE = 'tuple' SET = 'set' # TODO DICT = 'dict' -ENUM = 'enum' # TODO +ENUM = 'enum' FUNC = 'func' STRUCT = 'struct' CLASS = 'class' @@ -83,6 +83,7 @@ TRUE = 'true' FALSE = 'false' NULL = 'null' # TODO +INF = 'inf' # Keywords IF = 'if' @@ -99,7 +100,7 @@ CONST = 'const' NEW = 'new' # TODO SUPER = 'super' # TODO -THIS = 'this' # TODO +SELF = 'self' RETURN = 'return' TRY = 'try' # TODO CATCH = 'catch' # TODO @@ -117,7 +118,7 @@ WITH = 'with' # TODO PASS = 'pass' VOID = 'void' -ALIAS = 'alias' +TYPE = 'type' OVERRIDE = 'override' # TODO ABSTRACT = 'abstract' # TODO ASSERT = 'assert' # TODO @@ -151,17 +152,17 @@ ) KEYWORDS = ( - IF, ELSE, WHILE, FOR, SWITCH, CASE, DEF, SUPER, THIS, RETURN, TRY, CATCH, THROW, + IF, ELSE, WHILE, FOR, SWITCH, CASE, DEF, SUPER, RETURN, TRY, CATCH, THROW, FINALLY, YIELD, BREAK, CONTINUE, DEL, IMPORT, FROM, WITH, PASS, VOID, - CONST, OVERRIDE, ABSTRACT, ASSERT, DEFAULT, NEW, ALIAS, FALLTHROUGH, + CONST, OVERRIDE, ABSTRACT, ASSERT, DEFAULT, NEW, TYPE, FALLTHROUGH, DEFER ) MULTI_WORD_KEYWORDS = (IF, ELSE, ELSE_IF) -TYPES = (ANY, INT, INT8, INT16, INT32, INT64, INT128, UINT, UINT8, UINT16, UINT32, UINT64, UINT128, DOUBLE, FLOAT, COMPLEX, STR, BOOL, LIST, TUPLE, DICT, ENUM, FUNC, STRUCT, CLASS) +TYPES = (ANY, INT, INT8, INT16, INT32, INT64, INT128, UINT, UINT8, UINT16, UINT32, UINT64, UINT128, DOUBLE, FLOAT, COMPLEX, STR, BOOL, LIST, TUPLE, DICT, ENUM, FUNC, LIST, TUPLE, STRUCT, CLASS) -CONSTANTS = (TRUE, FALSE, NULL) +CONSTANTS = (TRUE, FALSE, NULL, INF) PRINT = 'print' INPUT = 'input' @@ -169,7 +170,7 @@ BUILTIN_FUNCTIONS = (PRINT, INPUT) # For Lexer -TYPE = 'TYPE' +LTYPE = 'LTYPE' NUMBER = 'NUMBER' STRING = 'STRING' OP = 'OP' diff --git a/src/lesma/lexer.py b/src/lesma/lexer.py index 4929053..f7afa75 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -24,10 +24,7 @@ def __str__(self): class Lexer(object): def __init__(self, text, file_name=None): - if len(text) == 0: - error('empty input') - - self.text = text + self.text = self.sanitize_text(text) self.file_name = file_name self.pos = 0 self.current_char = self.text[self.pos] @@ -47,6 +44,15 @@ def next_char(self): self.current_char = self.text[self.pos] self.char_type = self.get_type(self.current_char) + @staticmethod + def sanitize_text(text): + if len(text) == 0: + error('empty input') + elif text[-1] != '\n': + text += '\n' + + return text + def reset_word(self): old_word = self.word self.word = '' @@ -242,24 +248,37 @@ def get_next_token(self): return Token(KEYWORD, self.reset_word(), self.line_num, self.indent_level) elif self.word in TYPES: - return Token(TYPE, self.reset_word(), self.line_num, self.indent_level) + return Token(LTYPE, self.reset_word(), self.line_num, self.indent_level) elif self.word in CONSTANTS: return Token(CONSTANT, self.reset_word(), self.line_num, self.indent_level) return Token(NAME, self.utf8ToAscii(self.reset_word()), self.line_num, self.indent_level) if self.word_type == NUMERIC: - while self.char_type == NUMERIC or self.current_char == DOT and self.peek(1) != DOT: + base = 10 + while self.char_type == NUMERIC or (self.current_char == DOT and self.peek(1) != DOT) or \ + self.current_char in ('a', 'b', 'c', 'd', 'e', 'f', 'x', 'o'): self.word += self.current_char - self.next_char() if self.char_type == ALPHANUMERIC: - raise SyntaxError('Variables cannot start with numbers') + if self.current_char in ('b', 'x', 'o') and self.word.startswith('0') and len(self.word) == 2: + if self.current_char == 'b': + base = 2 + elif self.current_char == 'x': + base = 16 + elif self.current_char == 'o': + base = 8 + + self.word = "" + elif not (base == 16 and self.current_char in ('a', 'b', 'c', 'd', 'e', 'f')): + error("Unexpected number parsing") + + self.next_char() value = self.reset_word() if '.' in value: value = Decimal(value) value_type = DOUBLE else: - value = int(value) + value = int(value, base) value_type = INT return Token(NUMBER, value, self.line_num, self.indent_level, value_type=value_type) @@ -281,7 +300,8 @@ def analyze(self): token = self.get_next_token() yield token - def utf8ToAscii(self, string): + @staticmethod + def utf8ToAscii(string): unicode = "{}".format(string.encode("unicode_escape")) unicode = unicode[2:len(unicode) - 1] diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 2ed1f85..364ba36 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -29,7 +29,7 @@ def eat_type(self, *token_type): if self.current_token.type in token_type: self.next_token() else: - error('file={} line={} Syntax Error: expected {}'.format(self.file_name, node.line_num, ", ".join(token_type))) + error('file={} line={} Syntax Error: expected {}'.format(self.file_name, self.line_num, ", ".join(token_type))) def eat_value(self, *token_value): if self.current_token.value in token_value: @@ -65,6 +65,22 @@ def program(self): root.children.extend(comp.children) return Program(root) + def enum_declaration(self): + self.eat_value(ENUM) + name = self.next_token() + self.user_types.append(name.value) + self.eat_type(NEWLINE) + self.indent_level += 1 + fields = [] + while self.current_token.indent_level > name.indent_level: + field = self.next_token().value + fields.append(field) + + self.eat_type(NEWLINE) + + self.indent_level -= 1 + return EnumDeclaration(name.value, fields, self.line_num) + def struct_declaration(self): self.eat_value(STRUCT) name = self.next_token() @@ -72,20 +88,24 @@ def struct_declaration(self): self.eat_type(NEWLINE) self.indent_level += 1 fields = OrderedDict() + defaults = {} while self.current_token.indent_level > name.indent_level: field = self.next_token().value self.eat_value(COLON) field_type = self.type_spec() fields[field] = field_type + if self.current_token.value == ASSIGN: + self.eat_value(ASSIGN) + defaults[field] = self.expr() + self.eat_type(NEWLINE) self.indent_level -= 1 - return StructDeclaration(name.value, fields, self.line_num) + return StructDeclaration(name.value, fields, defaults, self.line_num) def class_declaration(self): base = None - constructor = None - methods = None - class_fields = OrderedDict() + methods = [] + fields = OrderedDict() instance_fields = None self.in_class = True self.next_token() @@ -105,13 +125,13 @@ def class_declaration(self): self.eat_type(NAME) self.eat_value(COLON) field_type = self.type_spec() - class_fields[field] = field_type + fields[field] = field_type self.eat_type(NEWLINE) - if self.current_token.value == NEW: - constructor = self.constructor_declaration(class_name) + if self.current_token.value == DEF: + methods.append(self.method_declaration(class_name)) self.indent_level -= 1 self.in_class = False - return ClassDeclaration(class_name.value, base, constructor, methods, class_fields, instance_fields) + return ClassDeclaration(class_name.value, base, methods, fields, instance_fields) def variable_declaration(self): var_node = Var(self.current_token.value, self.line_num) @@ -126,12 +146,12 @@ def variable_declaration(self): def variable_declaration_assignment(self, declaration): return Assign(declaration, self.next_token().value, self.expr(), self.line_num) - def alias_declaration(self): - self.eat_value(ALIAS) + def type_declaration(self): + self.eat_value(TYPE) name = self.next_token() self.user_types.append(name.value) self.eat_value(ASSIGN) - return AliasDeclaration(name.value, self.type_spec(), self.line_num) + return TypeDeclaration(name.value, self.type_spec(), self.line_num) def function_declaration(self): op_func = False @@ -204,18 +224,21 @@ def function_declaration(self): if len(params) not in (1, 2): # TODO: move this to type checker error("Operators can either be unary or binary, and the number of parameters do not match") - name.value = 'operator' + '.' + name.value + name.value = OPERATOR + '.' + name.value for param in params: - name.value += '.' + str(type_map[str(params[param].value)]) + type_name = str(type_map[str(params[param].value)]) if str(params[param].value) in type_map else str(params[param].value) + name.value += '.' + type_name return FuncDecl(name.value, return_type, params, stmts, self.line_num, param_defaults, vararg) - def constructor_declaration(self, class_name): - self.eat_value(NEW) + def method_declaration(self, class_name): + self.eat_value(DEF) + name = self.next_token() self.eat_value(LPAREN) params = OrderedDict() param_defaults = {} vararg = None + params[SELF] = class_name while self.current_token.value != RPAREN: param_name = self.current_token.value self.eat_type(NAME) @@ -225,7 +248,7 @@ def constructor_declaration(self, class_name): else: param_type = self.variable(self.current_token) - params[self.current_token.value] = param_type + params[param_name] = param_type if self.current_token.value != RPAREN: if self.current_token.value == ASSIGN: self.eat_value(ASSIGN) @@ -241,11 +264,23 @@ def constructor_declaration(self, class_name): if self.current_token.value != RPAREN: self.eat_value(COMMA) self.eat_value(RPAREN) + + if self.current_token.value != ARROW: + return_type = Void() + else: + self.eat_value(ARROW) + if self.current_token.value == VOID: + return_type = Void() + self.next_token() + else: + return_type = self.type_spec() + self.eat_type(NEWLINE) self.indent_level += 1 stmts = self.compound_statement() self.indent_level -= 1 - return FuncDecl('{}.constructor'.format(class_name), Void(), params, stmts, self.line_num, param_defaults, vararg) + + return FuncDecl("{}.{}".format(class_name.value, name.value), return_type, params, stmts, self.line_num, param_defaults, vararg) def bracket_literal(self): token = self.next_token() @@ -291,12 +326,24 @@ def type_spec(self): if token.value in self.user_types: self.eat_type(NAME) return Type(token.value, self.line_num) - self.eat_type(TYPE) + self.eat_type(LTYPE) type_spec = Type(token.value, self.line_num) func_ret_type = None func_params = OrderedDict() param_num = 0 - if self.current_token.value == LSQUAREBRACKET and token.value == FUNC: + if self.current_token.value == LSQUAREBRACKET and token.value in (LIST, TUPLE): + self.next_token() + while self.current_token.value != RSQUAREBRACKET: + param_type = self.type_spec() + func_params[str(param_num)] = param_type + param_num += 1 + if self.current_token.value != RSQUAREBRACKET: + self.eat_value(COMMA) + + self.eat_value(RSQUAREBRACKET) + type_spec.func_params = func_params + + elif self.current_token.value == LSQUAREBRACKET and token.value == FUNC: self.next_token() while self.current_token.value != RSQUAREBRACKET: param_type = self.type_spec() @@ -378,13 +425,15 @@ def statement(self): node = self.name_statement() elif self.current_token.value == DEF: node = self.function_declaration() - elif self.current_token.value == ALIAS: - node = self.alias_declaration() - elif self.current_token.type == TYPE: + elif self.current_token.value == TYPE: + node = self.type_declaration() + elif self.current_token.type == LTYPE: if self.current_token.value == STRUCT: node = self.struct_declaration() elif self.current_token.value == CLASS: node = self.class_declaration() + elif self.current_token.value == ENUM: + node = self.enum_declaration() elif self.current_token.value == EOF: return else: @@ -403,7 +452,7 @@ def square_bracket_expression(self, token): break self.eat_value(RSQUAREBRACKET) return Collection(LIST, self.line_num, False, *items) - elif self.current_token.type == TYPE: + elif self.current_token.type == LTYPE: type_token = self.next_token() if self.current_token.value == COMMA: return self.dictionary_assignment(token) @@ -653,13 +702,15 @@ def assignment_statement(self, token): node = IncrementAssign(left, token.value, self.line_num) elif token.value == COLON: type_node = self.type_spec() - var = VarDecl(left, type_node, self.line_num) node = self.variable_declaration_assignment(var) else: raise SyntaxError('Unknown assignment operator: {}'.format(token.value)) return node + def typ(self, token): + return Type(token.value, self.line_num) + def variable(self, token, read_only=False): return Var(token.value, self.line_num, read_only) @@ -670,6 +721,9 @@ def factor(self): token = self.current_token preview = self.preview() if preview.value == DOT: + if self.preview(2).type == NAME and self.preview(3).value == LPAREN: + return self.property_or_method(self.next_token()) + self.next_token() return self.dot_access(token) elif token.value in (PLUS, MINUS, BINARY_ONES_COMPLIMENT): @@ -686,7 +740,7 @@ def factor(self): return Str(token.value, self.line_num) elif token.value == DEF: return self.function_declaration() - elif token.type == TYPE: + elif token.type == LTYPE: return self.type_spec() elif token.value == LPAREN: if self.func_args or not self.find_until(COMMA, RPAREN): @@ -716,6 +770,8 @@ def factor(self): return self.curly_bracket_expression(token) elif token.type == NAME: self.next_token() + if token.value in self.user_types: + return self.typ(token) return self.variable(token) elif token.type == CONSTANT: self.next_token() diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index d7c5ed9..59d3486 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -1,6 +1,6 @@ from lesma.ast import Collection, Var, VarDecl, DotAccess, CollectionAccess from lesma.grammar import * -from lesma.visitor import AliasSymbol, CollectionSymbol, FuncSymbol, NodeVisitor, StructSymbol, ClassSymbol, VarSymbol +from lesma.visitor import TypeSymbol, CollectionSymbol, FuncSymbol, NodeVisitor, StructSymbol, EnumSymbol, ClassSymbol, VarSymbol from lesma.utils import warning, error @@ -21,7 +21,7 @@ def types_compatible(left_type, right_type): int_type = ('i8', 'i16', 'i32', 'i64', 'int8', 'int16', 'int32', 'int64', 'int') float_type = ('float', 'double') num_type = int_type + float_type - if (left_type is right_type) or \ + if (left_type == right_type) or \ (left_type in num_type and right_type in num_type): return True @@ -106,6 +106,8 @@ def visit_continue(self, node): def visit_constant(self, node): if node.value == TRUE or node.value == FALSE: return self.search_scopes(BOOL) + elif node.value == INF: + return self.search_scopes(DOUBLE) return NotImplementedError @@ -129,9 +131,14 @@ def visit_assign(self, node): # TODO clean up this mess of a function var_name = node.left.value.value value = self.infer_type(node.left.type) value.accessed = True - elif hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), (StructSymbol, ClassSymbol)): + if isinstance(node.right, Collection): + _, collection_type = self.visit(node.right) + + if value.name in (TUPLE, LIST) and node.right.type != value.name: + error('file={} line={}: Contradicting {}-{} declaration'.format(self.file_name, node.line_num, value.name, node.right.type)) + elif hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), (StructSymbol, EnumSymbol, ClassSymbol)): var_name = node.left.value - value = self.infer_type(self.search_scopes(node.right.name)) + value = self.search_scopes(node.right.name) value.accessed = True elif isinstance(node.right, Collection): var_name = node.left.value @@ -140,6 +147,11 @@ def visit_assign(self, node): # TODO clean up this mess of a function field_assignment = True var_name = self.visit(node.left) value = self.visit(node.right) + elif isinstance(node.right, DotAccess): + var_name = node.left.value + value = self.search_scopes(node.right.obj) + value.accessed = True + value = self.infer_type(value) elif isinstance(node.left, CollectionAccess): collection_assignment = True var_name = node.left.collection.value @@ -196,7 +208,7 @@ def visit_assign(self, node): # TODO clean up this mess of a function return if lookup_var.type is value.type: return - if isinstance(value, AliasSymbol): + if isinstance(value, TypeSymbol): value.accessed = True if value.type is self.search_scopes(FUNC): if value.type.return_type == lookup_var.type: @@ -212,9 +224,6 @@ def visit_opassign(self, node): left_type = self.infer_type(left) right_type = self.infer_type(right) any_type = self.search_scopes(ANY) - # if left_type in (self.search_scopes(DOUBLE), self.search_scopes(FLOAT)): - # if right_type in (self.search_scopes(INT), self.search_scopes(DOUBLE), self.search_scopes(FLOAT)): - # return left_type if types_compatible(left_type, right_type) or left_type is any_type or right_type is any_type: return left_type else: @@ -241,14 +250,14 @@ def visit_var(self, node): error('file={} line={}: Name Error: {}'.format(self.file_name, node.line_num, repr(var_name))) else: if not val.val_assigned: - error('file={} line={}: {} is being accessed before it was defined'.format(self.file_name, var_name, node.line_num)) + error('file={} line={}: {} is being accessed before it was defined'.format(self.file_name, node.line_num, var_name)) val.accessed = True return val def visit_binop(self, node): if node.op == CAST or node.op in (IS, IS_NOT): self.visit(node.left) - if node.right.value not in TYPES: + if node.right.value not in TYPES and not isinstance(self.search_scopes(node.right.value), (EnumSymbol, ClassSymbol, StructSymbol)): error('file={} line={}: type expected for operation {}, got {} : {}'.format(self.file_name, node.line_num, node.op, node.left, node.right)) return self.infer_type(self.visit(node.right)) else: @@ -288,18 +297,7 @@ def visit_compound(self, node): return results def visit_typedeclaration(self, node): - typs = [] - for t in node.collection: - typs.append(self.visit(t)) - if len(typs) == 1: - typs = typs[0] - else: - typs = tuple(typs) - typ = AliasSymbol(node.name.value, typs) - self.define(typ.name, typ) - - def visit_aliasdeclaration(self, node): - typ = AliasSymbol(node.name, node.collection.value) + typ = TypeSymbol(node.name, self.search_scopes(node.collection.value)) self.define(typ.name, typ) def visit_externfuncdecl(self, node): @@ -323,7 +321,7 @@ def visit_externfuncdecl(self, node): var_type = self.search_scopes(v.value) if var_type is self.search_scopes(FUNC): sym = FuncSymbol(k, v.func_ret_type, None, None) - elif isinstance(var_type, AliasSymbol): + elif isinstance(var_type, TypeSymbol): var_type.accessed = True if var_type.type is self.search_scopes(FUNC): sym = FuncSymbol(k, var_type.type.return_type, None, None) @@ -359,7 +357,7 @@ def visit_funcdecl(self, node): var_type = self.search_scopes(v.value) if var_type is self.search_scopes(FUNC): sym = FuncSymbol(k, v.func_ret_type, v.func_params, None) - elif isinstance(var_type, AliasSymbol): + elif isinstance(var_type, TypeSymbol): var_type.accessed = True if var_type.type is self.search_scopes(FUNC): sym = FuncSymbol(k, var_type.type.return_type, v.func_params, None) @@ -408,7 +406,7 @@ def visit_funccall(self, node): func = self.search_scopes(func_name) parameters = None parameter_defaults = None - if isinstance(func, (StructSymbol, ClassSymbol)): + if isinstance(func, (StructSymbol, ClassSymbol, EnumSymbol)): parameters = func.fields parameter_defaults = func.fields else: @@ -436,35 +434,43 @@ def visit_funccall(self, node): func.accessed = True return func.type - def visit_methodcall(self, node): # TODO: Not done here! - method_name = node.name - method = self.search_scopes(method_name) - for x, param in enumerate(method.parameters.values()): - if x < len(node.arguments): - var = self.visit(node.arguments[x]) - param_ss = self.search_scopes(param.value) - if param_ss != self.search_scopes(ANY) and param.value != var.name and param.value != var.type.name: - raise TypeError - else: - method_param_keys = list(method.parameters.keys()) - if method_param_keys[x] not in node.named_arguments.keys() and method_param_keys[x] not in method.parameter_defaults.keys(): - raise TypeError('Missing arguments to method') - else: - if method_param_keys[x] in node.named_arguments.keys(): - if param.value != self.visit(node.named_arguments[method_param_keys[x]]).name: - raise TypeError - if method is None: - error('file={} line={}: Name Error: {}'.format(self.file_name, node.line_num, repr(method_name))) - else: - method.accessed = True - return method.type + def visit_methodcall(self, node): # TODO: Finish this, make Symbols for Classes and Methods + pass + # method_name = node.name + # method = self.search_scopes("{}.{}".format(self.search_scopes(node.obj).type.name, method_name)) + # for x, param in enumerate(method.parameters.values()): + # if x < len(node.arguments): + # var = self.visit(node.arguments[x]) + # param_ss = self.search_scopes(param.value) + # if param_ss != self.search_scopes(ANY) and param.value != var.name and param.value != var.type.name: + # raise TypeError + # else: + # method_param_keys = list(method.parameters.keys()) + # if method_param_keys[x] not in node.named_arguments.keys() and method_param_keys[x] not in method.parameter_defaults.keys(): + # raise TypeError('Missing arguments to method') + # else: + # if method_param_keys[x] in node.named_arguments.keys(): + # if param.value != self.visit(node.named_arguments[method_param_keys[x]]).name: + # raise TypeError + # if method is None: + # error('file={} line={}: Name Error: {}'.format(self.file_name, node.line_num, repr(method_name))) + # else: + # method.accessed = True + # return method.return_type def visit_structdeclaration(self, node): sym = StructSymbol(node.name, node.fields) self.define(sym.name, sym) + def visit_enumdeclaration(self, node): + sym = EnumSymbol(node.name, node.fields) + self.define(sym.name, sym) + def visit_classdeclaration(self, node): - sym = ClassSymbol(node.name, node.class_fields) + class_methods = [FuncSymbol(method.name, method.return_type, method.parameters, method.body) for method in node.methods] + sym = ClassSymbol(node.name, node.fields, class_methods) + for method in class_methods: + self.define(method.name, method) self.define(sym.name, sym) def visit_return(self, node): @@ -482,7 +488,11 @@ def visit_vardecl(self, node): type_name = node.type.value type_symbol = self.search_scopes(type_name) var_name = node.value.value - var_symbol = VarSymbol(var_name, type_symbol) + if type_name in (LIST, TUPLE): + var_symbol = CollectionSymbol(var_name, type_symbol, node.type.func_params['0'].value) + var_symbol.read_only = type_name == TUPLE + else: + var_symbol = VarSymbol(var_name, type_symbol) self.define(var_symbol.name, var_symbol) def visit_collection(self, node): @@ -500,7 +510,9 @@ def visit_collection(self, node): def visit_dotaccess(self, node): obj = self.search_scopes(node.obj) obj.accessed = True - if node.field not in obj.type.fields: + if isinstance(obj, EnumSymbol): + return obj + elif node.field not in obj.type.fields: error('file={} line={}: Invalid property {} of variable {}'.format( self.file_name, node.line_num, node.field, node.obj)) return self.visit(obj.type.fields[node.field]) diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 91cd090..c295d87 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -65,6 +65,17 @@ def __str__(self): __repr__ = __str__ +class EnumSymbol(Symbol): + def __init__(self, name, fields): + super().__init__(name) + self.fields = fields + self.accessed = False + self.val_assigned = False + + def __str__(self): + return ENUM + + class StructSymbol(Symbol): def __init__(self, name, fields): super().__init__(name) @@ -74,9 +85,10 @@ def __init__(self, name, fields): class ClassSymbol(Symbol): - def __init__(self, name, class_fields): + def __init__(self, name, fields, methods): super().__init__(name) - self.class_fields = class_fields + self.fields = fields + self.methods = methods self.accessed = False self.val_assigned = False @@ -87,6 +99,7 @@ def __init__(self, name, var_type, item_types): self.item_types = item_types self.accessed = False self.val_assigned = False + self.read_only = False class FuncSymbol(Symbol): @@ -104,7 +117,7 @@ def __str__(self): __repr__ = __str__ -class AliasSymbol(Symbol): +class TypeSymbol(Symbol): def __init__(self, name, types): super().__init__(name, types) self.accessed = False @@ -179,6 +192,8 @@ def second_scope(self): return self._scope[-2] if len(self._scope) >= 2 else None def search_scopes(self, name, level=None): + if name in (None, []): + return None if level: if name in self._scope[level]: return self._scope[level][name] @@ -236,6 +251,8 @@ def infer_type(self, value): return self.search_scopes(STR) if isinstance(value, StructSymbol): return self.search_scopes(STRUCT) + if isinstance(value, EnumSymbol): + return self.search_scopes(ENUM) if isinstance(value, ClassSymbol): return self.search_scopes(CLASS) if isinstance(value, bool): diff --git a/tests/io/anon_func.les b/tests/io/anon_func.les index a25960d..c0dea54 100644 --- a/tests/io/anon_func.les +++ b/tests/io/anon_func.les @@ -4,5 +4,16 @@ x: func[int, int] -> int = def (x: int, y: int) -> int else return x * y +y = def (x: int, y: int) -> int + if x > y + return x + y + else + return x * y + +z: func = def() + print(5) + + print(x(2,1)) -print(x(2,4)) \ No newline at end of file +print(y(2,4)) +z() \ No newline at end of file diff --git a/tests/io/anon_func.output b/tests/io/anon_func.output index 036ee18..c8857a3 100644 --- a/tests/io/anon_func.output +++ b/tests/io/anon_func.output @@ -1,2 +1,3 @@ 3 -8 \ No newline at end of file +8 +5 \ No newline at end of file diff --git a/tests/io/class.les b/tests/io/class.les new file mode 100644 index 0000000..8105175 --- /dev/null +++ b/tests/io/class.les @@ -0,0 +1,41 @@ +class DemoClass + def new() + pass + +y = DemoClass() + + +class Thing + a_thing: int + b_thing: float + def new(b_thing: float = 3.14) + self.a_thing = 1 + self.b_thing = b_thing + print(5) + print(self.just_demo()) + + def another() + print(self.a_thing) + self.a_thing = 7 + print(self.a_thing) + + def just_demo() -> int + return 5 + + +x: Thing = Thing() +print(x.a_thing) +x.a_thing = 3 +print(x.a_thing) +x.another() +print(x.just_demo()) +print(x.b_thing) + +def giveMeAThing(some: Thing) -> Thing + some.a_thing = 99 + return some + +z = giveMeAThing(x) +print(z.b_thing) +print(z.a_thing) +print(z is Thing) \ No newline at end of file diff --git a/tests/io/class.output b/tests/io/class.output new file mode 100644 index 0000000..f5099a4 --- /dev/null +++ b/tests/io/class.output @@ -0,0 +1,11 @@ +5 +5 +1 +3 +3 +7 +5 +3.14 +3.14 +99 +true \ No newline at end of file diff --git a/tests/io/enum.les b/tests/io/enum.les new file mode 100644 index 0000000..820ad8d --- /dev/null +++ b/tests/io/enum.les @@ -0,0 +1,18 @@ +enum Color + Red + White + +x: Color = Color.White +y: Color = Color.Red +print(x == Color.White) +print(x == Color.Red) +print(x == y) + +# Testing enums as parameters and return values +def giveMeSomeColor(some: Color) -> Color + return some + +cir: Color = Color.Red +xy = giveMeSomeColor(cir) +print(xy == Color.Red) +print(xy is Color) \ No newline at end of file diff --git a/tests/io/enum.output b/tests/io/enum.output new file mode 100644 index 0000000..f3956f9 --- /dev/null +++ b/tests/io/enum.output @@ -0,0 +1,5 @@ +true +false +false +true +true \ No newline at end of file diff --git a/tests/io/function.les b/tests/io/function.les index d41e270..5f11b78 100644 --- a/tests/io/function.les +++ b/tests/io/function.les @@ -1,3 +1,4 @@ +# Basic function def fib(n: int) -> int a = 0 b = 1 @@ -7,6 +8,7 @@ def fib(n: int) -> int b = prev_a + b return a +# Recursive def fib_rec(n: int) -> int if n == 0 return 0 @@ -14,11 +16,13 @@ def fib_rec(n: int) -> int return 1 return fib_rec(n - 1) + fib_rec(n - 2) +# Default value def factorial(n: int = 5) -> int if n <= 1 return 1 return n * factorial(n - 1) +# Function as parameter def do_stuff(x: int, callback: func[int] -> int) -> int return callback(5) diff --git a/tests/io/list.les b/tests/io/list.les index 8ce4c6f..7557567 100644 --- a/tests/io/list.les +++ b/tests/io/list.les @@ -2,4 +2,13 @@ things = [1, 2, 3] # List, homogeneous print(things[0]) print(things[1*1+1]) things[2] = 1002 -print(things[2]) \ No newline at end of file +print(things[2]) +things[2]++ +print(things[2]) + +x: list[double] = [1.5] +print(x[0]) +x[0] = 5.5 + +y = [2.5,3.5, inf] +print(y[0]) \ No newline at end of file diff --git a/tests/io/list.output b/tests/io/list.output index 5efd464..9f74aa2 100644 --- a/tests/io/list.output +++ b/tests/io/list.output @@ -1,3 +1,6 @@ 1 3 -1002 \ No newline at end of file +1002 +1003 +1.5 +2.5 \ No newline at end of file diff --git a/tests/io/operators.les b/tests/io/operators.les index dc860e5..ac1eae1 100644 --- a/tests/io/operators.les +++ b/tests/io/operators.les @@ -19,4 +19,9 @@ print(5 != 5) print(5 < 9) print(5 <= 9) print(3 > 2) -print(3 >= 2) \ No newline at end of file +print(3 >= 2) +print(inf > 999999999999) +print(-inf < -999999999999) +print(0xa) +print(0b1010) +print(0o12) \ No newline at end of file diff --git a/tests/io/operators.output b/tests/io/operators.output index 1aec396..c8045d6 100644 --- a/tests/io/operators.output +++ b/tests/io/operators.output @@ -19,4 +19,9 @@ false true true true -true \ No newline at end of file +true +true +true +10 +10 +10 \ No newline at end of file diff --git a/tests/io/range.les b/tests/io/range.les index 7bf65b3..6257dd5 100644 --- a/tests/io/range.les +++ b/tests/io/range.les @@ -1,2 +1,6 @@ for i in 0..11 + print(i) + +x = 11 +for i in 0..x print(i) \ No newline at end of file diff --git a/tests/io/range.output b/tests/io/range.output index 7bd5250..e405fac 100644 --- a/tests/io/range.output +++ b/tests/io/range.output @@ -8,4 +8,15 @@ 7 8 9 +10 +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 10 \ No newline at end of file diff --git a/tests/io/struct.les b/tests/io/struct.les index 3b44d89..cae9861 100644 --- a/tests/io/struct.les +++ b/tests/io/struct.les @@ -1,10 +1,22 @@ struct Circle radius: int x: int - y: int + y: int = 99 -cir: Circle = Circle(radius=5, x=2, y=4) +cir: Circle = Circle(radius=5, x=2) +anotherCir = Circle(radius=1, x=3) +print(anotherCir.x) print(cir.radius + cir.x) cir.x = 20 -print(cir.x) \ No newline at end of file +print(cir.x) +print(cir.y) + +def giveMeACircle(some: Circle) -> Circle + some.y = -1 + return some + +a_circle = giveMeACircle(cir) +print(a_circle.x) +print(a_circle.y) +print(a_circle is Circle) \ No newline at end of file diff --git a/tests/io/struct.output b/tests/io/struct.output index 1845bb0..cf87bdb 100644 --- a/tests/io/struct.output +++ b/tests/io/struct.output @@ -1,2 +1,7 @@ +3 7 -20 \ No newline at end of file +20 +99 +20 +-1 +true \ No newline at end of file diff --git a/tests/io/tuple.les b/tests/io/tuple.les index 10e5241..23d5cbb 100644 --- a/tests/io/tuple.les +++ b/tests/io/tuple.les @@ -1,2 +1,6 @@ x = (1,2,3,4) -print(x[0]) \ No newline at end of file +print(x[0]) +y = (1.5,3.5) +print(y[1]) +z: tuple[double] = (2.5,3.5, inf) +print(z[0]) \ No newline at end of file diff --git a/tests/io/tuple.output b/tests/io/tuple.output index 56a6051..33462b0 100644 --- a/tests/io/tuple.output +++ b/tests/io/tuple.output @@ -1 +1,3 @@ -1 \ No newline at end of file +1 +3.5 +2.5 \ No newline at end of file diff --git a/tests/io/typedef.les b/tests/io/typedef.les new file mode 100644 index 0000000..9eb09e6 --- /dev/null +++ b/tests/io/typedef.les @@ -0,0 +1,3 @@ +type my_float = float +x: my_float = 3.5 +print(x) \ No newline at end of file diff --git a/tests/io/typedef.output b/tests/io/typedef.output new file mode 100644 index 0000000..56e972a --- /dev/null +++ b/tests/io/typedef.output @@ -0,0 +1 @@ +3.5 \ No newline at end of file diff --git a/tests/io/varassign.les b/tests/io/varassign.les new file mode 100644 index 0000000..64e756a --- /dev/null +++ b/tests/io/varassign.les @@ -0,0 +1,40 @@ +a1: int = 1 +a2: int8 = 2 +a3: int16 = 3 +a4: int32 = 4 +a5: int64 = 5 +a6: int128 = 6 +a7: double = 6.5 +a8: float = 7.69 +a9: bool = true +a10: str = "Hey there" +a11: list[int] = [1,2,3,4] +a12: tuple[double] = (1.5,3.5,9.0) +# a13: list[str] = ["Hey", "There"] +# a14: list[bool] = [true, false, false] +a15: double = inf +a16: double = -inf + +b1 = 1 as int +b2 = 2 as int8 +b3 = 3 as int16 +b4 = 4 as int32 +b5 = 5 as int64 +b6 = 6 as int128 +b7 = 6.5 as double +b8 = 7.69 as float +b9 = true +b10 = "Hey there" +b11 = [1,2,3,4] +b12 = (1.5,3.5,9.0) +# b13 = ["Hey", "There"] +# b14 = [true, false, false] +b15 = inf +b16 = -inf + +x = 5 +y = 7.5 +z = "Hey there" +print(x) +print(y) +print(z) \ No newline at end of file diff --git a/tests/io/vardecl.output b/tests/io/varassign.output similarity index 100% rename from tests/io/vardecl.output rename to tests/io/varassign.output diff --git a/tests/io/vardecl.les b/tests/io/vardecl.les index a3182bc..906699a 100644 --- a/tests/io/vardecl.les +++ b/tests/io/vardecl.les @@ -1,21 +1,12 @@ -a1: int = 1 -a2: int8 = 2 -a3: int16 = 3 -a4: int32 = 4 -a5: int64 = 5 -a6: int128 = 6 -a7: double = 6.5 -a8: float = 7.69 -a10: bool = true -# a11: str = "Hey there" -a12 = [1,2,3,4] -# a13 = [1.5,3.5,9.0] -# a14 = ["Hey", "There"] -# a15 = [true, false, false] - -x: int = 5 -y = 7.5 -z = "Hey there" -print(x) -print(y) -print(z) \ No newline at end of file +a1: int +a2: int8 +a3: int16 +a4: int32 +a5: int64 +a6: int128 +a8: float +a7: double +a10: bool +a11: str +a12: list[int] +a13: tuple[float] \ No newline at end of file diff --git a/tests/test_all.py b/tests/test_all.py index 3a31508..356e471 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -1,31 +1,34 @@ import os +import sys import pytest from subprocess import Popen, PIPE def get_tests(): tests = [] - for file in os.listdir("./tests/io"): - if file.endswith(".les"): - tests.append(os.path.basename(file).split('.')[0]) - + path = os.path.dirname(__file__) + for unittest in os.listdir(os.path.join(path, "io")): + if unittest.endswith(".les"): + tests.append(os.path.basename(unittest).split('.')[0]) return tests -# Base test for all files +# Base test for Lesma script files @pytest.mark.parametrize("test_name", get_tests()) def test_base(test_name): - proc = Popen(["python3", "src/les.py", "run", f'tests/io/{test_name}.les'], stdout=PIPE, stderr=PIPE) + path = os.path.join(os.path.dirname(__file__), os.pardir) + proc = Popen([sys.executable, os.path.join(path, "src", "les.py"), + "run", os.path.join(path, "tests", "io", test_name + ".les")], + stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = proc.communicate() - output = out.decode('utf-8').strip() - error = err.decode('utf-8').strip() + output = out.strip() + error = err.strip() rc = proc.returncode assert 'Error:' not in error assert rc == 0 if output: - with open(f'tests/io/{test_name}.output') as expected: + with open(os.path.join(path, "tests", "io", test_name + ".output"), newline=None) as expected: exp_str = "".join(expected.readlines()) assert output == exp_str - expected.close()