From a278c6b95c53c614514be1c32044d5db4484b727 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 29 Dec 2018 14:36:36 +0100 Subject: [PATCH 01/24] Added module debug info --- src/lesma/compiler/code_generator.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 2bc548d..c27b4a8 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -818,11 +818,27 @@ def stringz(string): def generate_code(self, node): return self.visit(node) + def add_debug_info(self, optimize, filename): + di_file = self.module.add_debug_info("DIFile", { + "filename": os.path.basename(os.path.abspath(filename)), + "directory": os.path.dirname(os.path.abspath(filename)), + }) + di_module = self.module.add_debug_info("DICompileUnit", { + "language": ir.DIToken("DW_LANG_Python"), + "file": di_file, + "producer": "lesma", + "runtimeVersion": 1, + "isOptimized": optimize, + }, is_distinct=True) + # self.module.add_global(di_module) + self.module.add_named_metadata('debug_info', [di_file, di_module]) + def evaluate(self, optimize=True, ir_dump=False, timer=False): if ir_dump and not optimize: for func in self.module.functions: if func.name == "main": print(func) + llvmmod = llvm.parse_assembly(str(self.module)) if optimize: pmb = llvm.create_pass_manager_builder() @@ -846,6 +862,8 @@ def compile(self, filename, optimize=True, output=None, emit_llvm=False): spinner = Spinner() spinner.startSpinner("Compiling") compile_time = time() + + self.add_debug_info(optimize, filename) program_string = llvm.parse_assembly(str(self.module)) prog_str = str(program_string) From 53d838d09cc4517ed495c5485fe5ec4eb99ab21d Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 29 Dec 2018 14:50:31 +0100 Subject: [PATCH 02/24] Type casting bug fixed --- src/lesma/compiler/operations.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index a472526..5d05bd5 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -63,6 +63,10 @@ def binary_op(compiler, node): elif isinstance(left.type, ir.IntType) and isinstance(right.type, ir.IntType): return int_ops(compiler, 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) + elif isinstance(right.type, ir.IntType): + left = cast_ops(compiler, right, left.type, node) return float_ops(compiler, op, left, right, node) From 796a97b3617cb073aed0a8016ad192132f551f30 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 29 Dec 2018 14:52:45 +0100 Subject: [PATCH 03/24] Updated docs, adjusted TODO for 0.3.0 roadmap --- README.md | 2 +- docs/TODO.md | 16 ++--------- docs/examples.md | 4 ++- docs/features/keywords.md | 2 +- docs/features/operators.md | 4 +-- docs/features/syntax.md | 4 +-- docs/features/types.md | 58 +++++++++++++++++++++++--------------- docs/index.md | 2 +- 8 files changed, 48 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index e5fb73e..6789530 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Currently an early Work in Progress, and **many thanks** to [Ayehavgunne](https: ## 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 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 +- **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 diff --git a/docs/TODO.md b/docs/TODO.md index e356208..51e7a6d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,40 +1,30 @@ # TODO ## Fixes -- [x] Fix **break** and **continue** not branching if the parent block is not the loop -- [x] Fix constant declaration not allowing types - [ ] Fix Type declaration not expecting square brackets (for lists) - [ ] Fix input function -- [x] Should not allow declaring the type to an existing variable - [ ] Fix not being able to return user-defined structs and classes - [ ] Fix not being able to overload operators on user-defined structs and classes -- [x] Fix Python error on empty input - [ ] Unicode doesn't work on Windows platforms - [ ] Fix string and list type declaration not working ## Improvements - [ ] Allow any type for lists/tuples (currently only int) -- [ ] Allow any type for range (currently only int) -- [x] Allow any type for casting -- [x] Change casting syntax - [ ] Allow more operators on different types such as strings - [ ] Improve warning messages - [ ] Add indentation related errors - [ ] Add docs for as and is - [ ] Remove clang as a dependency -- [x] Change from anonymous structs to identified (to allow proper struct types) - [ ] Move error messages from source files to typechecker +- [ ] Fix array types not working and empty lists ## Features -- [ ] Implement Null (maybe) +- [ ] Implement Null (maybe someday) - [ ] Implement Tuples - [ ] Implement Dictionary -- [ ] Implement Empty lists - [ ] Implement 'in' as a boolean result - [ ] Implement anonymous functions -- [x] Implement alias - [ ] Implement Closure - [ ] Implement string interpolation - [ ] Implement Enums -- [x] Implement unsigned numbers -- [x] Implement operator overloading \ No newline at end of file +- [ ] Implement defer keyword \ No newline at end of file diff --git a/docs/examples.md b/docs/examples.md index 238c58e..68e09e2 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -167,7 +167,9 @@ print(abs(-5)) # Named parameters and defaults def optional_params(x: int, y: int32 = 5, z: double = 9) -> int # Lesma takes care of casting the return type between "compatible" types - return x + z + return x + z + +optional_params(5, z=11) # Enums enum Colors diff --git a/docs/features/keywords.md b/docs/features/keywords.md index ce7b554..8ad71ec 100644 --- a/docs/features/keywords.md +++ b/docs/features/keywords.md @@ -8,7 +8,7 @@ For reference, here are all the keywords categorized by type. | if | else | for | while | | switch | case | default | def | | const | break | continue | pass -| void | alias | +| void | alias | extern | operator | ## Operator keywords diff --git a/docs/features/operators.md b/docs/features/operators.md index 4f4e251..d60d72f 100644 --- a/docs/features/operators.md +++ b/docs/features/operators.md @@ -24,9 +24,9 @@ Lesma offers the standard mathematical and boolean operators |And| `and`| And| |Or| `or`| Or| |Xor| `xor`| Exclusive Or| -|Is| `is` | Is type operator| +|Is| `is` | Type checking operator| |As| `as` | Casting operator| -|Not| `not` | Not bool operator| +|Not| `not` | Not boolean operator| |Equals| `==` | Value equality| |Not Equals| `!=` | Value inequality| |Less Than| `<` | Less than value| diff --git a/docs/features/syntax.md b/docs/features/syntax.md index 5a6f183..811f142 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 an indentation +- Whitespace is signifiant, indentation uses either tabs or exactly 4 spaces +- Flow control statements, structs, 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) diff --git a/docs/features/types.md b/docs/features/types.md index 73f4132..60dcb3e 100644 --- a/docs/features/types.md +++ b/docs/features/types.md @@ -2,24 +2,16 @@ 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. + !!! warning Types that are not specified are inferred, this is fundamentally different to dynamic types! -The type must be either a user-defined alias, struct or class, or a built-in type such as: - -- `Any` -- `Int` -- `Double` -- `Float` -- `Str` -- `Bool` -- `List` -- `Tuple` -- `Dict` -- `Range` +--- +## Built-in Types -## Any +### Any Any types can receive any kind of value, so it can receive any kind of value at any moment. @@ -31,7 +23,7 @@ x = "Hey there" !!! warning Any not implemented yet! -## Int +### 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. - Signed: `int`, `int8`, `int16`, `int32`, `int64`, `int128` - Unsigned: `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `uint128` @@ -45,21 +37,21 @@ large_num: int128 = 5 !!! info In Lesma, int and uint are by default 64 bits wide, and they will enlarge by themselves to 128 bits wide if needed. This is not the default behaviour if you specify the size yourself! -## Float +### Float Float is floating-point real number. ```py x: float = 0.5 ``` -## Double +### Double Double is double-precision floating-point real number. ```py x: double = 172312.41923 ``` -## Str +### Str Str is Lesma's implementation of strings/char lists. All Lesma strings support UTF-8. ```py @@ -68,13 +60,13 @@ x = '🍌' x = '夜のコンサートは最高でした。' ``` -## Bool +### Bool Bools occupy only 1 bit, and can be either `true` or `false`. ```py x: bool = true ``` -## List +### 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. ```py @@ -85,7 +77,7 @@ print(x[2]) !!! warning Lists currently only support integers, no other types! -## Tuple +### Tuple Tuples are like lists, but immutable, and declared using round paranthesis. ```py @@ -96,7 +88,7 @@ print(x[0]) !!! warning Tuples are not yet implemented! -## Dict +### Dict Dictionaries are lists of key-value pairs (similar to hashmaps), and they're mutable ```py @@ -108,7 +100,7 @@ print(x['first_name']) !!! warning Dicts are not yet implemented! -## Range +### Range Ranges are similar to Python's ranges, defined using `start..end` kind of syntax, and they're especially used for loops ```py @@ -117,4 +109,24 @@ for x in 0..100 ``` !!! warning - Ranges can not currently be assigned to variables \ No newline at end of file + Ranges can not currently be assigned to variables + +---- + +## Type Operations + +### Is +`Is` binary operator checks if the left operand's type matches the right operand and returns a bool as a result + +```py +x: int = 5 +print(x is int) +``` + +### As +`As` binary operator casts the left operand to the right operand type and returns the casted value + +```py +x: float = 5.5 +print(x as int) # Should print 5 +``` diff --git a/docs/index.md b/docs/index.md index f73d22d..b95d841 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,7 +10,7 @@ Currently an early Work in Progress, and many thanks to [Ayehavgunne](https://gi ## 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 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 +- **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 From e7f0694ec5826629417a6fd181ae5e83e01bd6c8 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 30 Dec 2018 10:50:57 +0100 Subject: [PATCH 04/24] Newlines are now allowed in indented blocks --- src/lesma/parser.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 7878445..c152c32 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -37,6 +37,12 @@ def eat_value(self, *token_value): def preview(self, num=1): return self.lexer.preview_token(num) + + def keep_indent(self): + while self.current_token.type == NEWLINE: + self.eat_type(NEWLINE) + return self.current_token.indent_level == self.indent_level + def program(self): root = Compound() @@ -282,7 +288,7 @@ def statement_list(self): if isinstance(node, Return): return [node] results = [node] - while self.current_token.indent_level == self.indent_level: + while self.keep_indent(): results.append(self.statement()) if self.current_token.type == NEWLINE: self.next_token() @@ -557,7 +563,7 @@ def switch_statement(self): switch = Switch(value, [], self.line_num) if self.current_token.type == NEWLINE: self.next_token() - while self.current_token.indent_level == self.indent_level: + while self.keep_indent(): switch.cases.append(self.case_statement()) if self.current_token.type == NEWLINE: self.next_token() From a857e0aebad06b923d3a014a2e12331abd6d393e Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 30 Dec 2018 10:55:16 +0100 Subject: [PATCH 05/24] Added initial class implementation --- src/lesma/compiler/code_generator.py | 23 +++++++++++++++---- src/lesma/parser.py | 34 +++++++++++++++++++--------- src/lesma/type_checker.py | 16 +++++-------- src/lesma/visitor.py | 7 ++++++ 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index c27b4a8..ef33518 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -35,6 +35,7 @@ def __init__(self, file_name): self.loop_test_blocks = [] self.loop_end_blocks = [] self.is_break = False + self.in_class = False llvm.initialize() llvm.initialize_native_target() llvm.initialize_native_asmprinter() @@ -170,16 +171,30 @@ def visit_compound(self, node): def visit_structdeclaration(self, node): fields = [] for field in node.fields.values(): - if field.value == STR: - raise NotImplementedError - else: - fields.append(type_map[field.value]) + fields.append(type_map[field.value]) struct = self.module.context.get_identified_type(node.name) struct.fields = [field for field in node.fields.keys()] struct.name = 'struct.' + node.name struct.set_body([field for field in fields]) self.define(node.name, struct) + + + def visit_classdeclaration(self, node): + self.in_class = True + + fields = [] + for field in node.class_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.set_body([field for field in fields]) + + # 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 diff --git a/src/lesma/parser.py b/src/lesma/parser.py index c152c32..bbd683f 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -71,22 +71,33 @@ def class_declaration(self): base = None constructor = None methods = None - class_fields = None + class_fields = OrderedDict() instance_fields = None self.in_class = True self.next_token() class_name = self.current_token + self.user_types.append(class_name.value) self.eat_type(NAME) if self.current_token.value == LPAREN: pass # TODO impliment multiple inheritance self.eat_type(NEWLINE) self.indent_level += 1 - while self.current_token.indent_level == self.indent_level: + while self.keep_indent(): + if self.current_token.type == NEWLINE: + self.eat_type(NEWLINE) + continue + if self.current_token.type == NAME and self.preview().value == COLON: + field = self.current_token.value + self.eat_type(NAME) + self.eat_value(COLON) + field_type = self.type_spec() + class_fields[field] = field_type + self.eat_type(NEWLINE) if self.current_token.value == NEW: constructor = self.constructor_declaration(class_name) self.indent_level -= 1 self.in_class = False - return ClassDeclaration(class_name.value, base=base, constructor=constructor, methods=methods, class_fields=class_fields, instance_fields=instance_fields) + return ClassDeclaration(class_name.value, base, constructor, methods, class_fields, instance_fields) def variable_declaration(self): var_node = Var(self.current_token.value, self.line_num) @@ -192,14 +203,15 @@ def constructor_declaration(self, class_name): param_defaults = {} vararg = None while self.current_token.value != RPAREN: - if self.current_token.type == NAME: - param_type = self.variable(self.current_token) - self.eat_type(NAME) - else: - param_type = self.type_spec() - params[self.current_token.value] = param_type param_name = self.current_token.value self.eat_type(NAME) + if self.current_token.value == COLON: + self.eat_value(COLON) + param_type = self.type_spec() + else: + param_type = self.variable(self.current_token) + + params[self.current_token.value] = param_type if self.current_token.value != RPAREN: if self.current_token.value == ASSIGN: self.eat_value(ASSIGN) @@ -334,8 +346,8 @@ def statement(self): elif self.current_token.type == TYPE: 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 == CLASS: + node = self.class_declaration() elif self.current_token.value == EOF: return else: diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index c010c8c..365fb5f 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -1,14 +1,6 @@ -from lesma.ast import Collection -from lesma.ast import Var -from lesma.ast import VarDecl -from lesma.ast import DotAccess -from lesma.ast import CollectionAccess +from lesma.ast import Collection, Var, VarDecl, DotAccess, CollectionAccess from lesma.grammar import * -from lesma.visitor import AliasSymbol -from lesma.visitor import CollectionSymbol -from lesma.visitor import FuncSymbol -from lesma.visitor import NodeVisitor, StructSymbol -from lesma.visitor import VarSymbol +from lesma.visitor import AliasSymbol, CollectionSymbol, FuncSymbol, NodeVisitor, StructSymbol, ClassSymbol, VarSymbol from lesma.utils import warning, error @@ -446,6 +438,10 @@ def visit_structdeclaration(self, node): sym = StructSymbol(node.name, node.fields) self.define(sym.name, sym) + def visit_classdeclaration(self, node): + sym = ClassSymbol(node.name, node.class_fields) + self.define(sym.name, sym) + def visit_return(self, node): res = self.visit(node.value) self.return_flag = True diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index e6639ba..7a21b9c 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -72,6 +72,13 @@ def __init__(self, name, fields): self.accessed = False self.val_assigned = False +class ClassSymbol(Symbol): + def __init__(self, name, class_fields): + super().__init__(name) + self.class_fields = class_fields + self.accessed = False + self.val_assigned = False + class CollectionSymbol(Symbol): def __init__(self, name, var_type, item_types): From d85eb3b030bff182d18146b62ea36abe6e0a738c Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 30 Dec 2018 11:48:01 +0100 Subject: [PATCH 06/24] Modified syntax for struct/class initialization --- docs/examples.md | 2 +- src/lesma/ast.py | 6 ------ src/lesma/compiler/code_generator.py | 24 ++++++++++++------------ src/lesma/parser.py | 13 ++----------- src/lesma/type_checker.py | 20 ++++++++++++++++---- src/lesma/visitor.py | 4 ++++ 6 files changed, 35 insertions(+), 34 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 68e09e2..4390edb 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -184,7 +184,7 @@ struct Circle x: int y: int -cir: Circle = {radius=5, x=2, y=4} +cir: Circle = Circle(radius=5, x=2, y=4) print(cir.radius) diff --git a/src/lesma/ast.py b/src/lesma/ast.py index 66ac647..0c87583 100644 --- a/src/lesma/ast.py +++ b/src/lesma/ast.py @@ -117,12 +117,6 @@ def __init__(self, name, fields, line_num): self.line_num = line_num -class StructLiteral(AST): - def __init__(self, fields, line_num): - self.fields = fields - self.line_num = line_num - - class ClassDeclaration(AST): def __init__(self, name, base=None, constructor=None, methods=None, class_fields=None, instance_fields=None): self.name = name diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index ef33518..7d4c15a 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -6,7 +6,7 @@ import subprocess from llvmlite import ir from lesma.grammar import * -from lesma.ast import CollectionAccess, DotAccess, Input, StructLiteral, VarDecl, Str +from lesma.ast import CollectionAccess, DotAccess, Input, VarDecl, Str from lesma.compiler import RET_VAR, type_map from lesma.compiler.operations import unary_op, binary_op, cast_ops from lesma.compiler.builtins import define_builtins @@ -115,9 +115,11 @@ def visit_funccall(self, node): func_type = func_type.type.pointee name = self.search_scopes(node.name) name = name.name + elif isinstance(func_type, ir.IdentifiedStructType): + return self.struct_assign(node) else: name = node.name - + if len(node.arguments) < len(func_type.args): args = [] args_supplied = [] @@ -404,8 +406,8 @@ def visit_range(self, node): return array_ptr def visit_assign(self, node): # TODO: Simplify this, it just keeps getting worse - if isinstance(node.right, StructLiteral): - self.struct_assign(node) + if isinstance(self.search_scopes(node.right.name), ir.IdentifiedStructType): + self.define(node.left.value.value, 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: @@ -447,18 +449,17 @@ def visit_fieldassignment(self, node): return self.builder.extract_value(self.load(node.obj), obj_type.fields.index(node.field)) def struct_assign(self, node): - struct_type = self.search_scopes(node.left.type.value) - name = node.left.value.value - struct = self.builder.alloca(struct_type, name=name) + struct_type = self.search_scopes(node.name) + struct = self.builder.alloca(struct_type) fields = [] - for field in node.right.fields.values(): + 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.left.type.value - self.define(name, struct) + + struct.struct_name = node.name + return struct def visit_dotaccess(self, node): obj = self.search_scopes(node.obj) @@ -618,7 +619,6 @@ def visit_print(self, node): self.call('putchar', [ir.Constant(type_map[INT32], 10)]) return if isinstance(val.type, ir.IntType): - # noinspection PyUnresolvedReferences if val.type.width == 1: array = self.create_array(BOOL) self.call('bool_to_str', [array, val]) diff --git a/src/lesma/parser.py b/src/lesma/parser.py index bbd683f..0e7baf8 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -407,27 +407,18 @@ def slice_expression(self, token): pass def curly_bracket_expression(self, token): - hash_or_struct = None if token.value == LCURLYBRACKET: pairs = OrderedDict() while self.current_token.value != RCURLYBRACKET: key = self.expr() - if self.current_token.value == COLON: - hash_or_struct = 'hash' - self.eat_value(COLON) - else: - hash_or_struct = 'struct' - self.eat_value(ASSIGN) + self.eat_value(ASSIGN) pairs[key.value] = self.expr() if self.current_token.value == COMMA: self.next_token() else: break self.eat_value(RCURLYBRACKET) - if hash_or_struct == 'hash': - return HashMap(pairs, self.line_num) - elif hash_or_struct == 'struct': - return StructLiteral(pairs, self.line_num) + return HashMap(pairs, self.line_num) else: raise SyntaxError('Wait... what?') diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 365fb5f..16bdb75 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -126,6 +126,10 @@ 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 isinstance(self.search_scopes(node.right.name), (StructSymbol, ClassSymbol)): + var_name = node.left.value + value = self.infer_type(self.search_scopes(node.right.name)) + value.accessed = True elif isinstance(node.right, Collection): var_name = node.left.value value, collection_type = self.visit(node.right) @@ -161,7 +165,6 @@ def visit_assign(self, node): # TODO clean up this mess of a function if isinstance(var, FuncSymbol): self.define(var_name, var) else: - # noinspection PyUnresolvedReferences val_info = self.search_scopes(node.right.value) func_sym = FuncSymbol(var_name, val_info.type.return_type, val_info.parameters, val_info.body, val_info.parameter_defaults) self.define(var_name, func_sym) @@ -391,15 +394,24 @@ def visit_anonymousfunc(self, node): def visit_funccall(self, node): func_name = node.name func = self.search_scopes(func_name) - for x, param in enumerate(func.parameters.values()): + parameters = None + parameter_defaults = None + if isinstance(func, (StructSymbol, ClassSymbol)): + parameters = func.fields + parameter_defaults = func.fields + else: + parameters = func.parameters + parameter_defaults = func.parameter_defaults + + for x, param in enumerate(parameters.values()): if x < len(node.arguments): var = self.visit(node.arguments[x]) param_ss = self.search_scopes(param.value) if not types_compatible(var, param_ss) and (param_ss != self.search_scopes(ANY) and param.value != var.name and param.value != var.type.name): raise TypeError # TODO: Make this an actual error else: - func_param_keys = list(func.parameters.keys()) - if func_param_keys[x] not in node.named_arguments.keys() and func_param_keys[x] not in func.parameter_defaults.keys(): + func_param_keys = list(parameters.keys()) + if func_param_keys[x] not in node.named_arguments.keys() and func_param_keys[x] not in parameter_defaults.keys(): error('file={} line={}: Missing arguments to function: {}'.format(self.file_name, node.line_num, repr(func_name))) else: if func_param_keys[x] in node.named_arguments.keys(): diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 7a21b9c..3eab234 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -233,6 +233,10 @@ def infer_type(self, value): return self.search_scopes(COMPLEX) if isinstance(value, str): return self.search_scopes(STR) + if isinstance(value, StructSymbol): + return self.search_scopes(STRUCT) + if isinstance(value, ClassSymbol): + return self.search_scopes(CLASS) if isinstance(value, bool): return self.search_scopes(BOOL) if isinstance(value, list): From fd5ccec8aa5f04d2b163507e2a01512872f5dad5 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 31 Dec 2018 09:51:08 +0100 Subject: [PATCH 07/24] Fixed Windows error when opening files with utf-8 characters --- src/les.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/les.py b/src/les.py index 6759803..ce3d402 100644 --- a/src/les.py +++ b/src/les.py @@ -32,7 +32,7 @@ def _run(arg_list): error(les_file + " is not a valid file") return - code = open(les_file).read() + code = open(les_file, encoding="utf8").read() lexer = Lexer(code, les_file) parser = Parser(lexer) t = parser.parse() @@ -55,7 +55,7 @@ def _compile(arg_list): return les_file = os.path.abspath(les_file) - code = open(les_file).read() + code = open(les_file, encoding="utf8").read() lexer = Lexer(code, les_file) parser = Parser(lexer) t = parser.parse() From 8eee1f74b36bde38170fdb0baab7bfb0b696116c Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 31 Dec 2018 11:14:38 +0100 Subject: [PATCH 08/24] Fixed errors provoked by the last commits --- docs/TODO.md | 1 + src/lesma/compiler/code_generator.py | 2 +- src/lesma/type_checker.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 51e7a6d..5b5936d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -17,6 +17,7 @@ - [ ] 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 ## Features - [ ] Implement Null (maybe someday) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 7d4c15a..cddbd13 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -406,7 +406,7 @@ def visit_range(self, node): return array_ptr def visit_assign(self, node): # TODO: Simplify this, it just keeps getting worse - if isinstance(self.search_scopes(node.right.name), ir.IdentifiedStructType): + 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)) 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)) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 16bdb75..4af405e 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -126,7 +126,7 @@ 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 isinstance(self.search_scopes(node.right.name), (StructSymbol, ClassSymbol)): + elif hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), (StructSymbol, ClassSymbol)): var_name = node.left.value value = self.infer_type(self.search_scopes(node.right.name)) value.accessed = True From 2454ba0e3e107410f3aea5d8d8af91344a3e2efa Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 31 Dec 2018 11:31:05 +0100 Subject: [PATCH 09/24] Tests now also compare the output properly --- tests/test_operators.py | 2 ++ tests/test_vardecl.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/test_operators.py b/tests/test_operators.py index cf83b66..c1d6b0e 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -10,7 +10,9 @@ def test_arithmetic(self): error = err.decode('utf-8').strip() rc = proc.returncode with open('tests/io/operators.output') as expected: + exp_str = "".join(expected.readlines()) self.assertTrue('Error:' not in error) + self.assertTrue(output == exp_str) self.assertEqual(rc, 0) expected.close() diff --git a/tests/test_vardecl.py b/tests/test_vardecl.py index 8c5be9b..7f6671d 100644 --- a/tests/test_vardecl.py +++ b/tests/test_vardecl.py @@ -10,7 +10,9 @@ def test_vardecl(self): error = err.decode('utf-8').strip() rc = proc.returncode with open('tests/io/vardecl.output') as expected: + exp_str = "".join(expected.readlines()) self.assertTrue('Error:' not in error) + self.assertTrue(output == exp_str) self.assertEqual(rc, 0) expected.close() From e79a359d87cd4ce1f5cedf0d90c5c2ebab380f1b Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 31 Dec 2018 12:37:49 +0100 Subject: [PATCH 10/24] Added more tests, reduced amount of boilerplate test files (automatic) --- .circleci/config.yml | 6 +++--- README.md | 5 +++-- tests/io/list.les | 5 +++++ tests/io/list.output | 3 +++ tests/io/opassign.les | 33 +++++++++++++++++++++++++++++++++ tests/io/opassign.output | 16 ++++++++++++++++ tests/io/operators.les | 2 ++ tests/io/operators.output | 2 ++ tests/io/range.les | 2 ++ tests/io/range.output | 11 +++++++++++ tests/io/unicode.les | 3 +++ tests/io/unicode.output | 2 ++ tests/io/vardecl.les | 1 + tests/test_all.py | 31 +++++++++++++++++++++++++++++++ tests/test_operators.py | 21 --------------------- tests/test_vardecl.py | 21 --------------------- 16 files changed, 117 insertions(+), 47 deletions(-) create mode 100644 tests/io/list.les create mode 100644 tests/io/list.output create mode 100644 tests/io/opassign.les create mode 100644 tests/io/opassign.output create mode 100644 tests/io/range.les create mode 100644 tests/io/range.output create mode 100644 tests/io/unicode.les create mode 100644 tests/io/unicode.output create mode 100644 tests/test_all.py delete mode 100644 tests/test_operators.py delete mode 100644 tests/test_vardecl.py diff --git a/.circleci/config.yml b/.circleci/config.yml index c745b59..cf2db6e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,11 +2,11 @@ version: 2 jobs: build: docker: - - image: circleci/python:3.7.1 + - image: circleci/python:3.7.2 steps: - checkout - run: | pip install --user -r requirements.txt - sudo pip install nose + sudo pip install pytest - run: | - nosetests \ No newline at end of file + pytest -vv \ No newline at end of file diff --git a/README.md b/README.md index 6789530..f274316 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,10 @@ python les.py run test.les python les.py compile test.les ``` -Or run the unit tests using nosetests +Or install pytest and run the unit tests yourself ```bash -nosetests +pip install pytest +pytest ``` For advanced usage or help, consult the CLI help menu diff --git a/tests/io/list.les b/tests/io/list.les new file mode 100644 index 0000000..8ce4c6f --- /dev/null +++ b/tests/io/list.les @@ -0,0 +1,5 @@ +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 diff --git a/tests/io/list.output b/tests/io/list.output new file mode 100644 index 0000000..5efd464 --- /dev/null +++ b/tests/io/list.output @@ -0,0 +1,3 @@ +1 +3 +1002 \ No newline at end of file diff --git a/tests/io/opassign.les b/tests/io/opassign.les new file mode 100644 index 0000000..a31d0e5 --- /dev/null +++ b/tests/io/opassign.les @@ -0,0 +1,33 @@ +number: int = 0 +print(number) +number += 5 +print(number) +number -= 1 +print(number) +number /= 2 +print(number) +number *= 10 +print(number) +number //= 7 +print(number) +number ^= 3 +print(number) +number %= 3 +print(number) + +number2: double = 0 +print(number2) +number2 += 5 +print(number2) +number2 -= 1 +print(number2) +number2 /= 2 +print(number2) +number2 *= 10 +print(number2) +number2 //= 7 +print(number2) +number2 ^= 3 +print(number2) +number2 %= 3 +print(number2) \ No newline at end of file diff --git a/tests/io/opassign.output b/tests/io/opassign.output new file mode 100644 index 0000000..a297552 --- /dev/null +++ b/tests/io/opassign.output @@ -0,0 +1,16 @@ +0 +5 +4 +2 +20 +2 +8 +2 +0 +5 +4 +2 +20 +2 +8 +2 \ No newline at end of file diff --git a/tests/io/operators.les b/tests/io/operators.les index 4308f10..dc860e5 100644 --- a/tests/io/operators.les +++ b/tests/io/operators.les @@ -6,6 +6,8 @@ print(5//2) print(5%3) print(2^8) print(-11) +print(1 << 3) +print(8 >> 2) print(true and false) print(true or false) print(true xor true) diff --git a/tests/io/operators.output b/tests/io/operators.output index aa31c21..1aec396 100644 --- a/tests/io/operators.output +++ b/tests/io/operators.output @@ -6,6 +6,8 @@ 2 256 -11 +8 +2 false true false diff --git a/tests/io/range.les b/tests/io/range.les new file mode 100644 index 0000000..7bf65b3 --- /dev/null +++ b/tests/io/range.les @@ -0,0 +1,2 @@ +for i in 0..11 + print(i) \ No newline at end of file diff --git a/tests/io/range.output b/tests/io/range.output new file mode 100644 index 0000000..7bd5250 --- /dev/null +++ b/tests/io/range.output @@ -0,0 +1,11 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 \ No newline at end of file diff --git a/tests/io/unicode.les b/tests/io/unicode.les new file mode 100644 index 0000000..a948e42 --- /dev/null +++ b/tests/io/unicode.les @@ -0,0 +1,3 @@ +π: float = 3.14 +print('🍌') +print('夜のコンサートは最高でした。') diff --git a/tests/io/unicode.output b/tests/io/unicode.output new file mode 100644 index 0000000..3d60a79 --- /dev/null +++ b/tests/io/unicode.output @@ -0,0 +1,2 @@ +🍌 +夜のコンサートは最高でした。 \ No newline at end of file diff --git a/tests/io/vardecl.les b/tests/io/vardecl.les index 0a60651..6a0b37c 100644 --- a/tests/io/vardecl.les +++ b/tests/io/vardecl.les @@ -7,6 +7,7 @@ a6: int128 = 6 a7: double = 6.5 a8: float = 7.69 a10: bool = true +# a11: str = "Hey there" x: int = 5 y = 7.5 diff --git a/tests/test_all.py b/tests/test_all.py new file mode 100644 index 0000000..3a31508 --- /dev/null +++ b/tests/test_all.py @@ -0,0 +1,31 @@ +import os +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]) + + return tests + + +# Base test for all 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) + out, err = proc.communicate() + output = out.decode('utf-8').strip() + error = err.decode('utf-8').strip() + rc = proc.returncode + + assert 'Error:' not in error + assert rc == 0 + + if output: + with open(f'tests/io/{test_name}.output') as expected: + exp_str = "".join(expected.readlines()) + assert output == exp_str + expected.close() diff --git a/tests/test_operators.py b/tests/test_operators.py deleted file mode 100644 index c1d6b0e..0000000 --- a/tests/test_operators.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest -from subprocess import Popen, PIPE - - -class TestArithmetic(unittest.TestCase): - def test_arithmetic(self): - proc = Popen(["python3", "src/les.py", "run", 'tests/io/operators.les'], stdout=PIPE, stderr=PIPE) - out, err = proc.communicate() - output = out.decode('utf-8').strip() - error = err.decode('utf-8').strip() - rc = proc.returncode - with open('tests/io/operators.output') as expected: - exp_str = "".join(expected.readlines()) - self.assertTrue('Error:' not in error) - self.assertTrue(output == exp_str) - self.assertEqual(rc, 0) - expected.close() - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_vardecl.py b/tests/test_vardecl.py deleted file mode 100644 index 7f6671d..0000000 --- a/tests/test_vardecl.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest -from subprocess import Popen, PIPE - - -class TestVariableDeclaration(unittest.TestCase): - def test_vardecl(self): - proc = Popen(["python3", "src/les.py", "run", 'tests/io/vardecl.les'], stdout=PIPE, stderr=PIPE) - out, err = proc.communicate() - output = out.decode('utf-8').strip() - error = err.decode('utf-8').strip() - rc = proc.returncode - with open('tests/io/vardecl.output') as expected: - exp_str = "".join(expected.readlines()) - self.assertTrue('Error:' not in error) - self.assertTrue(output == exp_str) - self.assertEqual(rc, 0) - expected.close() - - -if __name__ == '__main__': - unittest.main() From 0fe6aee321b31c4e79a4926feb47ffb2b19050bc Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 31 Dec 2018 12:38:51 +0100 Subject: [PATCH 11/24] Fixed floating points floordiv assign operator --- src/lesma/compiler/code_generator.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index cddbd13..7739904 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -141,7 +141,7 @@ def visit_funccall(self, 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]]), @@ -159,7 +159,7 @@ def visit_funccall(self, node): def comp_cast(self, arg, typ, node): if types_compatible(str(arg.type), typ): return cast_ops(self, arg, typ, node) - + return arg def visit_compound(self, node): @@ -180,7 +180,6 @@ def visit_structdeclaration(self, node): struct.name = 'struct.' + node.name struct.set_body([field for field in fields]) self.define(node.name, struct) - def visit_classdeclaration(self, node): self.in_class = True @@ -193,14 +192,14 @@ def visit_classdeclaration(self, node): classdecl.fields = [field for field in node.class_fields.keys()] classdecl.name = 'class.' + node.name classdecl.set_body([field for field in fields]) - + # 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 @@ -216,7 +215,7 @@ def visit_incrementassign(self, node): pointee = self.search_scopes(var_name).type.pointee op = node.op temp = ir.Constant(var.type, 1) - + if isinstance(pointee, ir.IntType): if op == PLUS_PLUS: res = self.builder.add(var, temp) @@ -235,7 +234,6 @@ def visit_incrementassign(self, node): else: self.store(res, var_name) - def visit_aliasdeclaration(self, node): type_map[node.name] = type_map[node.collection.value] return ALIAS @@ -455,8 +453,8 @@ def struct_assign(self, node): 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) + 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 return struct @@ -506,9 +504,9 @@ def visit_opassign(self, node): res = self.builder.srem(var, right) elif op == POWER_ASSIGN: if not isinstance(node.right.value, int): - error('Cannot use non-integers for power coeficient') + error('Cannot use non-integers for power coeficient') # TODO: Send me to typechecker and check for binop as well - + right = cast_ops(self, right, var.type, node) temp = self.alloc_and_store(var, type_map[INT]) for _ in range(node.right.value - 1): @@ -530,6 +528,8 @@ def visit_opassign(self, node): elif op == FLOORDIV_ASSIGN: right = cast_ops(self, right, var.type, node) res = self.builder.fdiv(var, right) + temp = cast_ops(self, res, ir.IntType(64), node) + res = cast_ops(self, temp, res.type, node) elif op == DIV_ASSIGN: right = cast_ops(self, right, var.type, node) res = self.builder.fdiv(var, right) @@ -853,7 +853,7 @@ def evaluate(self, optimize=True, ir_dump=False, timer=False): for func in self.module.functions: if func.name == "main": print(func) - + llvmmod = llvm.parse_assembly(str(self.module)) if optimize: pmb = llvm.create_pass_manager_builder() From 2641c62556dcd4dd4388167fbef66c73738b2dbb Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 16:18:31 +0100 Subject: [PATCH 12/24] Added git hooks with pytest and master protection --- .circleci/config.yml | 2 +- .hooks/pre-commit.sh | 20 ++++++++++++++++++++ setup_env.sh | 5 +++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100755 .hooks/pre-commit.sh create mode 100755 setup_env.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index cf2db6e..7b21c9f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,4 +9,4 @@ jobs: pip install --user -r requirements.txt sudo pip install pytest - run: | - pytest -vv \ No newline at end of file + pytest -vv --cache-clear \ No newline at end of file diff --git a/.hooks/pre-commit.sh b/.hooks/pre-commit.sh new file mode 100755 index 0000000..fe7d04a --- /dev/null +++ b/.hooks/pre-commit.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Pytest +echo "Running pre-commit hook" +pytest -vv --cache-clear + +if [ $? -ne 0 ]; then + echo "Tests must pass before committing" + exit 1 +fi +#### + +# Prevent master commit +branch="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$branch" = "master" ]; then + echo "You can't commit directly to master branch" + exit 1 +fi +#### \ No newline at end of file diff --git a/setup_env.sh b/setup_env.sh new file mode 100755 index 0000000..be7b466 --- /dev/null +++ b/setup_env.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# Setup Git Hooks +cd .git/hooks/ +ln -s -f ../../.hooks/pre-commit.sh ./pre-commit From ffd81e75f0dd984c53b757be2ac6d19630b2f5dd Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 16:21:58 +0100 Subject: [PATCH 13/24] Updated .gitignore and requirements --- .gitignore | 1 + requirements.txt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index aa50240..41f1a37 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ out/ \.vscode/ +\.pytest_cache/ site/ build/ diff --git a/requirements.txt b/requirements.txt index 1c73099..8c4a8d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ halo==0.0.22 -llvmlite==0.26.0 -docopt==0.6.2 \ No newline at end of file +llvmlite==0.27.0 +docopt==0.6.2 +pytest==4.0.2 \ No newline at end of file From e3c964d31dfedad1d63c774cd2f8e26eae74c483 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 18:08:16 +0100 Subject: [PATCH 14/24] Fixes to debugging info --- src/lesma/compiler/code_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 7739904..8c96c70 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -846,7 +846,7 @@ def add_debug_info(self, optimize, filename): "isOptimized": optimize, }, is_distinct=True) # self.module.add_global(di_module) - self.module.add_named_metadata('debug_info', [di_file, di_module]) + self.module.add_named_metadata('llvm.dbg.cu', [di_file, di_module]) def evaluate(self, optimize=True, ir_dump=False, timer=False): if ir_dump and not optimize: From 4c870b02a764e5a1bbd4a01aaf926bd78ea589de Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 21:11:50 +0100 Subject: [PATCH 15/24] Properly disabled pytest's cache folder --- .circleci/config.yml | 2 +- .hooks/pre-commit.sh | 2 +- README.md | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b21c9f..4d54b96 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,4 +9,4 @@ jobs: pip install --user -r requirements.txt sudo pip install pytest - run: | - pytest -vv --cache-clear \ No newline at end of file + pytest -vv -p no:cacheprovider \ No newline at end of file diff --git a/.hooks/pre-commit.sh b/.hooks/pre-commit.sh index fe7d04a..8816b29 100755 --- a/.hooks/pre-commit.sh +++ b/.hooks/pre-commit.sh @@ -2,7 +2,7 @@ # Pytest echo "Running pre-commit hook" -pytest -vv --cache-clear +pytest -vv -p no:cacheprovider if [ $? -ne 0 ]; then echo "Tests must pass before committing" diff --git a/README.md b/README.md index f274316..4f7a49d 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ python les.py compile test.les Or install pytest and run the unit tests yourself ```bash -pip install pytest pytest ``` From c7a311ce3364d7ce7ebda6192223cc04a94d3c80 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 21:40:45 +0100 Subject: [PATCH 16/24] Fixed printf for floats, added many more tests --- docs/TODO.md | 1 + docs/examples.md | 8 ++++---- src/lesma/compiler/code_generator.py | 2 ++ src/lesma/compiler/operations.py | 1 + tests/io/ffi.les | 7 +++++++ tests/io/ffi.output | 2 ++ tests/io/function.les | 20 ++++++++++++++++++++ tests/io/function.output | 4 ++++ tests/io/opoverload.les | 8 ++++++++ tests/io/opoverload.output | 2 ++ tests/io/types.les | 4 ++++ tests/io/types.output | 3 +++ tests/io/vardecl.les | 4 ++++ 13 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 tests/io/ffi.les create mode 100644 tests/io/ffi.output create mode 100644 tests/io/function.les create mode 100644 tests/io/function.output create mode 100644 tests/io/opoverload.les create mode 100644 tests/io/opoverload.output create mode 100644 tests/io/types.les create mode 100644 tests/io/types.output diff --git a/docs/TODO.md b/docs/TODO.md index 5b5936d..78f2520 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -7,6 +7,7 @@ - [ ] Fix not being able to overload operators on user-defined structs and classes - [ ] Unicode doesn't work on Windows platforms - [ ] Fix string and list type declaration not working +- [ ] Fix base unary operators being applied before user defined ones ## Improvements - [ ] Allow any type for lists/tuples (currently only int) diff --git a/docs/examples.md b/docs/examples.md index 4390edb..c7e0d62 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -144,7 +144,7 @@ print(start_at_5(4)) print(start_at_27(15)) # User input -Int age = input('How old are you?') +age: int = input('How old are you?') # String Interpolation print('Wow! You are {age} years old?!') @@ -157,12 +157,12 @@ def operator - (x: int) -> int # One parameters overloads binary operations return 0 - x + 1 # Extern functions (FFI) -def extern abs(x: int32) -> int32 # from C's stdlib +def extern abs(x: int) -> int # from C's stdlib -print(abs(-5 as int32)) # ints are int64 by default in Lesma, they're int32 in C +print(abs(-5.0 as int)) # ints are int64 by default in Lesma, they're int32 in C # or you can just let Lesma convert between "compatible" types such as numbers -print(abs(-5)) +print(abs(-5.0)) # Named parameters and defaults def optional_params(x: int, y: int32 = 5, z: double = 9) -> int diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 8c96c70..c70bae3 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -639,6 +639,8 @@ def visit_print(self, node): self.print_num("%llu", val) return elif isinstance(val.type, (ir.FloatType, ir.DoubleType)): + if isinstance(val.type, ir.FloatType): + val = cast_ops(self, val, ir.DoubleType(), node) self.print_num("%g", val) return self.call('print', [val]) diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 5d05bd5..1da9291 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -18,6 +18,7 @@ def hasFunction(compiler, func_name): if func.name == func_name: return True + def userdef_unary_str(op, expr): return 'operator' + '.' + op + '.' + str(expr.type) diff --git a/tests/io/ffi.les b/tests/io/ffi.les new file mode 100644 index 0000000..e9d5dd0 --- /dev/null +++ b/tests/io/ffi.les @@ -0,0 +1,7 @@ +# Extern functions (FFI) +def extern abs(x: int) -> int # from C's stdlib +def extern pow(x: double, y: double) -> double + + +print(abs(-5)) +print(pow(2,4)) \ No newline at end of file diff --git a/tests/io/ffi.output b/tests/io/ffi.output new file mode 100644 index 0000000..8304884 --- /dev/null +++ b/tests/io/ffi.output @@ -0,0 +1,2 @@ +5 +16 \ No newline at end of file diff --git a/tests/io/function.les b/tests/io/function.les new file mode 100644 index 0000000..6ca2856 --- /dev/null +++ b/tests/io/function.les @@ -0,0 +1,20 @@ +def fib(n: int) -> int + a = 0 + b = 1 + for _ in 0..n + prev_a = a + a = b + b = prev_a + b + return a + +def fib_rec(n: int) -> int + if n == 0 + return 0 + if n == 1 + return 1 + return fib_rec(n - 1) + fib_rec(n - 2) + +def factorial(n: int = 5) -> int + if n <= 1 + return 1 + return n * factorial(n - 1) \ No newline at end of file diff --git a/tests/io/function.output b/tests/io/function.output new file mode 100644 index 0000000..69dc5ca --- /dev/null +++ b/tests/io/function.output @@ -0,0 +1,4 @@ +55 +55 +120 +16 \ No newline at end of file diff --git a/tests/io/opoverload.les b/tests/io/opoverload.les new file mode 100644 index 0000000..3953a4e --- /dev/null +++ b/tests/io/opoverload.les @@ -0,0 +1,8 @@ +def operator - (x: int, y:int) -> int + return x + 3 + +def operator + (x: int) -> int + return 5 + +print(3 - 1) +print( +2 ) \ No newline at end of file diff --git a/tests/io/opoverload.output b/tests/io/opoverload.output new file mode 100644 index 0000000..ec385ae --- /dev/null +++ b/tests/io/opoverload.output @@ -0,0 +1,2 @@ +6 +5 \ No newline at end of file diff --git a/tests/io/types.les b/tests/io/types.les new file mode 100644 index 0000000..1974fdb --- /dev/null +++ b/tests/io/types.les @@ -0,0 +1,4 @@ +x: int32 = 5 +print(x as float) +print(x is int) +print(x is int32) \ No newline at end of file diff --git a/tests/io/types.output b/tests/io/types.output new file mode 100644 index 0000000..1c0a98d --- /dev/null +++ b/tests/io/types.output @@ -0,0 +1,3 @@ +5 +false +true \ No newline at end of file diff --git a/tests/io/vardecl.les b/tests/io/vardecl.les index 6a0b37c..a3182bc 100644 --- a/tests/io/vardecl.les +++ b/tests/io/vardecl.les @@ -8,6 +8,10 @@ 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 From 1d497cf29d7fc6e6b99c6994dad37483184207cc Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 22:02:38 +0100 Subject: [PATCH 17/24] Fixed Dot Access for structs, added initial test for structs --- src/lesma/compiler/code_generator.py | 12 ++++++++---- src/lesma/type_checker.py | 3 +++ tests/io/struct.les | 10 ++++++++++ tests/io/struct.output | 2 ++ 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 tests/io/struct.les create mode 100644 tests/io/struct.output diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index c70bae3..364d8bc 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -422,10 +422,14 @@ def visit_assign(self, node): # TODO: Simplify this, it just keeps getting wors elif isinstance(node.left, DotAccess): obj = self.search_scopes(node.left.obj) obj_type = self.search_scopes(obj.struct_name) - new_obj = self.builder.insert_value(self.load(obj.name), self.visit(node.right), obj_type.fields.index(node.left.field)) - struct_ptr = self.alloc_and_store(new_obj, obj_type, name=obj.name) - struct_ptr.struct_name = obj.struct_name - self.define(obj.name, struct_ptr) + idx = -1 + for i, v in enumerate(obj_type.fields): + if v == node.left.field: + idx = i + break + + elem = self.builder.gep(obj, [self.const(0, width=INT32), self.const(idx, width=INT32)], inbounds=True) + 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]) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 4af405e..28597dc 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -484,6 +484,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: + 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]) def visit_hashmap(self, node): diff --git a/tests/io/struct.les b/tests/io/struct.les new file mode 100644 index 0000000..3b44d89 --- /dev/null +++ b/tests/io/struct.les @@ -0,0 +1,10 @@ +struct Circle + radius: int + x: int + y: int + +cir: Circle = Circle(radius=5, x=2, y=4) + +print(cir.radius + cir.x) +cir.x = 20 +print(cir.x) \ No newline at end of file diff --git a/tests/io/struct.output b/tests/io/struct.output new file mode 100644 index 0000000..1845bb0 --- /dev/null +++ b/tests/io/struct.output @@ -0,0 +1,2 @@ +7 +20 \ No newline at end of file From b1e9b7bb28a5a9f3128a9b75425aa0de77839a77 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 22:21:38 +0100 Subject: [PATCH 18/24] Fixed functions being defined multiple times --- src/lesma/type_checker.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 28597dc..1bd82b0 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -224,7 +224,6 @@ def visit_incrementassign(self, node): else: error('file={} line={}: Things that should not be happening ARE happening (fix this message)'.format(self.file_name, node.line_num)) - def visit_fieldassignment(self, node): obj = self.search_scopes(node.obj) return self.visit(obj.type.fields[node.field]) @@ -292,14 +291,18 @@ def visit_typedeclaration(self, node): 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) self.define(typ.name, typ) - + def visit_externfuncdecl(self, node): func_name = node.name func_type = self.search_scopes(node.return_type.value) + + if self.search_scopes(func_name) is not None: + error('file={} line={}: Cannot redefine a declared function: {}'.format(self.file_name, node.line_num, func_name)) + if func_type and func_type.name == FUNC: func_type.return_type = self.visit(node.return_type.func_ret_type) self.define(func_name, FuncSymbol(func_name, func_type, node.parameters, None)) @@ -329,10 +332,13 @@ def visit_externfuncdecl(self, node): self.define(func_name, func_symbol, 1) self.drop_top_scope() - def visit_funcdecl(self, node): func_name = node.name func_type = self.search_scopes(node.return_type.value) + + if self.search_scopes(func_name) is not None: + error('file={} line={}: Cannot redefine a declared function: {}'.format(self.file_name, node.line_num, func_name)) + if func_type and func_type.name == FUNC: func_type.return_type = self.visit(node.return_type.func_ret_type) self.define(func_name, FuncSymbol(func_name, func_type, node.parameters, node.body, node.parameter_defaults)) From d93a91e1b8f8ff0ea756e26e7e2327b13fe95757 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 22:22:23 +0100 Subject: [PATCH 19/24] External functions already used by the compiler not duplicate anymore --- src/lesma/compiler/code_generator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 364d8bc..503384b 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -79,6 +79,10 @@ def visit_externfuncdecl(self, node): self.externfuncdecl(node.name, node) def externfuncdecl(self, name, node): + for func in self.module.functions: + if func.name == name: + self.define(name, func, 1) + return return_type = node.return_type parameters = node.parameters varargs = node.varargs From e2ad1e2ed41e2ace2dcd78cfd92d1088e81a0db3 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 1 Jan 2019 22:34:21 +0100 Subject: [PATCH 20/24] Temporary support for char in the form of int8 --- src/lesma/compiler/code_generator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 503384b..94185d5 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -632,7 +632,9 @@ def visit_print(self, node): self.call('bool_to_str', [array, val]) val = array else: - if val.type.signed: + if int(str(val.type).split("i")[1]) == 8: + self.print_num("%c", val) + elif val.type.signed: if int(str(val.type).split("i")[1]) < 32: val = self.builder.sext(val, type_map[INT32]) self.print_num("%d", val) @@ -824,7 +826,7 @@ def _add_builtins(self): scanf_ty = ir.FunctionType(type_map[INT], [type_map[INT8].as_pointer(), type_map[INT].as_pointer()], var_arg=True) ir.Function(self.module, scanf_ty, 'scanf') - getchar_ty = ir.FunctionType(ir.IntType(4), []) + getchar_ty = ir.FunctionType(ir.IntType(8), []) ir.Function(self.module, getchar_ty, 'getchar') puts_ty = ir.FunctionType(type_map[INT], [type_map[INT].as_pointer()]) From 56978da50de74716a9a26b48903c33fc0594ff45 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 2 Jan 2019 10:28:41 +0100 Subject: [PATCH 21/24] Small fixes --- docs/TODO.md | 3 ++- src/lesma/type_checker.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 78f2520..6fadf4f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -18,7 +18,8 @@ - [ ] 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 +- [ ] Catch struct/class used parameters that are not initialized +- [ ] Add support for functions with same name but different parameters ## Features - [ ] Implement Null (maybe someday) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 1bd82b0..02a3c36 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -413,7 +413,8 @@ def visit_funccall(self, node): if x < len(node.arguments): var = self.visit(node.arguments[x]) param_ss = self.search_scopes(param.value) - if not types_compatible(var, param_ss) and (param_ss != self.search_scopes(ANY) and param.value != var.name and param.value != var.type.name): + if var.type is not None and not types_compatible(var, param_ss) and \ + (param_ss != self.search_scopes(ANY) and param.value != var.name and param.value != var.type.name): raise TypeError # TODO: Make this an actual error else: func_param_keys = list(parameters.keys()) From 2d89f69362ed28893013714fa988bc70a33d45a3 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 2 Jan 2019 10:37:10 +0100 Subject: [PATCH 22/24] Updated TODO, Pep8 fixes --- docs/TODO.md | 2 +- src/lesma/ast.py | 1 + src/lesma/compiler/llvmlite_custom.py | 41 +++++++++++++++------------ src/lesma/parser.py | 6 ++-- src/lesma/visitor.py | 1 + 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 6fadf4f..40aa8aa 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -14,7 +14,7 @@ - [ ] Allow more operators on different types such as strings - [ ] Improve warning messages - [ ] Add indentation related errors -- [ ] Add docs for as and is +- [x] Add docs for as and is - [ ] Remove clang as a dependency - [ ] Move error messages from source files to typechecker - [ ] Fix array types not working and empty lists diff --git a/src/lesma/ast.py b/src/lesma/ast.py index 0c87583..1f0b2b9 100644 --- a/src/lesma/ast.py +++ b/src/lesma/ast.py @@ -72,6 +72,7 @@ def __init__(self, name, return_type, parameters, line_num, varargs=None): self.varargs = varargs self.line_num = line_num + class AnonymousFunc(AST): def __init__(self, return_type, parameters, body, line_num, parameter_defaults=None, varargs=None): self.return_type = return_type diff --git a/src/lesma/compiler/llvmlite_custom.py b/src/lesma/compiler/llvmlite_custom.py index 7ad6eb6..aae7282 100644 --- a/src/lesma/compiler/llvmlite_custom.py +++ b/src/lesma/compiler/llvmlite_custom.py @@ -1,7 +1,8 @@ from llvmlite import ir # Vectors - + + def extract_element(self, vector, idx, name=''): """ Returns the value at position idx. @@ -9,7 +10,8 @@ def extract_element(self, vector, idx, name=''): instr = ir.instructions.ExtractElement(self.block, vector, idx, name=name) self._insert(instr) return instr - + + def insert_element(self, vector, value, idx, name=''): """ Returns vector with vector[idx] replaced by value. @@ -19,6 +21,7 @@ def insert_element(self, vector, value, idx, name=''): self._insert(instr) return instr + def shuffle_vector(self, vector1, vector2, mask, name=''): """ Concatenate vectors and extract elements into new vector. @@ -38,13 +41,13 @@ def __init__(self, parent, vector, index, name=''): raise TypeError("index needs to be of IntType.") typ = vector.type.elementtype super(ExtractElement, self).__init__(parent, typ, "extractelement", - [vector, index], name=name) + [vector, index], name=name) def descr(self, buf): operands = ", ".join("{0} {1}".format( - op.type, op.get_reference()) for op in self.operands) + op.type, op.get_reference()) for op in self.operands) buf.append("{opname} {operands}\n".format( - opname = self.opname, operands = operands)) + opname=self.opname, operands=operands)) class InsertElement(ir.instructions.Instruction): @@ -58,13 +61,14 @@ def __init__(self, parent, vector, value, index, name=''): raise TypeError("index needs to be of IntType.") typ = vector.type super(InsertElement, self).__init__(parent, typ, "insertelement", - [vector, value, index], name=name) + [vector, value, index], name=name) def descr(self, buf): operands = ", ".join("{0} {1}".format( - op.type, op.get_reference()) for op in self.operands) + op.type, op.get_reference()) for op in self.operands) buf.append("{opname} {operands}\n".format( - opname = self.opname, operands = operands)) + opname=self.opname, operands=operands)) + class ShuffleVector(ir.instructions.Instruction): def __init__(self, parent, vector1, vector2, mask, name=''): @@ -75,22 +79,22 @@ def __init__(self, parent, vector1, vector2, mask, name=''): raise TypeError("vector2 needs to be " + "Undefined or of the same type as vector1.") if (not isinstance(mask, ir.Constant) or - not isinstance(mask.type, ir.types.VectorType) or - mask.type.elementtype != ir.types.IntType(32)): + not isinstance(mask.type, ir.types.VectorType) or + mask.type.elementtype != ir.types.IntType(32)): raise TypeError("mask needs to be a constant i32 vector.") typ = ir.types.VectorType(vector1.type.elementtype, mask.type.count) index_range = range(vector1.type.count if vector2 == ir.Undefined else 2 * vector1.type.count) if not all(ii.constant in index_range for ii in mask.constant): raise IndexError("mask values need to be in {0}".format(index_range)) super(ShuffleVector, self).__init__(parent, typ, "shufflevector", - [vector1, vector2, mask], name=name) + [vector1, vector2, mask], name=name) def descr(self, buf): buf.append("shufflevector {0} {1}\n".format( - ", ".join("{0} {1}".format(op.type, op.get_reference()) - for op in self.operands), - self._stringify_metadata(leading_comma=True), - )) + ", ".join("{0} {1}".format(op.type, op.get_reference()) + for op in self.operands), + self._stringify_metadata(leading_comma=True), + )) class VectorType(ir.Type): @@ -140,7 +144,7 @@ def wrap_constant_value(self, values): return (ir.Constant(self.elementtype, values), ) * self.count if len(values) != len(self): raise ValueError("wrong constant size for %s: got %d elements" - % (self, len(values))) + % (self, len(values))) return [ir.Constant(ty, val) if not isinstance(val, ir.Value) else val for ty, val in zip(self.elements, values)] @@ -199,6 +203,7 @@ def __new(cls, bits, signed): Old_IdentifiedStructType = ir.IdentifiedStructType + class _IdentifiedStructType(Old_IdentifiedStructType): def gep(self, i): """ @@ -208,7 +213,7 @@ def gep(self, i): """ if not isinstance(i.type, ir.IntType): raise TypeError(i.type) - + return self.elements[i.constant] def set_body(self, *elems): @@ -224,4 +229,4 @@ def set_body(self, *elems): ir.types.IdentifiedStructType = _IdentifiedStructType -ir.IdentifiedStructType = _IdentifiedStructType \ No newline at end of file +ir.IdentifiedStructType = _IdentifiedStructType diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 0e7baf8..0969b6a 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -4,6 +4,7 @@ from lesma.compiler.__init__ import type_map from lesma.utils import error + class Parser(object): def __init__(self, lexer): self.lexer = lexer @@ -37,12 +38,11 @@ def eat_value(self, *token_value): def preview(self, num=1): return self.lexer.preview_token(num) - + def keep_indent(self): while self.current_token.type == NEWLINE: self.eat_type(NEWLINE) return self.current_token.indent_level == self.indent_level - def program(self): root = Compound() @@ -189,7 +189,7 @@ def function_declaration(self): if op_func: 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 for param in params: name.value += '.' + str(type_map[str(params[param].value)]) diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 3eab234..826163c 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -72,6 +72,7 @@ def __init__(self, name, fields): self.accessed = False self.val_assigned = False + class ClassSymbol(Symbol): def __init__(self, name, class_fields): super().__init__(name) From 659bb1d42d27a0d43565a6bb5170e7d94811db6c Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 2 Jan 2019 10:44:17 +0100 Subject: [PATCH 23/24] Code style now also verified before commits --- .circleci/config.yml | 4 +++- .hooks/pre-commit.sh | 11 ++++++++++- requirements.txt | 3 +-- src/lesma/compiler/llvmlite_custom.py | 7 +++---- src/lesma/visitor.py | 4 ++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d54b96..dbea0cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,5 +8,7 @@ jobs: - run: | pip install --user -r requirements.txt sudo pip install pytest + sudo pip install pycodestyle - run: | - pytest -vv -p no:cacheprovider \ No newline at end of file + pytest -vv -p no:cacheprovider + pycodestyle src/* --ignore=E501 \ No newline at end of file diff --git a/.hooks/pre-commit.sh b/.hooks/pre-commit.sh index 8816b29..6c1953a 100755 --- a/.hooks/pre-commit.sh +++ b/.hooks/pre-commit.sh @@ -1,7 +1,7 @@ #!/bin/sh # Pytest -echo "Running pre-commit hook" +echo "Running pre-commit hooks" pytest -vv -p no:cacheprovider if [ $? -ne 0 ]; then @@ -10,6 +10,15 @@ if [ $? -ne 0 ]; then fi #### +# Pycodestyle +pycodestyle src/* --ignore=E501 + +if [ $? -ne 0 ]; then + echo "Tests must pass before committing" + exit 1 +fi +#### + # Prevent master commit branch="$(git rev-parse --abbrev-ref HEAD)" diff --git a/requirements.txt b/requirements.txt index 8c4a8d7..cbb7eeb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ halo==0.0.22 llvmlite==0.27.0 -docopt==0.6.2 -pytest==4.0.2 \ No newline at end of file +docopt==0.6.2 \ No newline at end of file diff --git a/src/lesma/compiler/llvmlite_custom.py b/src/lesma/compiler/llvmlite_custom.py index aae7282..2b4e749 100644 --- a/src/lesma/compiler/llvmlite_custom.py +++ b/src/lesma/compiler/llvmlite_custom.py @@ -76,10 +76,9 @@ def __init__(self, parent, vector1, vector2, mask, name=''): raise TypeError("vector1 needs to be of VectorType.") if vector2 != ir.Undefined: if vector2.type != vector1.type: - raise TypeError("vector2 needs to be " + - "Undefined or of the same type as vector1.") - if (not isinstance(mask, ir.Constant) or - not isinstance(mask.type, ir.types.VectorType) or + raise TypeError("vector2 needs to be Undefined or of the same type as vector1.") + if (not isinstance(mask, ir.Constant) or not + isinstance(mask.type, ir.types.VectorType) or not mask.type.elementtype != ir.types.IntType(32)): raise TypeError("mask needs to be a constant i32 vector.") typ = ir.types.VectorType(vector1.type.elementtype, mask.type.count) diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 826163c..91cd090 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -212,8 +212,8 @@ def items(self): @property def unvisited_symbols(self): return [sym_name for sym_name, sym_val in self.items if - not isinstance(sym_val, (BuiltinTypeSymbol, BuiltinFuncSymbol)) and - not sym_val.accessed and sym_name != '_'] + not isinstance(sym_val, (BuiltinTypeSymbol, BuiltinFuncSymbol)) and not + sym_val.accessed and sym_name != '_'] def infer_type(self, value): if isinstance(value, BuiltinTypeSymbol): From e7f01989ca7d0f4afa05188cf233fab84bdec496 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 2 Jan 2019 10:46:35 +0100 Subject: [PATCH 24/24] Bumped version --- README.md | 2 +- src/les.py | 2 +- src/lesma/compiler/code_generator.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4f7a49d..128f2fc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ___ [![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.2.0-brightgreen.svg)](https://github.com/hassanalinali/Lesma/blob/master/LICENSE.md) +[![Version](https://img.shields.io/badge/version-0.2.1-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) diff --git a/src/les.py b/src/les.py index ce3d402..33b1a43 100644 --- a/src/les.py +++ b/src/les.py @@ -68,7 +68,7 @@ def _compile(arg_list): if __name__ == "__main__": - args = docopt(__doc__, version='0.2.0') + args = docopt(__doc__, version='0.2.1') if args['compile']: _compile(args) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 94185d5..f80c516 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -853,7 +853,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", + "producer": "Lesma v0.2.1", "runtimeVersion": 1, "isOptimized": optimize, }, is_distinct=True)