From c90964650d66509b4b4207f816742274b02a43d4 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 6 Jan 2019 22:50:46 +0100 Subject: [PATCH 01/78] Anonymous functions are now private to avoid collisions --- src/lesma/compiler/code_generator.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 5637490..a1bd30f 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -76,7 +76,7 @@ def visit_defer(self, node): def visit_anonymousfunc(self, node): self.anon_counter += 1 - self.funcdecl('anon_func.{}'.format(self.anon_counter), node) + self.funcdecl('anon_func.{}'.format(self.anon_counter), node, "private") return self.search_scopes('anon_func.{}'.format(self.anon_counter)) def visit_funcdecl(self, node): @@ -102,8 +102,8 @@ 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): + func = 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] self.alloc_define_store(arg, arg.name, arg.type) @@ -730,7 +730,7 @@ def get_args(self, parameters): return args - def start_function(self, name, return_type, parameters, parameter_defaults=None, varargs=None): + 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() @@ -744,6 +744,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') From 068a2cfeebebf16c002fc39aa6b38ea4deb4fba3 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 7 Jan 2019 17:09:00 +0100 Subject: [PATCH 02/78] Updated docs --- README.md | 8 ++++---- docs/TODO.md | 1 + docs/index.md | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 36b7419..16732a0 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,21 @@ ___ [![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. diff --git a/docs/TODO.md b/docs/TODO.md index b7ef1d7..2088257 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -32,5 +32,6 @@ - [ ] Implement Closure - [ ] Implement string interpolation - [ ] Implement Enums +- [ ] Implement lambda functions - [x] Implement defer keyword - [x] Implement fallthrough and change switch's behaviour \ No newline at end of file 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 From 565fcb8f90c32fb86a66c2c82db4e7a6688624b1 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 8 Jan 2019 09:36:16 +0100 Subject: [PATCH 03/78] Don't store blank value in vardecl --- src/lesma/compiler/code_generator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index a1bd30f..6d15c63 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -266,7 +266,6 @@ def visit_vardecl(self, node): 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) @staticmethod def visit_type(node): From e0b4af2bd9cbe88c368af4b77a64fb1695ed49dd Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 8 Jan 2019 09:46:35 +0100 Subject: [PATCH 04/78] Fixing var decl not looking in scopes for structs/classes --- src/lesma/compiler/code_generator.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 6d15c63..c0cdad3 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -212,7 +212,6 @@ def visit_classdeclaration(self, node): 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) @@ -258,9 +257,12 @@ def visit_aliasdeclaration(self, node): return ALIAS 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 From ab5d1cb8d9fb59d0e028fb2839a07733b039d364 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 8 Jan 2019 16:15:05 +0100 Subject: [PATCH 05/78] Added variable declaration test --- tests/io/{vardecl.les => varassign.les} | 0 tests/io/{vardecl.output => varassign.output} | 0 tests/io/vardecl.io | 10 ++++++++++ 3 files changed, 10 insertions(+) rename tests/io/{vardecl.les => varassign.les} (100%) rename tests/io/{vardecl.output => varassign.output} (100%) create mode 100644 tests/io/vardecl.io diff --git a/tests/io/vardecl.les b/tests/io/varassign.les similarity index 100% rename from tests/io/vardecl.les rename to tests/io/varassign.les 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.io b/tests/io/vardecl.io new file mode 100644 index 0000000..130930b --- /dev/null +++ b/tests/io/vardecl.io @@ -0,0 +1,10 @@ +a1: int +a2: int8 +a3: int16 +a4: int32 +a5: int64 +a6: int128 +a8: float +a7: double +a10: bool +a11: str \ No newline at end of file From 00f83d4abb25523626c9ab84a3f8813744c5aa3f Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 8 Jan 2019 19:26:04 +0100 Subject: [PATCH 06/78] Class fields and methods now function properly, expect bugs --- docs/examples.md | 6 +- src/lesma/ast.py | 5 +- src/lesma/compiler/code_generator.py | 105 +++++++++++++++++++++++---- src/lesma/parser.py | 37 +++++++--- src/lesma/type_checker.py | 50 +++++++------ src/lesma/visitor.py | 5 +- 6 files changed, 152 insertions(+), 56 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 861afe1..e0507e8 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -197,17 +197,17 @@ 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 + def print_year() -> void print('This car was made in {this.year}') ford = Car(1992) diff --git a/src/lesma/ast.py b/src/lesma/ast.py index 35c5174..3d505ce 100644 --- a/src/lesma/ast.py +++ b/src/lesma/ast.py @@ -119,12 +119,11 @@ def __init__(self, name, fields, line_num): 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 diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index c0cdad3..baddbad 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -106,7 +106,12 @@ def funcdecl(self, name, node, linkage=None): func = 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] - 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) @@ -120,6 +125,51 @@ 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 < 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 @@ -132,7 +182,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 @@ -196,7 +251,8 @@ 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.name = node.name + struct.type = STRUCT struct.set_body([field for field in fields]) self.define(node.name, struct) @@ -204,13 +260,18 @@ 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) # To make use of self in methods, we need to predefine our class + for method in node.methods: + self.funcdecl(method.name, method) + classdecl.methods = [self.search_scopes(method.name) for method in node.methods] self.in_class = False self.define(node.name, classdecl) @@ -434,7 +495,7 @@ def visit_range(self, node): self.call('create_range', [array_ptr, start, stop]) return array_ptr - def visit_assign(self, node): # TODO: Simplify this, it just keeps getting worse + def visit_assign(self, node): 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): @@ -455,7 +516,7 @@ def visit_assign(self, node): # TODO: Simplify this, it just keeps getting wors 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: @@ -481,9 +542,19 @@ 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) @@ -494,12 +565,12 @@ def struct_assign(self, node): 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 + 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) + 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): @@ -722,12 +793,20 @@ 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 self.search_scopes(param.value) is not None: + args.append(self.search_scopes(param.value).as_pointer()) + else: + error("Parameter type not recognized: {}".format(param.value)) return args diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 2ed1f85..d9d79b4 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: @@ -83,9 +83,8 @@ def struct_declaration(self): 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 +104,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) @@ -210,12 +209,14 @@ def function_declaration(self): 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 +226,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 +242,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() diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index d7c5ed9..9749809 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -436,35 +436,39 @@ 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_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): diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 91cd090..9afac4b 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -74,9 +74,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 From 780761cf99553fa1747aa860d39ccc76a198989d Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 8 Jan 2019 19:36:23 +0100 Subject: [PATCH 07/78] Fixed not being able to use method returns in parser --- src/lesma/parser.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lesma/parser.py b/src/lesma/parser.py index d9d79b4..281bafe 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -683,8 +683,11 @@ def factor(self): token = self.current_token preview = self.preview() if preview.value == DOT: - self.next_token() - return self.dot_access(token) + if self.preview(2).type == NAME and self.preview(3).value == LPAREN: + return self.property_or_method(self.next_token()) + else: + self.next_token() + return self.dot_access(token) elif token.value in (PLUS, MINUS, BINARY_ONES_COMPLIMENT): self.next_token() return UnaryOp(token.value, self.factor(), self.line_num) From dec8546e4ad77ec1a09a48d0c79a3594feee6e97 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 8 Jan 2019 19:37:50 +0100 Subject: [PATCH 08/78] Added test for classes --- tests/io/class.les | 20 ++++++++++++++++++++ tests/io/class.output | 6 ++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/io/class.les create mode 100644 tests/io/class.output diff --git a/tests/io/class.les b/tests/io/class.les new file mode 100644 index 0000000..9e81244 --- /dev/null +++ b/tests/io/class.les @@ -0,0 +1,20 @@ +class Thing + a_thing: int + def new() + self.a_thing = 1 + print(5) + + 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()) \ No newline at end of file diff --git a/tests/io/class.output b/tests/io/class.output new file mode 100644 index 0000000..0698420 --- /dev/null +++ b/tests/io/class.output @@ -0,0 +1,6 @@ +5 +1 +3 +3 +7 +5 \ No newline at end of file From ea74957dc428c6712612bf77c06962e2f7f5925d Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 8 Jan 2019 19:38:51 +0100 Subject: [PATCH 09/78] Reverting dist to pyinstaller until nuitka gets fixed --- build_dist.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_dist.sh b/build_dist.sh index fabc75b..84e236d 100755 --- a/build_dist.sh +++ b/build_dist.sh @@ -1 +1 @@ -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 +pyinstaller src/les.py -D -n lesma \ No newline at end of file From 4e304f59ed9faa263e03311c89bc2cbd87d9fe26 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 11 Jan 2019 14:25:29 +0100 Subject: [PATCH 10/78] Slight improvements on docs and tests --- docs/TODO.md | 6 ++++++ tests/io/anon_func.les | 8 +++++++- tests/io/function.les | 4 ++++ tests/io/range.les | 4 ++++ tests/io/range.output | 11 +++++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/TODO.md b/docs/TODO.md index 2088257..f525a85 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -15,6 +15,8 @@ - [ ] Improve warning messages - [ ] Add indentation related errors - [x] Add docs for as and is +- [ ] Add docs for classes and structs +- [ ] Add docs for return type casting and compatible casting - [ ] Remove clang as a dependency - [ ] Move error messages from source files to typechecker - [ ] Fix array types not working and empty lists @@ -22,10 +24,14 @@ - [ ] 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 +- [ ] Allow default values for structs and classes +- [ ] Use dataclasses and static typing as much as possible ## Features - [ ] Implement Null (maybe someday) - [x] Implement Tuples +- [x] Implement Classes +- [x] Implement class inheritance - [ ] Implement Dictionary - [ ] Implement 'in' as a boolean result - [x] Implement anonymous functions diff --git a/tests/io/anon_func.les b/tests/io/anon_func.les index a25960d..ee1af6a 100644 --- a/tests/io/anon_func.les +++ b/tests/io/anon_func.les @@ -4,5 +4,11 @@ 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 + print(x(2,1)) -print(x(2,4)) \ No newline at end of file +print(y(2,4)) \ 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/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 From 756824b8c93006cfad6c83785f6ee16319f349fb Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 11 Jan 2019 14:31:19 +0100 Subject: [PATCH 11/78] Changed type decl keyword from alias -> type --- docs/examples.md | 2 +- docs/features/keywords.md | 2 +- docs/features/types.md | 2 +- src/lesma/ast.py | 2 +- src/lesma/compiler/code_generator.py | 4 ++-- src/lesma/grammar.py | 6 +++--- src/lesma/lexer.py | 2 +- src/lesma/parser.py | 18 +++++++++--------- src/lesma/type_checker.py | 14 +++++++------- src/lesma/visitor.py | 2 +- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index e0507e8..6bccca2 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -113,7 +113,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 diff --git a/docs/features/keywords.md b/docs/features/keywords.md index 18ecb80..a543277 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 | extern | operator | +| void | type | extern | operator | | fallthrough | defer ## Operator keywords diff --git a/docs/features/types.md b/docs/features/types.md index 60dcb3e..ac11ea1 100644 --- a/docs/features/types.md +++ b/docs/features/types.md @@ -2,7 +2,7 @@ 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 or class, or a built-in type. !!! warning Types that are not specified are inferred, this is fundamentally different to dynamic types! diff --git a/src/lesma/ast.py b/src/lesma/ast.py index 3d505ce..2e24002 100644 --- a/src/lesma/ast.py +++ b/src/lesma/ast.py @@ -299,7 +299,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/code_generator.py b/src/lesma/compiler/code_generator.py index baddbad..dac0eeb 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -313,9 +313,9 @@ def visit_incrementassign(self, node): else: self.store(res, var_name) - def visit_aliasdeclaration(self, node): + def visit_typedeclaration(self, 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] if node.type.value in type_map else self.search_scopes(node.type.value) diff --git a/src/lesma/grammar.py b/src/lesma/grammar.py index 87effb3..08a6cbb 100644 --- a/src/lesma/grammar.py +++ b/src/lesma/grammar.py @@ -117,7 +117,7 @@ WITH = 'with' # TODO PASS = 'pass' VOID = 'void' -ALIAS = 'alias' +TYPE = 'type' OVERRIDE = 'override' # TODO ABSTRACT = 'abstract' # TODO ASSERT = 'assert' # TODO @@ -153,7 +153,7 @@ KEYWORDS = ( IF, ELSE, WHILE, FOR, SWITCH, CASE, DEF, SUPER, THIS, 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 ) @@ -169,7 +169,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..deb972e 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -242,7 +242,7 @@ 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) diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 281bafe..b358eb3 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -125,12 +125,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 @@ -304,7 +304,7 @@ 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() @@ -391,9 +391,9 @@ 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: @@ -416,7 +416,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) @@ -702,7 +702,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): diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 9749809..435032f 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, ClassSymbol, VarSymbol from lesma.utils import warning, error @@ -196,7 +196,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: @@ -295,11 +295,11 @@ def visit_typedeclaration(self, node): typs = typs[0] else: typs = tuple(typs) - typ = AliasSymbol(node.name.value, typs) + typ = TypeSymbol(node.name.value, typs) self.define(typ.name, typ) - def visit_aliasdeclaration(self, node): - typ = AliasSymbol(node.name, node.collection.value) + def visit_typedeclaration(self, node): + typ = TypeSymbol(node.name, node.collection.value) self.define(typ.name, typ) def visit_externfuncdecl(self, node): @@ -323,7 +323,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 +359,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) diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 9afac4b..b817d79 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -105,7 +105,7 @@ def __str__(self): __repr__ = __str__ -class AliasSymbol(Symbol): +class TypeSymbol(Symbol): def __init__(self, name, types): super().__init__(name, types) self.accessed = False From bb8ec35cf709ea8e694788aa023863c723ac2bc9 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 11 Jan 2019 14:34:39 +0100 Subject: [PATCH 12/78] Added tests for type definition --- tests/io/typedef.les | 3 +++ tests/io/typedef.output | 1 + 2 files changed, 4 insertions(+) create mode 100644 tests/io/typedef.les create mode 100644 tests/io/typedef.output 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 From d110e18b69e44d61918b528dffee2f2eea3c3f42 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 12 Jan 2019 20:54:25 +0100 Subject: [PATCH 13/78] Updated docs --- docs/TODO.md | 8 ++++---- docs/examples.md | 7 ++++--- docs/features/classes.md | 27 +++++++++++++++++++++++++++ docs/features/structs.md | 15 +++++++++++++++ mkdocs.yml | 4 +++- 5 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 docs/features/classes.md create mode 100644 docs/features/structs.md diff --git a/docs/TODO.md b/docs/TODO.md index f525a85..28d51c5 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -8,6 +8,7 @@ - [ ] 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 structs and classes types not being implicitly defined on assignment ## Improvements - [ ] Allow any type for lists/tuples (currently only int) @@ -15,7 +16,7 @@ - [ ] Improve warning messages - [ ] Add indentation related errors - [x] Add docs for as and is -- [ ] Add docs for classes and structs +- [x] Add docs for classes and structs - [ ] Add docs for return type casting and compatible casting - [ ] Remove clang as a dependency - [ ] Move error messages from source files to typechecker @@ -23,15 +24,14 @@ - [ ] 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 -- [ ] Allow default values for structs and classes +- [ ] Allow default values for struct and class fields - [ ] Use dataclasses and static typing as much as possible ## Features - [ ] Implement Null (maybe someday) - [x] Implement Tuples - [x] Implement Classes -- [x] Implement class inheritance +- [ ] Implement Class inheritance - [ ] Implement Dictionary - [ ] Implement 'in' as a boolean result - [x] Implement anonymous functions diff --git a/docs/examples.md b/docs/examples.md index 6bccca2..043fd84 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -188,9 +188,9 @@ enum Colors 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) @@ -208,9 +208,10 @@ class Car(Vehicle) super.Vehicle(year, color) def print_year() -> void - print('This car was made in {this.year}') + 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/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/mkdocs.yml b/mkdocs.yml index b9cd03e..f728615 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,9 +14,11 @@ nav: - Features: - Syntax: features/syntax.md - Keywords: features/keywords.md + - Flow Control: features/flow_control.md + - Structs: features/structs.md + - Classes: features/classes.md - Types: features/types.md - Operators: features/operators.md - - Flow Control: features/flow_control.md - TODO: TODO.md - License: license.md From 861b3718bcdf78e240b6686656e1ce6a655bae2b Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 13 Jan 2019 00:45:23 +0100 Subject: [PATCH 14/78] Build won't trigger for documentation branch --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) 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: From f38972a6bcce7f01a18bf535efd09a5d398400f4 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 14 Jan 2019 19:27:23 +0100 Subject: [PATCH 15/78] Refactoring code_gen --- src/lesma/compiler/code_generator.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index dac0eeb..b98fa57 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -42,10 +42,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) @@ -268,7 +267,7 @@ def visit_classdeclaration(self, node): classdecl.name = node.name classdecl.type = CLASS classdecl.set_body([field for field in fields]) - self.define(node.name, classdecl) # To make use of self in methods, we need to predefine our class + self.define(node.name, classdecl) for method in node.methods: self.funcdecl(method.name, method) classdecl.methods = [self.search_scopes(method.name) for method in node.methods] @@ -327,8 +326,7 @@ def visit_vardecl(self, node): 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.alloc_and_define(typ, name=node.value.value) @staticmethod def visit_type(node): @@ -884,9 +882,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=''): @@ -907,10 +903,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)) From 63c1848a819605d9967da1a9882d8cdeec6a9442 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 14 Jan 2019 22:55:05 +0100 Subject: [PATCH 16/78] Refactoring, updated tests, now they are faster and cross platform --- src/lesma/compiler/code_generator.py | 5 +---- tests/io/anon_func.les | 7 ++++++- tests/io/anon_func.output | 3 ++- tests/test_all.py | 12 ++++++++---- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index b98fa57..44fad3a 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -23,7 +23,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.VoidType(), []) # [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') @@ -275,9 +275,6 @@ def visit_classdeclaration(self, node): 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 diff --git a/tests/io/anon_func.les b/tests/io/anon_func.les index ee1af6a..3c2d9f8 100644 --- a/tests/io/anon_func.les +++ b/tests/io/anon_func.les @@ -10,5 +10,10 @@ y = def (x: int, y: int) -> int else return x * y +z: func[] = def () + print(5) + + print(x(2,1)) -print(y(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/test_all.py b/tests/test_all.py index 3a31508..2439f90 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -5,17 +5,21 @@ def get_tests(): tests = [] - for file in os.listdir("./tests/io"): + path = os.path.dirname(__file__) + for file in os.listdir(os.path.join(path, "io")): if file.endswith(".les"): tests.append(os.path.basename(file).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(["python", os.path.join(path, "src", "les.py"), + "run", os.path.join(path, "tests", "io", test_name + ".les")], + stdout=PIPE, stderr=PIPE, shell=True) out, err = proc.communicate() output = out.decode('utf-8').strip() error = err.decode('utf-8').strip() @@ -25,7 +29,7 @@ def test_base(test_name): 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")) as expected: exp_str = "".join(expected.readlines()) assert output == exp_str expected.close() From db582db41b3561b34a54293dd6ad9634c4401a2f Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 14 Jan 2019 23:21:09 +0100 Subject: [PATCH 17/78] New test for compilation to binary and llvm-ir --- tests/test_compile.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/test_compile.py diff --git a/tests/test_compile.py b/tests/test_compile.py new file mode 100644 index 0000000..efd0d45 --- /dev/null +++ b/tests/test_compile.py @@ -0,0 +1,32 @@ +import os +import pytest +import tempfile +from subprocess import Popen, PIPE + + +def get_tests(): + tests = [] + path = os.path.dirname(__file__) + for file in os.listdir(os.path.join(path, "io")): + if file.endswith(".les"): + tests.append(os.path.basename(file).split('.')[0]) + + return tests + + +# Base test for Lesma script files +@pytest.mark.parametrize("test_name", get_tests()) +def test_base(test_name): + path = os.path.join(os.path.dirname(__file__), os.pardir) + with tempfile.TemporaryDirectory() as dirpath: + proc = Popen(["python", os.path.join(path, "src", "les.py"), + "compile", os.path.join(path, "tests", "io", test_name + ".les"), + "-l", "-o", os.path.join(dirpath, "temp")], + stdout=PIPE, stderr=PIPE, shell=True) + 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 From 2f0b11b9a9ea69cded09b83afbd14980ece7363e Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 15 Jan 2019 11:33:35 +0100 Subject: [PATCH 18/78] Fixed structs not caring about named arguments, implemented defaults --- docs/TODO.md | 3 +-- src/lesma/ast.py | 3 ++- src/lesma/compiler/code_generator.py | 17 ++++++++++++++--- src/lesma/parser.py | 7 ++++++- tests/io/struct.les | 7 ++++--- tests/io/struct.output | 3 ++- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 28d51c5..74e5ca2 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,12 +1,11 @@ # TODO ## Fixes -- [ ] Fix Type declaration not expecting square brackets (for lists) +- [ ] Fix type declaration for lists and tuples - [ ] 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 structs and classes types not being implicitly defined on assignment diff --git a/src/lesma/ast.py b/src/lesma/ast.py index 2e24002..7713241 100644 --- a/src/lesma/ast.py +++ b/src/lesma/ast.py @@ -112,10 +112,11 @@ def __init__(self, value, line_num): class StructDeclaration(AST): - def __init__(self, name, fields, line_num): + 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): diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 44fad3a..b4fa899 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -250,6 +250,7 @@ def visit_structdeclaration(self, node): struct = self.module.context.get_identified_type(node.name) struct.fields = [field for field in node.fields.keys()] + struct.defaults = node.defaults struct.name = node.name struct.type = STRUCT struct.set_body([field for field in fields]) @@ -554,11 +555,21 @@ def struct_assign(self, node): struct_type = self.search_scopes(node.name) struct = self.builder.alloca(struct_type) + # Once for defaults fields = [] - for field in node.named_arguments.values(): + for index, field in struct_type.defaults.items(): 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) + pos = struct_type.fields.index(index) + elem = self.builder.gep(struct, [self.const(0, width=INT32), self.const(pos, width=INT32)], inbounds=True) + self.builder.store(fields[-1], elem) + + # Another one for calling values + fields.clear() + for index, field in enumerate(node.named_arguments.values()): + fields.append(self.visit(field)) + pos = struct_type.fields.index(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(fields[-1], elem) struct.name = node.name return struct diff --git a/src/lesma/parser.py b/src/lesma/parser.py index b358eb3..5dff2fc 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -72,14 +72,19 @@ 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 diff --git a/tests/io/struct.les b/tests/io/struct.les index 3b44d89..fcd8408 100644 --- a/tests/io/struct.les +++ b/tests/io/struct.les @@ -1,10 +1,11 @@ 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) print(cir.radius + cir.x) cir.x = 20 -print(cir.x) \ No newline at end of file +print(cir.x) +print(cir.y) \ No newline at end of file diff --git a/tests/io/struct.output b/tests/io/struct.output index 1845bb0..8ff7834 100644 --- a/tests/io/struct.output +++ b/tests/io/struct.output @@ -1,2 +1,3 @@ 7 -20 \ No newline at end of file +20 +99 \ No newline at end of file From fda4e1f29bff671296bad945e46552d3b9aa883e Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 15 Jan 2019 11:39:27 +0100 Subject: [PATCH 19/78] Fixed bug not allowing default value in class methods --- docs/TODO.md | 2 +- src/lesma/compiler/code_generator.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/TODO.md b/docs/TODO.md index 74e5ca2..f01fb4d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -23,7 +23,7 @@ - [ ] 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 default values for struct and class fields +- [x] Allow default values for struct and class fields - [ ] Use dataclasses and static typing as much as possible ## Features diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index b4fa899..6f81997 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -140,6 +140,8 @@ def methodcall(self, node, func, obj): 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: From 8b82a07f345f50aba2bf371562b91cf524cec4f6 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 15 Jan 2019 11:40:42 +0100 Subject: [PATCH 20/78] Updated class test to include default parameters in methods --- tests/io/class.les | 7 +++++-- tests/io/class.output | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/io/class.les b/tests/io/class.les index 9e81244..e4aaed1 100644 --- a/tests/io/class.les +++ b/tests/io/class.les @@ -1,7 +1,9 @@ class Thing a_thing: int - def new() + b_thing: float + def new(b_thing: float = 3.14) self.a_thing = 1 + self.b_thing = b_thing print(5) def another() @@ -17,4 +19,5 @@ print(x.a_thing) x.a_thing = 3 print(x.a_thing) x.another() -print(x.just_demo()) \ No newline at end of file +print(x.just_demo()) +print(x.b_thing) \ No newline at end of file diff --git a/tests/io/class.output b/tests/io/class.output index 0698420..2f4f4d6 100644 --- a/tests/io/class.output +++ b/tests/io/class.output @@ -3,4 +3,5 @@ 3 3 7 -5 \ No newline at end of file +5 +3.14 \ No newline at end of file From ccf7b954d89a8d2c8b7f10c5da389805354bdee1 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 15 Jan 2019 23:29:57 +0100 Subject: [PATCH 21/78] Function are now forward declared in classes --- src/lesma/compiler/code_generator.py | 52 ++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 6f81997..1f661ea 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -75,11 +75,11 @@ def visit_defer(self, node): def visit_anonymousfunc(self, node): self.anon_counter += 1 - self.funcdecl('anon_func.{}'.format(self.anon_counter), node, "private") + 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,6 +102,24 @@ def externfuncdecl(self, name, node): self.define(name, func, 1) def funcdecl(self, name, node, linkage=None): + func = self.func_decl(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs, linkage) + + def funcimpl(self, name, node): + func = self.implement_func_body(name) + 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) + self.end_function(ret) + + def funcdef(self, name, node, linkage=None): func = 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] @@ -273,6 +291,9 @@ def visit_classdeclaration(self, node): 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.in_class = False @@ -818,6 +839,33 @@ def get_args(self, parameters): return args + 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([]) + 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) From 39416b811ee954710804a9abf6a02f8d5010d340 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 15 Jan 2019 23:33:45 +0100 Subject: [PATCH 22/78] Updated class test to include forward declaration --- tests/io/class.les | 1 + tests/io/class.output | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/io/class.les b/tests/io/class.les index e4aaed1..a5d7a1d 100644 --- a/tests/io/class.les +++ b/tests/io/class.les @@ -5,6 +5,7 @@ class Thing self.a_thing = 1 self.b_thing = b_thing print(5) + print(just_demo()) def another() print(self.a_thing) diff --git a/tests/io/class.output b/tests/io/class.output index 2f4f4d6..56bb226 100644 --- a/tests/io/class.output +++ b/tests/io/class.output @@ -1,4 +1,5 @@ 5 +5 1 3 3 From 8f7a3c319ea9ebfe9294288bfe1d95f36d180cb4 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Thu, 17 Jan 2019 11:24:22 +0100 Subject: [PATCH 23/78] Return type no longer casted automatically --- 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 1f661ea..4988dd2 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -137,7 +137,7 @@ def funcdef(self, name, node, linkage=None): def visit_return(self, node): val = self.visit(node.value) if val.type != ir.VoidType(): - val = self.comp_cast(val, self.search_scopes(RET_VAR).type.pointee, node) + # val = self.comp_cast(val, self.search_scopes(RET_VAR).type.pointee, node) self.store(val, RET_VAR) self.branch(self.exit_blocks[-1]) return True From b962cf89569b842d4ea9b4e6a074c7f524e56915 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Thu, 17 Jan 2019 12:34:41 +0100 Subject: [PATCH 24/78] Code style refactoring --- .hooks/pre-commit.sh | 2 +- build_dist.sh | 1 + setup_env.sh | 2 +- src/lesma/compiler/code_generator.py | 8 ++------ src/lesma/compiler/llvmlite_custom.py | 2 +- src/lesma/lexer.py | 3 ++- 6 files changed, 8 insertions(+), 10 deletions(-) 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/build_dist.sh b/build_dist.sh index 84e236d..2fbb0b3 100755 --- a/build_dist.sh +++ b/build_dist.sh @@ -1 +1,2 @@ +#!/usr/bin/env bash pyinstaller src/les.py -D -n lesma \ No newline at end of file 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/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 4988dd2..8f6b6de 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -192,7 +192,7 @@ def methodcall(self, node, func, obj): 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 @@ -806,11 +806,7 @@ def print_num(self, num_format, num): 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]) + self.print_string(node.value.value) percent_d = self.stringz('%d') percent_ptr = self.alloc_and_store(percent_d, ir.ArrayType(percent_d.type.element, percent_d.type.count)) 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/lexer.py b/src/lesma/lexer.py index deb972e..f9bc21e 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -281,7 +281,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] From 6201ff5a9b06b739c4cc2e05bfe12f75915a25ca Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Thu, 17 Jan 2019 13:43:28 +0100 Subject: [PATCH 25/78] Main function now returns 0 instead of void for cross-platform compat --- src/lesma/compiler/code_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 8f6b6de..500c011 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -23,7 +23,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') @@ -55,7 +55,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): From 128c223751f55fb50faea97327625063b226a66c Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Thu, 17 Jan 2019 14:57:52 +0100 Subject: [PATCH 26/78] Added support for bin, octal, hex numbers in syntax --- docs/examples.md | 5 +++++ src/lesma/lexer.py | 24 ++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 043fd84..cf01ae2 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -15,6 +15,11 @@ 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 diff --git a/src/lesma/lexer.py b/src/lesma/lexer.py index f9bc21e..b077b6c 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -249,17 +249,33 @@ def get_next_token(self): 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 + dot_preset = False + while self.char_type == NUMERIC or (self.current_char == DOT and not dot_preset) 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 == '.': + dot_preset = True + elif self.char_type == ALPHANUMERIC: + if self.current_char in ('b', 'x', 'o') and self.word == '0': + if self.current_char == 'b': + base = 2 + elif self.current_char == 'x': + base = 16 + elif self.current_char == 'o': + base = 8 + + self.next_char() + self.word = "" + elif not (base == 16 and self.current_char in ('a', 'b', 'c', 'd', 'e', 'f')): + raise SyntaxError('Variables cannot start with numbers') 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) From ba3f15b93397410f43b5a4a9cdb65fc59b449c42 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Thu, 17 Jan 2019 18:31:36 +0100 Subject: [PATCH 27/78] Fixed linter error --- src/lesma/lexer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lesma/lexer.py b/src/lesma/lexer.py index b077b6c..49e2648 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -252,7 +252,8 @@ def get_next_token(self): base = 10 dot_preset = False while self.char_type == NUMERIC or (self.current_char == DOT and not dot_preset) or \ - (self.current_char in ('a', 'b', 'c', 'd', 'e', 'f', 'x', 'o')): + self.current_char in ('a', 'b', 'c', 'd', 'e', 'f', 'x', 'o'): + self.word += self.current_char self.next_char() if self.current_char == '.': From 86ea745c84f8c90cbe1c682bb419c932533ef3e0 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 18 Jan 2019 20:20:23 +0100 Subject: [PATCH 28/78] Input function is working(w/o strings yet) --- docs/TODO.md | 2 +- src/lesma/compiler/code_generator.py | 39 +++++++++++++++++++++++++--- src/lesma/compiler/operations.py | 2 +- src/lesma/visitor.py | 2 ++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index f01fb4d..faee878 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -2,7 +2,7 @@ ## Fixes - [ ] Fix type declaration for lists and tuples -- [ ] Fix input function +- [x] 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 diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 500c011..4113968 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -521,7 +521,10 @@ def visit_assign(self, node): 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 @@ -803,16 +806,44 @@ 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): self.print_string(node.value.value) - percent_d = self.stringz('%d') + 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 = [] @@ -999,7 +1030,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), []) diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 1da9291..23fa253 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -223,7 +223,7 @@ def cast_ops(compiler, left, right, node): elif cast_type == 'float' and orig_type == 'double': return compiler.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): diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index b817d79..09cb444 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -180,6 +180,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] From 3717a91e34926671540435a063026ab642a17902 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 18 Jan 2019 20:55:59 +0100 Subject: [PATCH 29/78] Update TODO --- docs/TODO.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index faee878..a3fea2b 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -5,18 +5,15 @@ - [x] 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 base unary operators being applied before user defined ones - [ ] Fix structs and classes types not being implicitly defined on assignment ## 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 - [x] Add docs for classes and structs -- [ ] Add docs for return type casting and compatible casting +- [ ] 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 @@ -24,10 +21,12 @@ - [ ] Add support for functions with same name but different parameters - [ ] Fix local - global variable behaviour, currently there's an implicit main func - [x] Allow default values for struct and class fields -- [ ] Use dataclasses and static typing as much as possible +- [ ] Use dataclasses and static typing as much as possible in source code +- [ ] Allow any type for lists/tuples (currently only int) +- [ ] String are currently unsupported on: type declaration with assignment, input function, operators, etc. +- [ ] Find a way to use pointers and null for FFI but restrict or not allow it in normal Lesma ## Features -- [ ] Implement Null (maybe someday) - [x] Implement Tuples - [x] Implement Classes - [ ] Implement Class inheritance From 80e7d543b33a58ab39015960c825bce9b98459d0 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 19 Jan 2019 21:08:01 +0100 Subject: [PATCH 30/78] Fixed decimals being registered as dot access --- src/lesma/lexer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lesma/lexer.py b/src/lesma/lexer.py index 49e2648..0cc7393 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -251,12 +251,12 @@ def get_next_token(self): if self.word_type == NUMERIC: base = 10 dot_preset = False - while self.char_type == NUMERIC or (self.current_char == DOT and not dot_preset) or \ + while self.char_type == NUMERIC or (self.current_char == DOT) or \ self.current_char in ('a', 'b', 'c', 'd', 'e', 'f', 'x', 'o'): self.word += self.current_char self.next_char() - if self.current_char == '.': + if self.current_char == '.' and base == 10 and not dot_preset: dot_preset = True elif self.char_type == ALPHANUMERIC: if self.current_char in ('b', 'x', 'o') and self.word == '0': From fd1644693e3b35783f3508058707d0e1ad44df78 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 19 Jan 2019 21:58:38 +0100 Subject: [PATCH 31/78] Refactoring --- src/lesma/compiler/builtins.py | 136 +++++++++++++------------- src/lesma/compiler/operations.py | 158 +++++++++++++++---------------- 2 files changed, 147 insertions(+), 147 deletions(-) diff --git a/src/lesma/compiler/builtins.py b/src/lesma/compiler/builtins.py index 94a291b..fe3141f 100644 --- a/src/lesma/compiler/builtins.py +++ b/src/lesma/compiler/builtins.py @@ -15,47 +15,47 @@ two_32 = ir.Constant(type_map[INT32], 2) -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('Dynamic_Array', 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 - 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) + dynamic_array_init(self, str_struct_ptr) + dynamic_array_double_if_full(self, str_struct_ptr) + dynamic_array_append(self, str_struct_ptr) + dynamic_array_get(self, str_struct_ptr) + dynamic_array_set(self, str_struct_ptr) + dynamic_array_length(self, str_struct_ptr) + define_create_range(self, str_struct_ptr) + 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(compiler, array_type): - array_ptr = compiler.search_scopes('{}_Array'.format(array_type)) +def create_dynamic_array_methods(self, array_type): + array_ptr = self.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) + dynamic_array_init(self, array_ptr) + dynamic_array_double_if_full(self, array_ptr) + dynamic_array_append(self, array_ptr) + dynamic_array_get(self, array_ptr) + dynamic_array_set(self, array_ptr) + dynamic_array_length(self, array_ptr) + define_create_range(self, array_ptr) -def define_create_range(compiler, dyn_array_struct_ptr): +def define_create_range(self, 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') + 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') @@ -77,7 +77,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('dyn_array_append'), [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,13 +86,13 @@ 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_struct_ptr): # 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 = ir.Function(self.module, dyn_array_init_type, 'dyn_array_init') 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) @@ -107,7 +107,7 @@ 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.call(self.module.get_global('malloc'), [size_of]) mem_alloc = builder.bitcast(mem_alloc, type_map[INT].as_pointer()) builder.store(mem_alloc, data_ptr) @@ -118,13 +118,13 @@ 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_struct_ptr): # 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 = ir.Function(self.module, dyn_array_double_capacity_if_full_type, 'dyn_array_double_capacity_if_full') 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) @@ -152,7 +152,7 @@ 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.call(self.module.get_global('realloc'), [data_ptr_8, size_of]) re_alloc = builder.bitcast(re_alloc, type_map[INT].as_pointer()) builder.store(re_alloc, data_ptr) @@ -163,13 +163,13 @@ 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_struct_ptr): # 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 = ir.Function(self.module, dyn_array_append_type, 'dyn_array_append') 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) @@ -178,7 +178,7 @@ def dynamic_array_append(compiler, dyn_array_struct_ptr): 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('dyn_array_double_capacity_if_full'), [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,13 +199,13 @@ 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_struct_ptr): # 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 = ir.Function(self.module, dyn_array_get_type, 'dyn_array_get') 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') @@ -227,8 +227,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,13 +259,13 @@ 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_struct_ptr): # 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 = ir.Function(self.module, dyn_array_set_type, 'dyn_array_set') 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') @@ -290,8 +290,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,13 +325,13 @@ 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_struct_ptr): # 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 = ir.Function(self.module, dyn_array_length_type, 'dyn_array_length') 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) builder.store(dyn_array_length.args[0], array_ptr) @@ -353,13 +353,13 @@ def dynamic_array_length(compiler, dyn_array_struct_ptr): # reverse() -def define_print(compiler, dyn_array_struct_ptr): +def define_print(self, dyn_array_struct_ptr): # START func_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr]) - func = ir.Function(compiler.module, func_type, 'print') + 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') @@ -371,7 +371,7 @@ def define_print(compiler, dyn_array_struct_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('dyn_array_length'), [builder.load(array_ptr)]) builder.branch(zero_length_check_block) builder.position_at_end(zero_length_check_block) @@ -388,25 +388,25 @@ 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('dyn_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_struct_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 = 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) @@ -423,10 +423,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('dyn_array_append'), [builder.load(array_ptr), char]) builder.branch(exit_block) # CLOSE @@ -434,20 +434,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_struct_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 = 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) 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('dyn_array_append') with builder.if_else(equalszero) as (then, otherwise): with then: diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 23fa253..59946e5 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -13,8 +13,8 @@ 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 @@ -31,58 +31,58 @@ def userdef_binary_str(op, left, right): return 'operator' + '.' + op + '.' + str(left.type) + '.' + str(right) -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 = 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) -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 = 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) + left = cast_ops(self, right, left.type, node) + return float_ops(self, op, left, right, node) -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 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 +90,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 +150,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 +197,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(type_map[STR]): raise NotImplementedError elif cast_type in (ANY, FUNC, ENUM, DICT, 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 From 4586f8b6bd90d6bfa8cfb9751cda173094f68a72 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 19 Jan 2019 23:03:41 +0100 Subject: [PATCH 32/78] Dynamic Arrays now have dynamic type using proto generic types WOHOOO --- docs/TODO.md | 4 +- src/lesma/compiler/builtins.py | 137 +++++++++++++++------------ src/lesma/compiler/code_generator.py | 50 +++++----- src/lesma/compiler/operations.py | 8 +- 4 files changed, 108 insertions(+), 91 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index a3fea2b..fb15d3a 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -22,8 +22,8 @@ - [ ] Fix local - global variable behaviour, currently there's an implicit main func - [x] Allow default values for struct and class fields - [ ] Use dataclasses and static typing as much as possible in source code -- [ ] Allow any type for lists/tuples (currently only int) -- [ ] String are currently unsupported on: type declaration with assignment, input function, operators, etc. +- [x] Allow any type for lists/tuples (currently only int) +- [ ] String/Lists are currently unsupported on: type declaration with assignment, input function, operators, etc. - [ ] Find a way to use pointers and null for FFI but restrict or not allow it in normal Lesma ## Features diff --git a/src/lesma/compiler/builtins.py b/src/lesma/compiler/builtins.py index fe3141f..918bb3f 100644 --- a/src/lesma/compiler/builtins.py +++ b/src/lesma/compiler/builtins.py @@ -14,44 +14,55 @@ one_32 = ir.Constant(type_map[INT32], 1) two_32 = ir.Constant(type_map[INT32], 2) +array_types = [INT] + 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()]) - self.define('Dynamic_Array', str_struct) - # self.define('Str', str_struct) + self.define('Str', str_struct) str_struct_ptr = str_struct.as_pointer() self.define('Str_ptr', str_struct_ptr) type_map[STR] = str_struct - dynamic_array_init(self, str_struct_ptr) - dynamic_array_double_if_full(self, str_struct_ptr) - dynamic_array_append(self, str_struct_ptr) - dynamic_array_get(self, str_struct_ptr) - dynamic_array_set(self, str_struct_ptr) - dynamic_array_length(self, str_struct_ptr) - define_create_range(self, str_struct_ptr) + dynamic_array_init(self, str_struct_ptr, INT) + dynamic_array_double_if_full(self, str_struct_ptr, INT) + dynamic_array_append(self, str_struct_ptr, INT) + dynamic_array_get(self, str_struct_ptr, INT) + dynamic_array_set(self, str_struct_ptr, INT) + dynamic_array_length(self, str_struct_ptr, INT) + + define_create_range(self, str_struct_ptr, INT) + 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): - array_ptr = self.search_scopes('{}_Array'.format(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) - dynamic_array_init(self, array_ptr) - dynamic_array_double_if_full(self, array_ptr) - dynamic_array_append(self, array_ptr) - dynamic_array_get(self, array_ptr) - dynamic_array_set(self, array_ptr) - dynamic_array_length(self, array_ptr) - define_create_range(self, array_ptr) + self.position_at_end(current_block) -def define_create_range(self, dyn_array_struct_ptr): - create_range_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr, type_map[INT], type_map[INT]]) +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) @@ -61,7 +72,7 @@ def define_create_range(self, dyn_array_struct_ptr): 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 +88,7 @@ def define_create_range(self, dyn_array_struct_ptr): builder.cbranch(cond, create_range_body, create_range_exit) builder.position_at_end(create_range_body) - builder.call(self.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 +97,16 @@ def define_create_range(self, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_init(self, 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(self.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) 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 @@ -108,7 +119,7 @@ def dynamic_array_init(self, 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(self.module.get_global('malloc'), [size_of]) - mem_alloc = builder.bitcast(mem_alloc, type_map[INT].as_pointer()) + mem_alloc = builder.bitcast(mem_alloc, type_map[array_type].as_pointer()) builder.store(mem_alloc, data_ptr) builder.branch(dyn_array_init_exit) @@ -118,17 +129,17 @@ def dynamic_array_init(self, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_double_if_full(self, 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(self.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) 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 @@ -153,7 +164,7 @@ def dynamic_array_double_if_full(self, dyn_array_struct_ptr): data_ptr_8 = builder.bitcast(builder.load(data_ptr), type_map[INT8].as_pointer()) re_alloc = builder.call(self.module.get_global('realloc'), [data_ptr_8, size_of]) - re_alloc = builder.bitcast(re_alloc, type_map[INT].as_pointer()) + re_alloc = builder.bitcast(re_alloc, type_map[array_type].as_pointer()) builder.store(re_alloc, data_ptr) builder.branch(dyn_array_double_capacity_if_full_exit) @@ -163,22 +174,22 @@ def dynamic_array_double_if_full(self, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_append(self, 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(self.module, dyn_array_append_type, 'dyn_array_append') + dyn_array_append_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, 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) 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(type_map[array_type]) builder.store(dyn_array_append.args[1], value_ptr) # BODY - builder.call(self.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,10 +210,10 @@ def dynamic_array_append(self, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_get(self, 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(self.module, dyn_array_get_type, 'dyn_array_get') + dyn_array_get_type = ir.FunctionType(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) self.builder = builder @@ -212,7 +223,7 @@ def dynamic_array_get(self, dyn_array_struct_ptr): 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) @@ -259,10 +270,10 @@ def dynamic_array_get(self, dyn_array_struct_ptr): builder.ret(builder.load(data_element_ptr)) -def dynamic_array_set(self, 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(self.module, dyn_array_set_type, 'dyn_array_set') + dyn_array_set_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, type_map[INT], 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) self.builder = builder @@ -272,11 +283,11 @@ def dynamic_array_set(self, dyn_array_struct_ptr): 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(type_map[array_type]) builder.store(dyn_array_set.args[2], value_ptr) # BODY @@ -325,15 +336,15 @@ def dynamic_array_set(self, dyn_array_struct_ptr): builder.ret_void() -def dynamic_array_length(self, 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(self.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) 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,9 +364,9 @@ def dynamic_array_length(self, dyn_array_struct_ptr): # reverse() -def define_print(self, dyn_array_struct_ptr): +def define_print(self, dyn_array_ptr): # START - func_type = ir.FunctionType(type_map[VOID], [dyn_array_struct_ptr]) + 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) @@ -366,12 +377,12 @@ def define_print(self, dyn_array_struct_ptr): 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(self.module.get_global('dyn_array_length'), [builder.load(array_ptr)]) + length = builder.call(self.module.get_global('int_array_length'), [builder.load(array_ptr)]) builder.branch(zero_length_check_block) builder.position_at_end(zero_length_check_block) @@ -388,7 +399,7 @@ def define_print(self, dyn_array_struct_ptr): builder.cbranch(cond, body_block, exit_block) builder.position_at_end(body_block) - char = builder.call(self.module.get_global('dyn_array_get'), [builder.load(array_ptr), builder.load(position_ptr)]) + char = builder.call(self.module.get_global('int_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) @@ -400,16 +411,16 @@ def define_print(self, dyn_array_struct_ptr): builder.ret_void() -def define_int_to_str(self, 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_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) 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) @@ -426,7 +437,7 @@ def define_int_to_str(self, dyn_array_struct_ptr): 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(self.module.get_global('dyn_array_append'), [builder.load(array_ptr), char]) + builder.call(self.module.get_global('int_array_append'), [builder.load(array_ptr), char]) builder.branch(exit_block) # CLOSE @@ -434,20 +445,20 @@ def define_int_to_str(self, dyn_array_struct_ptr): builder.ret_void() -def define_bool_to_str(self, 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_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) 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 = self.module.get_global('dyn_array_append') + dyn_array_append = self.module.get_global('int_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 4113968..3b33216 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -10,6 +10,8 @@ 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 +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 @@ -418,7 +420,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('int_array_length', [iterator]) self.branch(zero_length_block) self.position_at_end(zero_length_block) @@ -427,7 +429,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('int_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) @@ -437,7 +439,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('int_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: @@ -549,7 +551,9 @@ def visit_assign(self, node): 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]) + # Fix this ugly shit, maybe make identified struct types + 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) @@ -612,8 +616,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 @@ -690,7 +695,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) @@ -714,28 +719,28 @@ 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(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]) + # dyn_array_type = self.module.context.get_identified_type('{}_Array'.format(array_type)) + # dyn_array_type.set_body([type_map[INT], type_map[INT], 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), self.const(0).inttoptr(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(array_type), [array_ptr, element]) return self.load(array_ptr) def visit_hashmap(self, node): @@ -744,8 +749,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]) @@ -753,7 +759,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('int_array_append', [array, self.const(char)]) return array def visit_print(self, node): diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 59946e5..bbe5e91 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -36,7 +36,7 @@ def unary_op(self, node): 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") + [expr], "unop") elif op == MINUS: if isinstance(expr.type, ir.IntType): return self.builder.neg(expr) @@ -56,7 +56,7 @@ def binary_op(self, node): 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") + (left, right), "binop") elif op == CAST: return cast_ops(self, left, right, node) elif op in (IS, IS_NOT): @@ -107,7 +107,7 @@ def int_ops(self, op, left, right, node): return self.builder.udiv(left, right, 'divtmp') elif op == DIV: return (self.builder.fdiv(cast_ops(self, left, type_map[DOUBLE], node), - cast_ops(self, right, type_map[DOUBLE], node), 'fdivtmp')) + cast_ops(self, right, type_map[DOUBLE], node), 'fdivtmp')) elif op == MOD: if left.type.signed: return self.builder.srem(left, right, 'modtmp') @@ -162,7 +162,7 @@ def float_ops(self, op, left, right, node): return self.builder.fmul(left, right, 'fmultmp') elif op == FLOORDIV: return (self.builder.sdiv(cast_ops(self, left, ir.IntType(64), node), - cast_ops(self, right, ir.IntType(64), node), 'ffloordivtmp')) + cast_ops(self, right, ir.IntType(64), node), 'ffloordivtmp')) elif op == DIV: return self.builder.fdiv(left, right, 'fdivtmp') elif op == MOD: From 58cbec55c1f1145d9848b2aa45118f66d9be1878 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 21 Jan 2019 02:24:12 +0100 Subject: [PATCH 33/78] Tests were not properly reported if failed, many tests are fixed now --- src/lesma/compiler/__init__.py | 16 ++++++++++++++++ src/lesma/lexer.py | 16 ++++++++-------- tests/io/class.les | 2 +- tests/test_all.py | 5 ++--- tests/test_compile.py | 4 ++-- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/lesma/compiler/__init__.py b/src/lesma/compiler/__init__.py index 720572d..49278b6 100644 --- a/src/lesma/compiler/__init__.py +++ b/src/lesma/compiler/__init__.py @@ -26,6 +26,22 @@ VOID: ir.VoidType(), } +llvm_to_lesma_type = { + "u1": BOOL, + "u8": UINT8, + "u16": UINT16, + "u32": UINT32, + "u64": UINT64, + "u128": UINT128, + "i8": INT8, + "i16": INT16, + "i32": INT32, + "i64": INT, + "i128": INT128, + "double": DOUBLE, + "float": FLOAT, +} + llvm_type_map = { "u1": ir.IntType(1, signed=False), "u8": ir.IntType(8, signed=False), diff --git a/src/lesma/lexer.py b/src/lesma/lexer.py index 0cc7393..83c0b0e 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -250,15 +250,10 @@ def get_next_token(self): if self.word_type == NUMERIC: base = 10 - dot_preset = False - while self.char_type == NUMERIC or (self.current_char == DOT) or \ + 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.current_char == '.' and base == 10 and not dot_preset: - dot_preset = True - elif self.char_type == ALPHANUMERIC: + if self.char_type == ALPHANUMERIC: if self.current_char in ('b', 'x', 'o') and self.word == '0': if self.current_char == 'b': base = 2 @@ -270,7 +265,12 @@ def get_next_token(self): self.next_char() self.word = "" elif not (base == 16 and self.current_char in ('a', 'b', 'c', 'd', 'e', 'f')): - raise SyntaxError('Variables cannot start with numbers') + print(self.word, self.current_char) + else: + error("Unexpected number parsing") + + self.next_char() + value = self.reset_word() if '.' in value: value = Decimal(value) diff --git a/tests/io/class.les b/tests/io/class.les index a5d7a1d..8762dfe 100644 --- a/tests/io/class.les +++ b/tests/io/class.les @@ -5,7 +5,7 @@ class Thing self.a_thing = 1 self.b_thing = b_thing print(5) - print(just_demo()) + print(self.just_demo()) def another() print(self.a_thing) diff --git a/tests/test_all.py b/tests/test_all.py index 2439f90..b739489 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -17,9 +17,9 @@ def get_tests(): @pytest.mark.parametrize("test_name", get_tests()) def test_base(test_name): path = os.path.join(os.path.dirname(__file__), os.pardir) - proc = Popen(["python", os.path.join(path, "src", "les.py"), + proc = Popen(["python3", os.path.join(path, "src", "les.py"), "run", os.path.join(path, "tests", "io", test_name + ".les")], - stdout=PIPE, stderr=PIPE, shell=True) + stdout=PIPE, stderr=PIPE) out, err = proc.communicate() output = out.decode('utf-8').strip() error = err.decode('utf-8').strip() @@ -32,4 +32,3 @@ def test_base(test_name): with open(os.path.join(path, "tests", "io", test_name + ".output")) as expected: exp_str = "".join(expected.readlines()) assert output == exp_str - expected.close() diff --git a/tests/test_compile.py b/tests/test_compile.py index efd0d45..460c0ee 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -19,10 +19,10 @@ def get_tests(): def test_base(test_name): path = os.path.join(os.path.dirname(__file__), os.pardir) with tempfile.TemporaryDirectory() as dirpath: - proc = Popen(["python", os.path.join(path, "src", "les.py"), + proc = Popen(["python3", os.path.join(path, "src", "les.py"), "compile", os.path.join(path, "tests", "io", test_name + ".les"), "-l", "-o", os.path.join(dirpath, "temp")], - stdout=PIPE, stderr=PIPE, shell=True) + stdout=PIPE, stderr=PIPE) out, err = proc.communicate() output = out.decode('utf-8').strip() error = err.decode('utf-8').strip() From a655c46359ecf61226c418e760c5fee10f3dd4e2 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 21 Jan 2019 02:24:59 +0100 Subject: [PATCH 34/78] Fix --- src/lesma/compiler/code_generator.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 3b33216..00aeff1 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -7,7 +7,7 @@ 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_to_lesma_type 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 @@ -553,6 +553,8 @@ def visit_assign(self, node): right = self.visit(node.right) # Fix this ugly shit, maybe make identified struct types array_type = str(self.search_scopes(node.left.collection.value).type.pointee.elements[-1].pointee) + if array_type in llvm_to_lesma_type.keys(): + array_type = llvm_to_lesma_type[array_type] 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 @@ -616,7 +618,10 @@ def visit_opassign(self, node): if isinstance(node.left, CollectionAccess): collection_access = True var_name = self.search_scopes(node.left.collection.value) + # Fix this ugly shit array_type = str(self.search_scopes(node.left.collection.value).type.pointee.elements[-1].pointee) + if array_type in llvm_to_lesma_type.keys(): + array_type = llvm_to_lesma_type[array_type] key = self.const(node.left.key.value) var = self.call('{}_array_get'.format(array_type), [var_name, key]) pointee = var.type @@ -764,13 +769,16 @@ def visit_str(self, node): def visit_print(self, node): if node.value: + # print(node) + # print(node.value) + # exit() val = self.visit(node.value) else: self.call('putchar', [ir.Constant(type_map[INT32], 10)]) 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: From e9bfea01b49fa87ab77dcf64eaec2bc1adc96109 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 21 Jan 2019 08:24:17 +0100 Subject: [PATCH 35/78] Removed compiling test as it slows down commits and it's not important --- tests/test_compile.py | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 tests/test_compile.py diff --git a/tests/test_compile.py b/tests/test_compile.py deleted file mode 100644 index 460c0ee..0000000 --- a/tests/test_compile.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -import pytest -import tempfile -from subprocess import Popen, PIPE - - -def get_tests(): - tests = [] - path = os.path.dirname(__file__) - for file in os.listdir(os.path.join(path, "io")): - if file.endswith(".les"): - tests.append(os.path.basename(file).split('.')[0]) - - return tests - - -# Base test for Lesma script files -@pytest.mark.parametrize("test_name", get_tests()) -def test_base(test_name): - path = os.path.join(os.path.dirname(__file__), os.pardir) - with tempfile.TemporaryDirectory() as dirpath: - proc = Popen(["python3", os.path.join(path, "src", "les.py"), - "compile", os.path.join(path, "tests", "io", test_name + ".les"), - "-l", "-o", os.path.join(dirpath, "temp")], - 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 From b07482fe25fbc0161b7331fee992eaf75ef5cc75 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 21 Jan 2019 08:46:45 +0100 Subject: [PATCH 36/78] Lists now use llvm types as func identifiers --- src/lesma/compiler/builtins.py | 41 ++++++++++++++-------------- src/lesma/compiler/code_generator.py | 27 +++++++----------- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/lesma/compiler/builtins.py b/src/lesma/compiler/builtins.py index 918bb3f..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,7 +14,7 @@ one_32 = ir.Constant(type_map[INT32], 1) two_32 = ir.Constant(type_map[INT32], 2) -array_types = [INT] +array_types = [str(type_map[INT])] def define_builtins(self): @@ -26,15 +26,16 @@ def define_builtins(self): str_struct_ptr = str_struct.as_pointer() self.define('Str_ptr', str_struct_ptr) type_map[STR] = str_struct + lint = str(type_map[INT]) - dynamic_array_init(self, str_struct_ptr, INT) - dynamic_array_double_if_full(self, str_struct_ptr, INT) - dynamic_array_append(self, str_struct_ptr, INT) - dynamic_array_get(self, str_struct_ptr, INT) - dynamic_array_set(self, str_struct_ptr, INT) - dynamic_array_length(self, str_struct_ptr, 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, INT) + define_create_range(self, str_struct_ptr, lint) define_int_to_str(self, str_struct_ptr) define_bool_to_str(self, str_struct_ptr) @@ -119,7 +120,7 @@ def dynamic_array_init(self, dyn_array_ptr, array_type): 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(self.module.get_global('malloc'), [size_of]) - mem_alloc = builder.bitcast(mem_alloc, type_map[array_type].as_pointer()) + 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) @@ -164,7 +165,7 @@ def dynamic_array_double_if_full(self, dyn_array_ptr, array_type): data_ptr_8 = builder.bitcast(builder.load(data_ptr), type_map[INT8].as_pointer()) re_alloc = builder.call(self.module.get_global('realloc'), [data_ptr_8, size_of]) - re_alloc = builder.bitcast(re_alloc, type_map[array_type].as_pointer()) + 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) @@ -176,7 +177,7 @@ def dynamic_array_double_if_full(self, dyn_array_ptr, array_type): def dynamic_array_append(self, dyn_array_ptr, array_type): # START - dyn_array_append_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, type_map[array_type]]) + 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) @@ -185,7 +186,7 @@ def dynamic_array_append(self, dyn_array_ptr, array_type): builder.position_at_end(dyn_array_append_entry) array_ptr = builder.alloca(dyn_array_ptr) builder.store(dyn_array_append.args[0], array_ptr) - value_ptr = builder.alloca(type_map[array_type]) + value_ptr = builder.alloca(llvm_type_map[array_type]) builder.store(dyn_array_append.args[1], value_ptr) # BODY @@ -212,7 +213,7 @@ def dynamic_array_append(self, dyn_array_ptr, array_type): def dynamic_array_get(self, dyn_array_ptr, array_type): # START - dyn_array_get_type = ir.FunctionType(type_map[array_type], [dyn_array_ptr, type_map[INT]]) + 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) @@ -272,7 +273,7 @@ def dynamic_array_get(self, dyn_array_ptr, array_type): def dynamic_array_set(self, dyn_array_ptr, array_type): # START - dyn_array_set_type = ir.FunctionType(type_map[VOID], [dyn_array_ptr, type_map[INT], type_map[array_type]]) + 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) @@ -287,7 +288,7 @@ def dynamic_array_set(self, dyn_array_ptr, array_type): 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[array_type]) + value_ptr = builder.alloca(llvm_type_map[array_type]) builder.store(dyn_array_set.args[2], value_ptr) # BODY @@ -382,7 +383,7 @@ def define_print(self, dyn_array_ptr): # BODY builder.position_at_end(entry_block) - length = builder.call(self.module.get_global('int_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) @@ -399,7 +400,7 @@ def define_print(self, dyn_array_ptr): builder.cbranch(cond, body_block, exit_block) builder.position_at_end(body_block) - char = builder.call(self.module.get_global('int_array_get'), [builder.load(array_ptr), builder.load(position_ptr)]) + 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) @@ -437,7 +438,7 @@ def define_int_to_str(self, dyn_array_ptr): 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(self.module.get_global('int_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 @@ -458,7 +459,7 @@ def define_bool_to_str(self, dyn_array_ptr): # BODY equalszero = builder.icmp_signed(EQUALS, func.args[1], ir.Constant(type_map[BOOL], 0)) - dyn_array_append = self.module.get_global('int_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 00aeff1..a87a637 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -7,7 +7,7 @@ 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, llvm_to_lesma_type +from lesma.compiler import RET_VAR, type_map, llvm_to_lesma_type, 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 @@ -420,7 +420,7 @@ def visit_for(self, node): iterator = self.visit(node.iterator) else: iterator = self.search_scopes(node.iterator.value) - stop = self.call('int_array_length', [iterator]) + stop = self.call('i64_array_length', [iterator]) self.branch(zero_length_block) self.position_at_end(zero_length_block) @@ -429,7 +429,7 @@ def visit_for(self, node): self.position_at_end(non_zero_length_block) varname = node.elements[0].value - val = self.call('int_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) @@ -439,7 +439,7 @@ def visit_for(self, node): self.cbranch(cond, body_block, end_block) self.position_at_end(body_block) - self.store(self.call('int_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: @@ -551,10 +551,7 @@ def visit_assign(self, node): self.builder.store(self.visit(node.right), elem) elif isinstance(node.left, CollectionAccess): right = self.visit(node.right) - # Fix this ugly shit, maybe make identified struct types array_type = str(self.search_scopes(node.left.collection.value).type.pointee.elements[-1].pointee) - if array_type in llvm_to_lesma_type.keys(): - array_type = llvm_to_lesma_type[array_type] 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 @@ -618,10 +615,7 @@ def visit_opassign(self, node): if isinstance(node.left, CollectionAccess): collection_access = True var_name = self.search_scopes(node.left.collection.value) - # Fix this ugly shit array_type = str(self.search_scopes(node.left.collection.value).type.pointee.elements[-1].pointee) - if array_type in llvm_to_lesma_type.keys(): - array_type = llvm_to_lesma_type[array_type] key = self.const(node.left.key.value) var = self.call('{}_array_get'.format(array_type), [var_name, key]) pointee = var.type @@ -727,15 +721,14 @@ def define_array(self, node, elements): array_type = node.items[0].val_type array_ptr = self.create_array(array_type) for element in elements: - self.call('{}_array_append'.format(array_type), [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.module.context.get_identified_type('{}_Array'.format(array_type)) - # dyn_array_type.set_body([type_map[INT], type_map[INT], type_map[array_type].as_pointer()]) - dyn_array_type = ir.LiteralStructType([type_map[INT], type_map[INT], type_map[array_type].as_pointer()]) + 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(type_map[array_type].as_pointer())]) + 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) create_dynamic_array_methods(self, array_type) self.call('{}_array_init'.format(array_type), [array]) @@ -745,7 +738,7 @@ def define_tuple(self, node, elements): array_type = node.items[0].val_type array_ptr = self.create_array(array_type) for element in elements: - self.call('{}_array_append'.format(array_type), [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): @@ -764,7 +757,7 @@ def visit_str(self, node): array = self.create_array(INT) string = node.value.encode('utf-8') for char in string: - self.call('int_array_append', [array, self.const(char)]) + self.call('i64_array_append', [array, self.const(char)]) return array def visit_print(self, node): From 7f69511756f87aee899221c2a932d59916500d5a Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 21 Jan 2019 23:14:15 +0100 Subject: [PATCH 37/78] Fixed inconsistent docs, self now a proper keyword --- docs/features/syntax.md | 2 +- src/lesma/compiler/code_generator.py | 4 ++-- src/lesma/grammar.py | 4 ++-- src/lesma/parser.py | 2 +- test.les | 19 +++++++++++++++++++ 5 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 test.les diff --git a/docs/features/syntax.md b/docs/features/syntax.md index 811f142..60a4424 100644 --- a/docs/features/syntax.md +++ b/docs/features/syntax.md @@ -30,6 +30,6 @@ struct thing class Example new(x: int) - this.x = x + self.x = x ``` diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index a87a637..d0e030b 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -112,7 +112,7 @@ def funcimpl(self, name, node): 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): + 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) @@ -127,7 +127,7 @@ def funcdef(self, name, node, linkage=None): 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): + 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) diff --git a/src/lesma/grammar.py b/src/lesma/grammar.py index 08a6cbb..93bbfc5 100644 --- a/src/lesma/grammar.py +++ b/src/lesma/grammar.py @@ -99,7 +99,7 @@ CONST = 'const' NEW = 'new' # TODO SUPER = 'super' # TODO -THIS = 'this' # TODO +SELF = 'self' RETURN = 'return' TRY = 'try' # TODO CATCH = 'catch' # TODO @@ -151,7 +151,7 @@ ) 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, TYPE, FALLTHROUGH, DEFER diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 5dff2fc..c70cf0e 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -221,7 +221,7 @@ def method_declaration(self, class_name): params = OrderedDict() param_defaults = {} vararg = None - params['self'] = class_name + params[SELF] = class_name while self.current_token.value != RPAREN: param_name = self.current_token.value self.eat_type(NAME) diff --git a/test.les b/test.les new file mode 100644 index 0000000..0c920bb --- /dev/null +++ b/test.les @@ -0,0 +1,19 @@ +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) \ No newline at end of file From 85af41ce22e1a7deaf4becb4f58fecc9975c0517 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 21 Jan 2019 23:20:20 +0100 Subject: [PATCH 38/78] More refactoring --- docs/features/keywords.md | 2 +- src/lesma/compiler/operations.py | 6 +++--- src/lesma/parser.py | 2 +- test.les | 19 ------------------- 4 files changed, 5 insertions(+), 24 deletions(-) delete mode 100644 test.les diff --git a/docs/features/keywords.md b/docs/features/keywords.md index a543277..d77ce6f 100644 --- a/docs/features/keywords.md +++ b/docs/features/keywords.md @@ -9,7 +9,7 @@ For reference, here are all the keywords categorized by type. | switch | case | default | def | | const | break | continue | pass | | void | type | extern | operator | -| fallthrough | defer +| fallthrough | defer | self ## Operator keywords diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index bbe5e91..a5b1db6 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -20,15 +20,15 @@ def hasFunction(self, func_name): def userdef_unary_str(op, expr): - return 'operator' + '.' + op + '.' + str(expr.type) + return OPERATOR + '.' + op + '.' + str(expr.type) # 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) + return OPERATOR + '.' + op + '.' + str(left.type) + '.' + str(right.type) except Exception: - return 'operator' + '.' + op + '.' + str(left.type) + '.' + str(right) + return OPERATOR + '.' + op + '.' + str(left.type) + '.' + str(right) def unary_op(self, node): diff --git a/src/lesma/parser.py b/src/lesma/parser.py index c70cf0e..0728cbf 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -208,7 +208,7 @@ 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)]) diff --git a/test.les b/test.les deleted file mode 100644 index 0c920bb..0000000 --- a/test.les +++ /dev/null @@ -1,19 +0,0 @@ -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) \ No newline at end of file From 115cfd20f37b03ecb36cfad823d388451db2467f Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 00:37:03 +0100 Subject: [PATCH 39/78] Implemented basic enums (operations not yet supported, it's kinda late) --- src/lesma/ast.py | 7 +++++++ src/lesma/compiler/code_generator.py | 25 +++++++++++++++++++++---- src/lesma/grammar.py | 2 +- src/lesma/parser.py | 18 ++++++++++++++++++ src/lesma/type_checker.py | 10 +++++++--- src/lesma/visitor.py | 8 ++++++++ 6 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/lesma/ast.py b/src/lesma/ast.py index 7713241..23b368b 100644 --- a/src/lesma/ast.py +++ b/src/lesma/ast.py @@ -111,6 +111,13 @@ def __init__(self, value, line_num): self.line_num = line_num +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 diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index d0e030b..a8459f1 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -265,6 +265,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(64, signed=False)]) + self.define(node.name, enum) + def visit_structdeclaration(self, node): fields = [] for field in node.fields.values(): @@ -349,7 +357,8 @@ def visit_vardecl(self, node): func_parameters = self.get_args(node.type.func_params) func_ty = ir.FunctionType(func_ret_type, func_parameters, None).as_pointer() typ = func_ty - self.alloc_and_define(typ, name=node.value.value) + + self.alloc_and_define(node.value.value, typ) @staticmethod def visit_type(node): @@ -517,7 +526,8 @@ def visit_range(self, node): return array_ptr def visit_assign(self, node): - if hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), ir.IdentifiedStructType): + 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): 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)) @@ -605,8 +615,15 @@ def struct_assign(self, node): def visit_dotaccess(self, node): obj = self.search_scopes(node.obj) - 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)) + 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), val) + return enum + else: + 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): right = self.visit(node.right) diff --git a/src/lesma/grammar.py b/src/lesma/grammar.py index 93bbfc5..5830e6b 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' diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 0728cbf..08fae45 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -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() @@ -403,6 +419,8 @@ def statement(self): 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: diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 435032f..a7fd90c 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 TypeSymbol, CollectionSymbol, FuncSymbol, NodeVisitor, StructSymbol, ClassSymbol, VarSymbol +from lesma.visitor import TypeSymbol, CollectionSymbol, FuncSymbol, NodeVisitor, StructSymbol, EnumSymbol, ClassSymbol, VarSymbol from lesma.utils import warning, error @@ -129,7 +129,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 hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), (StructSymbol, ClassSymbol)): + 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.accessed = True @@ -408,7 +408,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: @@ -464,6 +464,10 @@ 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): 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) diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 09cb444..032878e 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -65,6 +65,14 @@ 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 + + class StructSymbol(Symbol): def __init__(self, name, fields): super().__init__(name) From b866b28b1e31ac0ee0a7f48e3b70eca6e1fa84f1 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 00:37:28 +0100 Subject: [PATCH 40/78] Sanitize text before lexer --- src/lesma/lexer.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lesma/lexer.py b/src/lesma/lexer.py index 83c0b0e..e9677aa 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,14 @@ def next_char(self): self.current_char = self.text[self.pos] self.char_type = self.get_type(self.current_char) + def sanitize_text(self, 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 = '' From e1af82368b60d4c57a685d11e261e6e4fee1c0bc Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 11:24:53 +0100 Subject: [PATCH 41/78] Enum objects now use i8 for space improvements --- src/lesma/compiler/code_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index a8459f1..41d5a39 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -270,7 +270,7 @@ def visit_enumdeclaration(self, node): enum.fields = [field for field in node.fields] enum.name = node.name enum.type = ENUM - enum.set_body([ir.IntType(64, signed=False)]) + enum.set_body([ir.IntType(8, signed=False)]) self.define(node.name, enum) def visit_structdeclaration(self, node): @@ -619,7 +619,7 @@ def visit_dotaccess(self, node): 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), val) + self.builder.store(self.const(idx, width=INT8), val) return enum else: obj_type = self.search_scopes(obj.type.pointee.name.split('.')[-1]) From e8a77b6ebe9812b1db9ea07bc43d6ab9ffd5cce5 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 11:39:14 +0100 Subject: [PATCH 42/78] Struct, Class and Enum types are now inferred on var assign, enum tests --- src/lesma/compiler/code_generator.py | 3 ++- src/lesma/type_checker.py | 8 +++++++- src/lesma/visitor.py | 2 ++ tests/io/enum.les | 5 +++++ tests/io/struct.les | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tests/io/enum.les diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 41d5a39..1ea1ec9 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -528,7 +528,8 @@ def visit_range(self, node): 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): - self.define(node.left.value.value, self.visit(node.right)) + 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: diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index a7fd90c..87f40f4 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -131,8 +131,9 @@ def visit_assign(self, node): # TODO clean up this mess of a function value.accessed = True 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 + value = self.infer_type(value) elif isinstance(node.right, Collection): var_name = node.left.value value, collection_type = self.visit(node.right) @@ -140,6 +141,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 diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 032878e..af63008 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -247,6 +247,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/enum.les b/tests/io/enum.les new file mode 100644 index 0000000..ee1a70f --- /dev/null +++ b/tests/io/enum.les @@ -0,0 +1,5 @@ +enum Color + Red + White + +x: Color = Color.White \ No newline at end of file diff --git a/tests/io/struct.les b/tests/io/struct.les index fcd8408..8b737ca 100644 --- a/tests/io/struct.les +++ b/tests/io/struct.les @@ -4,6 +4,7 @@ struct Circle y: int = 99 cir: Circle = Circle(radius=5, x=2) +anotherCir = Circle(radius=1, x=3) print(cir.radius + cir.x) cir.x = 20 From ba74bfabf33e3b2df8b815459cda106de0b901b1 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 11:40:46 +0100 Subject: [PATCH 43/78] Updated class tests to include inferred type assign --- tests/io/class.les | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/io/class.les b/tests/io/class.les index 8762dfe..8893d68 100644 --- a/tests/io/class.les +++ b/tests/io/class.les @@ -1,3 +1,10 @@ +class DemoClass + def new() + pass + +y = DemoClass() + + class Thing a_thing: int b_thing: float @@ -15,6 +22,7 @@ class Thing def just_demo() -> int return 5 + x: Thing = Thing() print(x.a_thing) x.a_thing = 3 From 1f3b63bd36bbc98f10c6338a35b1337959b872e1 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 12:35:51 +0100 Subject: [PATCH 44/78] Refactoring, updated TODO, preparing for 0.4.0 --- docs/TODO.md | 5 +++-- src/lesma/compiler/code_generator.py | 10 +++------- src/lesma/type_checker.py | 3 --- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index fb15d3a..aa7958d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -6,7 +6,7 @@ - [ ] Fix not being able to return user-defined structs and classes - [ ] Fix not being able to overload operators on user-defined structs and classes - [ ] Fix base unary operators being applied before user defined ones -- [ ] Fix structs and classes types not being implicitly defined on assignment +- [x] Fix structs and classes types not being implicitly defined on assignment ## Improvements - [ ] Improve warning messages @@ -25,6 +25,7 @@ - [x] Allow any type for lists/tuples (currently only int) - [ ] String/Lists are currently unsupported on: type declaration with assignment, input function, operators, etc. - [ ] Find a way to use pointers and null for FFI but restrict or not allow it in normal Lesma +- [ ] Add basic operators for Enums ## Features - [x] Implement Tuples @@ -35,7 +36,7 @@ - [x] Implement anonymous functions - [ ] Implement Closure - [ ] Implement string interpolation -- [ ] Implement Enums +- [x] Implement Enums - [ ] Implement lambda functions - [x] Implement defer keyword - [x] Implement fallthrough and change switch's behaviour \ No newline at end of file diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 1ea1ec9..50a7b44 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -139,7 +139,7 @@ def funcdef(self, name, node, linkage=None): def visit_return(self, node): val = self.visit(node.value) if val.type != ir.VoidType(): - # val = self.comp_cast(val, self.search_scopes(RET_VAR).type.pointee, node) + val = self.comp_cast(val, self.search_scopes(RET_VAR).type.pointee, node) self.store(val, RET_VAR) self.branch(self.exit_blocks[-1]) return True @@ -521,7 +521,7 @@ 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 @@ -780,9 +780,6 @@ def visit_str(self, node): def visit_print(self, node): if node.value: - # print(node) - # print(node.value) - # exit() val = self.visit(node.value) else: self.call('putchar', [ir.Constant(type_map[INT32], 10)]) @@ -858,8 +855,7 @@ def typeToFormat(typ): return fmt def visit_input(self, node): - # Print text if it exists - if isinstance(node.value, Str): + 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])) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 87f40f4..44f1eac 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -218,9 +218,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: From df78473f055bf0c960dfc8b75a8c585016741969 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 18:50:52 +0100 Subject: [PATCH 45/78] Fixed var declaration of lists and tuples --- docs/TODO.md | 2 +- src/lesma/compiler/code_generator.py | 15 ++++++++++----- src/lesma/grammar.py | 2 +- src/lesma/parser.py | 14 +++++++++++++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index aa7958d..d4b9c85 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,7 +1,7 @@ # TODO ## Fixes -- [ ] Fix type declaration for lists and tuples +- [x] Fix type declaration for lists and tuples - [x] 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 diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 50a7b44..cb24d71 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -543,12 +543,17 @@ def visit_assign(self, node): 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.type.pointee.name.split('.')[-1]) diff --git a/src/lesma/grammar.py b/src/lesma/grammar.py index 5830e6b..fa8a1a1 100644 --- a/src/lesma/grammar.py +++ b/src/lesma/grammar.py @@ -159,7 +159,7 @@ 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) diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 08fae45..792845d 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -330,7 +330,19 @@ def type_spec(self): 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() From 7cc53c3fb5bcb8da1248cefcd0d459ff7591abea Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Tue, 22 Jan 2019 20:22:59 +0100 Subject: [PATCH 46/78] Fixed declaration contradiction between lists and tuples --- src/lesma/type_checker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 44f1eac..59f4765 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -129,6 +129,8 @@ 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 + if value.name in (TUPLE, LIST) and node.right.type != value.type: + 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.search_scopes(node.right.name) From 5399e50b0558258d86ff26e2de724768ff338cef Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 25 Jan 2019 10:05:00 +0100 Subject: [PATCH 47/78] Fixed typechecker bug with undeclared struct vars and added tests --- src/lesma/type_checker.py | 1 - tests/io/struct.les | 1 + tests/io/struct.output | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 59f4765..c4aa7b6 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -135,7 +135,6 @@ def visit_assign(self, node): # TODO clean up this mess of a function var_name = node.left.value value = self.search_scopes(node.right.name) value.accessed = True - value = self.infer_type(value) elif isinstance(node.right, Collection): var_name = node.left.value value, collection_type = self.visit(node.right) diff --git a/tests/io/struct.les b/tests/io/struct.les index 8b737ca..9f8cf09 100644 --- a/tests/io/struct.les +++ b/tests/io/struct.les @@ -6,6 +6,7 @@ struct Circle 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) diff --git a/tests/io/struct.output b/tests/io/struct.output index 8ff7834..d848eb0 100644 --- a/tests/io/struct.output +++ b/tests/io/struct.output @@ -1,3 +1,4 @@ +3 7 20 99 \ No newline at end of file From e496e209fd71faf92a183aaef17d5c26e558aef3 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 25 Jan 2019 10:22:01 +0100 Subject: [PATCH 48/78] Struct initialization now expects definition of all non default fields --- docs/TODO.md | 2 +- src/lesma/compiler/code_generator.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index d4b9c85..90f609e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -17,7 +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 +- [x] 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 - [x] Allow default values for struct and class fields diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index cb24d71..22011ee 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -600,21 +600,24 @@ def struct_assign(self, node): struct_type = self.search_scopes(node.name) struct = self.builder.alloca(struct_type) - # Once for defaults - fields = [] + fields = set() for index, field in struct_type.defaults.items(): - fields.append(self.visit(field)) + 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(fields[-1], elem) + self.builder.store(val, elem) - # Another one for calling values - fields.clear() for index, field in enumerate(node.named_arguments.values()): - fields.append(self.visit(field)) + 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(fields[-1], elem) + 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 From 8e950938acb233c4e6be8cd9542e5b06b04308f9 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 25 Jan 2019 10:25:51 +0100 Subject: [PATCH 49/78] Fixed list initialization typechecker error --- src/lesma/type_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index c4aa7b6..b2c2396 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -129,7 +129,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 - if value.name in (TUPLE, LIST) and node.right.type != value.type: + 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 From b33955bc0d35da6352d4c3e1cf07cd4b1c1201a8 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 25 Jan 2019 10:42:09 +0100 Subject: [PATCH 50/78] Fixed lists and tuples w/ declared type, updated list/tuple tests --- src/lesma/type_checker.py | 3 +++ tests/io/list.les | 9 ++++++++- tests/io/list.output | 4 +++- tests/io/tuple.les | 6 +++++- tests/io/tuple.output | 4 +++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index b2c2396..01f8952 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -129,6 +129,9 @@ 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 + 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)): diff --git a/tests/io/list.les b/tests/io/list.les index 8ce4c6f..5eb4d64 100644 --- a/tests/io/list.les +++ b/tests/io/list.les @@ -2,4 +2,11 @@ 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]) + +x: list[double] = [1.5] +print(x[0]) +x[0] = 5.5 + +y = [2.5,3.5] +print(y[0]) \ No newline at end of file diff --git a/tests/io/list.output b/tests/io/list.output index 5efd464..785f6e3 100644 --- a/tests/io/list.output +++ b/tests/io/list.output @@ -1,3 +1,5 @@ 1 3 -1002 \ No newline at end of file +1002 +1.5 +2.5 \ No newline at end of file diff --git a/tests/io/tuple.les b/tests/io/tuple.les index 10e5241..4a4f304 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) +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 From eced0b9ae30b235a7271c567b125fbb8ed4d26f0 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 25 Jan 2019 19:28:15 +0100 Subject: [PATCH 51/78] Added equality operator for enums --- src/lesma/compiler/operations.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index a5b1db6..29a1667 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -3,6 +3,7 @@ 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 @@ -54,6 +55,7 @@ def binary_op(self, node): op = node.op 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") @@ -69,6 +71,14 @@ def binary_op(self, node): elif isinstance(right.type, ir.IntType): left = cast_ops(self, right, left.type, node) return float_ops(self, op, left, right, node) + elif hasattr(left.type, 'type') and left.type.type == ENUM and hasattr(right.type, 'type') and right.type.type == ENUM: + 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_ops(self, op, left, right, node): @@ -82,6 +92,15 @@ def is_ops(self, op, left, right, node): raise SyntaxError('Unknown identity operator', node.op) +def enum_ops(self, op, left, right, node): + 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 \ From ad4aa063be2d8e4b1f2f3622c689b125d06e7e02 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Fri, 25 Jan 2019 19:28:52 +0100 Subject: [PATCH 52/78] Fixed typechecker type comparison bug --- src/lesma/type_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 01f8952..38395f4 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -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 From 4c74f239c6c8fe34ef51fc07e440e86a19ce7507 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 26 Jan 2019 10:49:50 +0100 Subject: [PATCH 53/78] Added equality operator to Enums, fixed bugs --- src/lesma/compiler/operations.py | 12 +++++++++++- src/lesma/type_checker.py | 4 +++- src/lesma/visitor.py | 3 +++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 29a1667..6fbd999 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -71,7 +71,11 @@ def binary_op(self, node): elif isinstance(right.type, ir.IntType): left = cast_ops(self, right, left.type, node) return float_ops(self, op, left, right, node) - elif hasattr(left.type, 'type') and left.type.type == ENUM and hasattr(right.type, 'type') and right.type.type == ENUM: + elif is_enum(left.type) and is_enum(right.type): + if left.type.is_pointer: + left = self.builder.load(left) + if right.type.is_pointer: + right = self.builder.load(right) return enum_ops(self, op, left, right, node) else: error('file={} line={}: Unknown operator {} for {} and {}'.format( @@ -81,6 +85,12 @@ def binary_op(self, node): )) +def is_enum(typ): + if typ.is_pointer: + typ = typ.pointee + return hasattr(typ, 'type') and typ.type == ENUM + + def is_ops(self, op, left, right, node): orig = str(left.type) compare = str(right) diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 38395f4..6d9f80f 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -515,7 +515,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 af63008..4ca1b83 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -72,6 +72,9 @@ def __init__(self, name, fields): self.accessed = False self.val_assigned = False + def __str__(self): + return ENUM + class StructSymbol(Symbol): def __init__(self, name, fields): From 6bba8ea18531aff1a2d2dd803fa916901c95433c Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 26 Jan 2019 10:51:49 +0100 Subject: [PATCH 54/78] Updated enum test --- tests/io/enum.les | 6 +++++- tests/io/enum.output | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 tests/io/enum.output diff --git a/tests/io/enum.les b/tests/io/enum.les index ee1a70f..aa5c830 100644 --- a/tests/io/enum.les +++ b/tests/io/enum.les @@ -2,4 +2,8 @@ enum Color Red White -x: Color = Color.White \ No newline at end of file +x: Color = Color.White +y: Color = Color.Red +print(x == Color.White) +print(x == Color.Red) +print(x == y) \ No newline at end of file diff --git a/tests/io/enum.output b/tests/io/enum.output new file mode 100644 index 0000000..1246878 --- /dev/null +++ b/tests/io/enum.output @@ -0,0 +1,3 @@ +true +false +false \ No newline at end of file From 637854ce5eb8d59ad532db2910db259032d8556c Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 26 Jan 2019 11:25:56 +0100 Subject: [PATCH 55/78] Updates docs, refactoring --- docs/TODO.md | 2 +- docs/features/enums.md | 12 ++++++++++++ src/lesma/type_checker.py | 13 +------------ tests/io/anon_func.les | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 docs/features/enums.md diff --git a/docs/TODO.md b/docs/TODO.md index 90f609e..cb5cce8 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -25,7 +25,7 @@ - [x] Allow any type for lists/tuples (currently only int) - [ ] String/Lists are currently unsupported on: type declaration with assignment, input function, operators, etc. - [ ] Find a way to use pointers and null for FFI but restrict or not allow it in normal Lesma -- [ ] Add basic operators for Enums +- [x] Add basic operators for Enums ## Features - [x] Implement Tuples diff --git a/docs/features/enums.md b/docs/features/enums.md new file mode 100644 index 0000000..5f81c99 --- /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 +red: Color = Color.Red +print(white != red) +``` \ No newline at end of file diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index 6d9f80f..d20d7eb 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -295,18 +295,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 = TypeSymbol(node.name.value, typs) - self.define(typ.name, typ) - - def visit_typedeclaration(self, node): - typ = TypeSymbol(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): diff --git a/tests/io/anon_func.les b/tests/io/anon_func.les index 3c2d9f8..e6f4eb4 100644 --- a/tests/io/anon_func.les +++ b/tests/io/anon_func.les @@ -10,7 +10,7 @@ y = def (x: int, y: int) -> int else return x * y -z: func[] = def () +z: func = def () print(5) From b9f961cb4d84ae352a5252d5f4c486503cd62242 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sat, 26 Jan 2019 11:39:36 +0100 Subject: [PATCH 56/78] More refactoring --- src/lesma/compiler/__init__.py | 45 ---------------------------- src/lesma/compiler/code_generator.py | 11 +++---- src/lesma/compiler/types.py | 6 ++-- src/lesma/lexer.py | 3 +- 4 files changed, 12 insertions(+), 53 deletions(-) diff --git a/src/lesma/compiler/__init__.py b/src/lesma/compiler/__init__.py index 49278b6..2cd8769 100644 --- a/src/lesma/compiler/__init__.py +++ b/src/lesma/compiler/__init__.py @@ -26,22 +26,6 @@ VOID: ir.VoidType(), } -llvm_to_lesma_type = { - "u1": BOOL, - "u8": UINT8, - "u16": UINT16, - "u32": UINT32, - "u64": UINT64, - "u128": UINT128, - "i8": INT8, - "i16": INT16, - "i32": INT32, - "i64": INT, - "i128": INT128, - "double": DOUBLE, - "float": FLOAT, -} - llvm_type_map = { "u1": ir.IntType(1, signed=False), "u8": ir.IntType(8, signed=False), @@ -58,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/code_generator.py b/src/lesma/compiler/code_generator.py index 22011ee..84e7f8a 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -7,7 +7,7 @@ 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, llvm_to_lesma_type, llvm_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 @@ -104,10 +104,10 @@ def externfuncdecl(self, name, node): self.define(name, func, 1) def funcdecl(self, name, node, linkage=None): - func = self.func_decl(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs, linkage) + self.func_decl(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs, linkage) def funcimpl(self, name, node): - func = self.implement_func_body(name) + self.implement_func_body(name) for i, arg in enumerate(self.current_function.args): arg.name = list(node.parameters.keys())[i] @@ -122,7 +122,7 @@ def funcimpl(self, name, node): self.end_function(ret) def funcdef(self, name, node, linkage=None): - func = self.start_function(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs, linkage) + 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] @@ -343,7 +343,8 @@ def visit_incrementassign(self, node): else: self.store(res, var_name) - def visit_typedeclaration(self, node): + @staticmethod + def visit_typedeclaration(node): type_map[node.name] = type_map[node.collection.value] return 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/lexer.py b/src/lesma/lexer.py index e9677aa..95656d4 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -44,7 +44,8 @@ def next_char(self): self.current_char = self.text[self.pos] self.char_type = self.get_type(self.current_char) - def sanitize_text(self, text): + @staticmethod + def sanitize_text(text): if len(text) == 0: error('empty input') elif text[-1] != '\n': From fe8f734a6b506be9bad477fa7b74ffb4d7d361b1 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sat, 26 Jan 2019 19:33:30 +0100 Subject: [PATCH 57/78] Added positive and negative Inf --- src/lesma/compiler/code_generator.py | 3 +++ src/lesma/grammar.py | 3 ++- src/lesma/type_checker.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 84e7f8a..2cd60fb 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -3,6 +3,7 @@ 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 * @@ -730,6 +731,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)) diff --git a/src/lesma/grammar.py b/src/lesma/grammar.py index fa8a1a1..e32da0c 100644 --- a/src/lesma/grammar.py +++ b/src/lesma/grammar.py @@ -83,6 +83,7 @@ TRUE = 'true' FALSE = 'false' NULL = 'null' # TODO +INF = 'inf' # Keywords IF = 'if' @@ -161,7 +162,7 @@ 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' diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index d20d7eb..fb9622e 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -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 From 7a03021d414257b8459febd7978a864bb63d93d3 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sat, 26 Jan 2019 19:42:33 +0100 Subject: [PATCH 58/78] Fixed fp and int operations bug --- src/lesma/compiler/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 6fbd999..64c48e7 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -69,7 +69,7 @@ def binary_op(self, node): if isinstance(left.type, ir.IntType): left = cast_ops(self, left, right.type, node) elif isinstance(right.type, ir.IntType): - left = cast_ops(self, right, left.type, 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): if left.type.is_pointer: From 5f81c7dabed0294fe99a3c7d31bc74d2f29b145c Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sat, 26 Jan 2019 19:43:42 +0100 Subject: [PATCH 59/78] Added tests for inf and vardecl --- tests/io/list.les | 2 +- tests/io/operators.les | 6 +++++- tests/io/operators.output | 4 ++++ tests/io/tuple.les | 2 +- tests/io/varassign.les | 14 ++++++++------ tests/io/{vardecl.io => vardecl.les} | 0 6 files changed, 19 insertions(+), 9 deletions(-) rename tests/io/{vardecl.io => vardecl.les} (100%) diff --git a/tests/io/list.les b/tests/io/list.les index 5eb4d64..0a95dba 100644 --- a/tests/io/list.les +++ b/tests/io/list.les @@ -8,5 +8,5 @@ x: list[double] = [1.5] print(x[0]) x[0] = 5.5 -y = [2.5,3.5] +y = [2.5,3.5, inf] print(y[0]) \ No newline at end of file diff --git a/tests/io/operators.les b/tests/io/operators.les index dc860e5..775ecec 100644 --- a/tests/io/operators.les +++ b/tests/io/operators.les @@ -19,4 +19,8 @@ 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) +print(-inf) +print(inf > 999999999999) +print(-inf < -999999999999) \ No newline at end of file diff --git a/tests/io/operators.output b/tests/io/operators.output index 1aec396..13b3284 100644 --- a/tests/io/operators.output +++ b/tests/io/operators.output @@ -19,4 +19,8 @@ false true true true +true +inf +-inf +true true \ No newline at end of file diff --git a/tests/io/tuple.les b/tests/io/tuple.les index 4a4f304..23d5cbb 100644 --- a/tests/io/tuple.les +++ b/tests/io/tuple.les @@ -2,5 +2,5 @@ x = (1,2,3,4) print(x[0]) y = (1.5,3.5) print(y[1]) -z: tuple[double] = (2.5,3.5) +z: tuple[double] = (2.5,3.5, inf) print(z[0]) \ No newline at end of file diff --git a/tests/io/varassign.les b/tests/io/varassign.les index a3182bc..6284d0d 100644 --- a/tests/io/varassign.les +++ b/tests/io/varassign.les @@ -6,12 +6,14 @@ 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] +a9: bool = true +# a10: str = "Hey there" +a11 = [1,2,3,4] +a12 = [1.5,3.5,9.0] +# a13 = ["Hey", "There"] +# a14 = [true, false, false] +a15: double = inf +a16: double = -inf x: int = 5 y = 7.5 diff --git a/tests/io/vardecl.io b/tests/io/vardecl.les similarity index 100% rename from tests/io/vardecl.io rename to tests/io/vardecl.les From ab3bdd23805742ff7031b27e6d9f8da93feebaf2 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sat, 26 Jan 2019 20:02:07 +0100 Subject: [PATCH 60/78] Updated tests for nan values --- tests/io/operators.les | 4 +++- tests/io/operators.output | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/io/operators.les b/tests/io/operators.les index 775ecec..aae1e68 100644 --- a/tests/io/operators.les +++ b/tests/io/operators.les @@ -23,4 +23,6 @@ print(3 >= 2) print(inf) print(-inf) print(inf > 999999999999) -print(-inf < -999999999999) \ No newline at end of file +print(-inf < -999999999999) +print(inf - inf) +print(0 * inf) \ No newline at end of file diff --git a/tests/io/operators.output b/tests/io/operators.output index 13b3284..f22d597 100644 --- a/tests/io/operators.output +++ b/tests/io/operators.output @@ -23,4 +23,6 @@ true inf -inf true -true \ No newline at end of file +true +nan +nan \ No newline at end of file From 2389f313071b8d08887f7f464bf671f638820579 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sat, 26 Jan 2019 22:14:52 +0100 Subject: [PATCH 61/78] Improved docs to be ready for v0.4 --- docs/TODO.md | 1 + docs/features/enums.md | 2 +- docs/features/keywords.md | 18 ++++++++++++------ docs/features/syntax.md | 3 ++- docs/features/types.md | 39 +++++++++++++++++++++++++++++---------- mkdocs.yml | 1 + tests/io/anon_func.les | 2 +- 7 files changed, 47 insertions(+), 19 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index cb5cce8..9311a2f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -38,5 +38,6 @@ - [ ] Implement string interpolation - [x] Implement Enums - [ ] Implement lambda functions +- [x] Implement inf - [x] Implement defer keyword - [x] Implement fallthrough and change switch's behaviour \ No newline at end of file diff --git a/docs/features/enums.md b/docs/features/enums.md index 5f81c99..53f6d45 100644 --- a/docs/features/enums.md +++ b/docs/features/enums.md @@ -6,7 +6,7 @@ enum Color Red White -white: Color = Color.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 d77ce6f..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 | type | extern | operator | -| fallthrough | defer | self +| 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/syntax.md b/docs/features/syntax.md index 60a4424..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,6 +29,7 @@ struct thing z: double class Example + x: int new(x: int) self.x = x ``` diff --git a/docs/features/types.md b/docs/features/types.md index ac11ea1..9c494e5 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 type, 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,27 @@ 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) +``` + ## Type Operations ### Is diff --git a/mkdocs.yml b/mkdocs.yml index f728615..b15261e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,6 +17,7 @@ nav: - 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 - TODO: TODO.md diff --git a/tests/io/anon_func.les b/tests/io/anon_func.les index e6f4eb4..c0dea54 100644 --- a/tests/io/anon_func.les +++ b/tests/io/anon_func.les @@ -10,7 +10,7 @@ y = def (x: int, y: int) -> int else return x * y -z: func = def () +z: func = def() print(5) From 6f2a02d70b0d280e501556b5d651297d2d9b90ad Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 27 Jan 2019 09:56:29 +0100 Subject: [PATCH 62/78] Fixed array increment operator, added tests for it --- src/lesma/compiler/code_generator.py | 5 +++-- tests/io/list.les | 2 ++ tests/io/list.output | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 2cd60fb..e93e921 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -316,8 +316,9 @@ def visit_incrementassign(self, node): 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 @@ -340,7 +341,7 @@ 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) diff --git a/tests/io/list.les b/tests/io/list.les index 0a95dba..7557567 100644 --- a/tests/io/list.les +++ b/tests/io/list.les @@ -3,6 +3,8 @@ print(things[0]) print(things[1*1+1]) things[2] = 1002 print(things[2]) +things[2]++ +print(things[2]) x: list[double] = [1.5] print(x[0]) diff --git a/tests/io/list.output b/tests/io/list.output index 785f6e3..9f74aa2 100644 --- a/tests/io/list.output +++ b/tests/io/list.output @@ -1,5 +1,6 @@ 1 3 1002 +1003 1.5 2.5 \ No newline at end of file From 585210b7fce3dc606f2936f176ca15e6b0b4f851 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 27 Jan 2019 10:08:07 +0100 Subject: [PATCH 63/78] Improved docs + tests --- docs/TODO.md | 2 +- tests/io/varassign.les | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 9311a2f..142d852 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -23,7 +23,7 @@ - [x] Allow default values for struct and class fields - [ ] Use dataclasses and static typing as much as possible in source code - [x] Allow any type for lists/tuples (currently only int) -- [ ] String/Lists are currently unsupported on: type declaration with assignment, input function, operators, etc. +- [ ] String/Lists are currently unsupported on: input function, operators, etc. - [ ] Find a way to use pointers and null for FFI but restrict or not allow it in normal Lesma - [x] Add basic operators for Enums diff --git a/tests/io/varassign.les b/tests/io/varassign.les index 6284d0d..64e756a 100644 --- a/tests/io/varassign.les +++ b/tests/io/varassign.les @@ -7,15 +7,32 @@ a6: int128 = 6 a7: double = 6.5 a8: float = 7.69 a9: bool = true -# a10: str = "Hey there" -a11 = [1,2,3,4] -a12 = [1.5,3.5,9.0] -# a13 = ["Hey", "There"] -# a14 = [true, false, false] +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 -x: int = 5 +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) From 93ab7daf7669ab49ac55777b5877a4f24e9dcda3 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 27 Jan 2019 10:26:16 +0100 Subject: [PATCH 64/78] Fixed collection declaration --- src/lesma/compiler/code_generator.py | 9 +++++++-- src/lesma/type_checker.py | 6 +++++- src/lesma/visitor.py | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index e93e921..4e68135 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -360,8 +360,13 @@ def visit_vardecl(self, node): func_parameters = self.get_args(node.type.func_params) func_ty = ir.FunctionType(func_ret_type, func_parameters, None).as_pointer() typ = func_ty - - self.alloc_and_define(node.value.value, typ) + 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): diff --git a/src/lesma/type_checker.py b/src/lesma/type_checker.py index fb9622e..4bce4b2 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -488,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): diff --git a/src/lesma/visitor.py b/src/lesma/visitor.py index 4ca1b83..c295d87 100644 --- a/src/lesma/visitor.py +++ b/src/lesma/visitor.py @@ -99,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): From 61f46691acc1bec6730679e4fef4aa95b4477f05 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 27 Jan 2019 10:29:21 +0100 Subject: [PATCH 65/78] Added list var decl --- tests/io/vardecl.les | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/io/vardecl.les b/tests/io/vardecl.les index 130930b..906699a 100644 --- a/tests/io/vardecl.les +++ b/tests/io/vardecl.les @@ -7,4 +7,6 @@ a6: int128 a8: float a7: double a10: bool -a11: str \ No newline at end of file +a11: str +a12: list[int] +a13: tuple[float] \ No newline at end of file From 5a66f2942c3d56901003497bee52c9b16603fab4 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sun, 27 Jan 2019 13:12:27 +0100 Subject: [PATCH 66/78] Fixed user defined type as func return --- 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 4e68135..33941dc 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -937,7 +937,7 @@ def start_function(self, name, return_type, parameters, parameter_defaults=None, self.block_stack.append(self.builder.block) self.new_scope() self.defer_stack.append([]) - ret_type = type_map[return_type.value] + 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 From 560f2ace01fe9f6f6645f7f9a1e2891bd766af8b Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sun, 27 Jan 2019 15:01:42 +0100 Subject: [PATCH 67/78] User defined types can now be used as parameters/return types for function, added tests for it --- src/lesma/compiler/code_generator.py | 4 +++- tests/io/class.les | 10 +++++++++- tests/io/class.output | 4 +++- tests/io/enum.les | 10 +++++++++- tests/io/enum.output | 3 ++- tests/io/struct.les | 10 +++++++++- tests/io/struct.output | 4 +++- 7 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 33941dc..7042f6b 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -898,8 +898,10 @@ def get_args(self, parameters): else: if param.value in type_map: args.append(type_map[param.value]) - elif self.search_scopes(param.value) is not None: + 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)) diff --git a/tests/io/class.les b/tests/io/class.les index 8893d68..b94db35 100644 --- a/tests/io/class.les +++ b/tests/io/class.les @@ -29,4 +29,12 @@ x.a_thing = 3 print(x.a_thing) x.another() print(x.just_demo()) -print(x.b_thing) \ No newline at end of file +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) \ No newline at end of file diff --git a/tests/io/class.output b/tests/io/class.output index 56bb226..095caa8 100644 --- a/tests/io/class.output +++ b/tests/io/class.output @@ -5,4 +5,6 @@ 3 7 5 -3.14 \ No newline at end of file +3.14 +3.14 +99 \ No newline at end of file diff --git a/tests/io/enum.les b/tests/io/enum.les index aa5c830..d3b189f 100644 --- a/tests/io/enum.les +++ b/tests/io/enum.les @@ -6,4 +6,12 @@ x: Color = Color.White y: Color = Color.Red print(x == Color.White) print(x == Color.Red) -print(x == y) \ No newline at end of file +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) \ No newline at end of file diff --git a/tests/io/enum.output b/tests/io/enum.output index 1246878..923e068 100644 --- a/tests/io/enum.output +++ b/tests/io/enum.output @@ -1,3 +1,4 @@ true false -false \ No newline at end of file +false +true \ No newline at end of file diff --git a/tests/io/struct.les b/tests/io/struct.les index 9f8cf09..409bb2e 100644 --- a/tests/io/struct.les +++ b/tests/io/struct.les @@ -10,4 +10,12 @@ print(anotherCir.x) print(cir.radius + cir.x) cir.x = 20 print(cir.x) -print(cir.y) \ No newline at end of file +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) \ No newline at end of file diff --git a/tests/io/struct.output b/tests/io/struct.output index d848eb0..5ca1439 100644 --- a/tests/io/struct.output +++ b/tests/io/struct.output @@ -1,4 +1,6 @@ 3 7 20 -99 \ No newline at end of file +99 +20 +-1 \ No newline at end of file From dcca80d97055761f6965ee3a4221b689631e0579 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sun, 27 Jan 2019 15:18:11 +0100 Subject: [PATCH 68/78] User defined types are now capable of operator overflow --- docs/TODO.md | 4 ++-- docs/features/types.md | 12 ++++++++++++ src/lesma/compiler/operations.py | 10 +++++----- src/lesma/parser.py | 3 ++- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 142d852..39c3d19 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -3,8 +3,8 @@ ## Fixes - [x] Fix type declaration for lists and tuples - [x] 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 +- [x] Fix not being able to return user-defined structs and classes +- [x] Fix not being able to overload operators on user-defined structs and classes - [ ] Fix base unary operators being applied before user defined ones - [x] Fix structs and classes types not being implicitly defined on assignment diff --git a/docs/features/types.md b/docs/features/types.md index 9c494e5..34bd83a 100644 --- a/docs/features/types.md +++ b/docs/features/types.md @@ -132,6 +132,18 @@ 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/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 64c48e7..03398c5 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -21,15 +21,15 @@ def hasFunction(self, func_name): def userdef_unary_str(op, expr): - return OPERATOR + '.' + op + '.' + str(expr.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) + 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(self, node): diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 792845d..6da3e8b 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -226,7 +226,8 @@ def function_declaration(self): 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) From 2f00b8e9610d8debc0c4fcc2d5e55e26332a1ebe Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sun, 27 Jan 2019 19:14:26 +0100 Subject: [PATCH 69/78] Classe, struct and enum names are regarded as types and can now use the 'is' operator --- src/lesma/compiler/code_generator.py | 5 ++--- src/lesma/parser.py | 6 +++++- src/lesma/type_checker.py | 4 ++-- tests/io/class.les | 3 ++- tests/io/class.output | 3 ++- tests/io/enum.les | 3 ++- tests/io/enum.output | 1 + tests/io/struct.les | 3 ++- tests/io/struct.output | 3 ++- 9 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index 7042f6b..cf1ab0d 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -368,9 +368,8 @@ def visit_vardecl(self, node): 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') diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 6da3e8b..96ebced 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -702,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) @@ -768,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 4bce4b2..59d3486 100644 --- a/src/lesma/type_checker.py +++ b/src/lesma/type_checker.py @@ -250,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: diff --git a/tests/io/class.les b/tests/io/class.les index b94db35..8105175 100644 --- a/tests/io/class.les +++ b/tests/io/class.les @@ -37,4 +37,5 @@ def giveMeAThing(some: Thing) -> Thing z = giveMeAThing(x) print(z.b_thing) -print(z.a_thing) \ No newline at end of file +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 index 095caa8..f5099a4 100644 --- a/tests/io/class.output +++ b/tests/io/class.output @@ -7,4 +7,5 @@ 5 3.14 3.14 -99 \ No newline at end of file +99 +true \ No newline at end of file diff --git a/tests/io/enum.les b/tests/io/enum.les index d3b189f..820ad8d 100644 --- a/tests/io/enum.les +++ b/tests/io/enum.les @@ -14,4 +14,5 @@ def giveMeSomeColor(some: Color) -> Color cir: Color = Color.Red xy = giveMeSomeColor(cir) -print(xy == Color.Red) \ No newline at end of file +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 index 923e068..f3956f9 100644 --- a/tests/io/enum.output +++ b/tests/io/enum.output @@ -1,4 +1,5 @@ true false false +true true \ No newline at end of file diff --git a/tests/io/struct.les b/tests/io/struct.les index 409bb2e..cae9861 100644 --- a/tests/io/struct.les +++ b/tests/io/struct.les @@ -18,4 +18,5 @@ def giveMeACircle(some: Circle) -> Circle a_circle = giveMeACircle(cir) print(a_circle.x) -print(a_circle.y) \ No newline at end of file +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 5ca1439..cf87bdb 100644 --- a/tests/io/struct.output +++ b/tests/io/struct.output @@ -3,4 +3,5 @@ 20 99 20 --1 \ No newline at end of file +-1 +true \ No newline at end of file From be2d0932feeb36362c675f169bd11b2402a231af Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sun, 27 Jan 2019 19:31:25 +0100 Subject: [PATCH 70/78] Updated TODO --- docs/TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TODO.md b/docs/TODO.md index 39c3d19..1fbf5d8 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -16,7 +16,7 @@ - [ ] 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 +- [x] Fix array types not working on empty lists - [x] 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 From 286fb80c56c53cb0415779755639364ab1713cb0 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Sun, 27 Jan 2019 20:44:51 +0100 Subject: [PATCH 71/78] Fixed bin, octal and hex numbers, added tests for it --- src/lesma/lexer.py | 6 +----- tests/io/operators.les | 5 ++++- tests/io/operators.output | 5 ++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lesma/lexer.py b/src/lesma/lexer.py index 95656d4..f7afa75 100644 --- a/src/lesma/lexer.py +++ b/src/lesma/lexer.py @@ -260,7 +260,7 @@ def get_next_token(self): self.current_char in ('a', 'b', 'c', 'd', 'e', 'f', 'x', 'o'): self.word += self.current_char if self.char_type == ALPHANUMERIC: - if self.current_char in ('b', 'x', 'o') and self.word == '0': + 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': @@ -268,15 +268,11 @@ def get_next_token(self): elif self.current_char == 'o': base = 8 - self.next_char() self.word = "" elif not (base == 16 and self.current_char in ('a', 'b', 'c', 'd', 'e', 'f')): - print(self.word, self.current_char) - else: error("Unexpected number parsing") self.next_char() - value = self.reset_word() if '.' in value: value = Decimal(value) diff --git a/tests/io/operators.les b/tests/io/operators.les index aae1e68..42d5397 100644 --- a/tests/io/operators.les +++ b/tests/io/operators.les @@ -25,4 +25,7 @@ print(-inf) print(inf > 999999999999) print(-inf < -999999999999) print(inf - inf) -print(0 * inf) \ No newline at end of file +print(0 * inf) +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 f22d597..80bde4e 100644 --- a/tests/io/operators.output +++ b/tests/io/operators.output @@ -25,4 +25,7 @@ inf true true nan -nan \ No newline at end of file +nan +10 +10 +10 \ No newline at end of file From 81d4b0dd3ef45b9956c0cdf5419d7b916d9e03f8 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Sun, 27 Jan 2019 23:03:46 +0100 Subject: [PATCH 72/78] More refactoring --- src/lesma/compiler/operations.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/lesma/compiler/operations.py b/src/lesma/compiler/operations.py index 03398c5..c9b9b65 100644 --- a/src/lesma/compiler/operations.py +++ b/src/lesma/compiler/operations.py @@ -10,23 +10,23 @@ 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(self, func_name): for func in self.module.functions: if func.name == func_name: return True + return False + def userdef_unary_str(op, expr): + # 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): + # 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 @@ -49,6 +49,13 @@ def unary_op(self, node): elif op == BINARY_ONES_COMPLIMENT: if isinstance(expr.type, ir.IntType): return self.builder.not_(expr) + else: + error('file={} line={}: Unknown operator {} for {}'.format( + self.file_name, + node.line_num, + op, + expr + )) def binary_op(self, node): @@ -72,10 +79,6 @@ def binary_op(self, 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): - if left.type.is_pointer: - left = self.builder.load(left) - if right.type.is_pointer: - right = self.builder.load(right) return enum_ops(self, op, left, right, node) else: error('file={} line={}: Unknown operator {} for {} and {}'.format( @@ -103,6 +106,11 @@ def is_ops(self, 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) @@ -255,7 +263,7 @@ def cast_ops(self, left, right, node): 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( self.file_name, node.line_num, From 337035f8f7b6110e6e0f9ab3036940a5654467db Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Mon, 28 Jan 2019 15:47:34 +0100 Subject: [PATCH 73/78] Pytest now fixed for windows, removed inf and nan from tests for cross-platform compatibility --- README.md | 15 +++++++++------ docs/TODO.md | 2 ++ tests/io/operators.les | 4 ---- tests/io/operators.output | 4 ---- tests/test_all.py | 12 ++++++------ 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 16732a0..15d9d4f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ___ [![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, 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. +**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. @@ -25,7 +25,10 @@ Currently an early Work in Progress, and **many thanks** to [Ayehavgunne](https: - Lua ## 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/docs/TODO.md b/docs/TODO.md index 1fbf5d8..1298d91 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -7,6 +7,8 @@ - [x] Fix not being able to overload operators on user-defined structs and classes - [ ] Fix base unary operators being applied before user defined ones - [x] Fix structs and classes types not being implicitly defined on assignment +- [x] Fix pytest for windows +- [ ] Fix unicode on windows bash ## Improvements - [ ] Improve warning messages diff --git a/tests/io/operators.les b/tests/io/operators.les index 42d5397..ac1eae1 100644 --- a/tests/io/operators.les +++ b/tests/io/operators.les @@ -20,12 +20,8 @@ print(5 < 9) print(5 <= 9) print(3 > 2) print(3 >= 2) -print(inf) -print(-inf) print(inf > 999999999999) print(-inf < -999999999999) -print(inf - inf) -print(0 * inf) 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 80bde4e..c8045d6 100644 --- a/tests/io/operators.output +++ b/tests/io/operators.output @@ -20,12 +20,8 @@ true true true true -inf --inf true true -nan -nan 10 10 10 \ No newline at end of file diff --git a/tests/test_all.py b/tests/test_all.py index b739489..63c6545 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -1,4 +1,5 @@ import os +import sys import pytest from subprocess import Popen, PIPE @@ -9,7 +10,6 @@ def get_tests(): for file in os.listdir(os.path.join(path, "io")): if file.endswith(".les"): tests.append(os.path.basename(file).split('.')[0]) - return tests @@ -17,18 +17,18 @@ def get_tests(): @pytest.mark.parametrize("test_name", get_tests()) def test_base(test_name): path = os.path.join(os.path.dirname(__file__), os.pardir) - proc = Popen(["python3", os.path.join(path, "src", "les.py"), + 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) + 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(os.path.join(path, "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 From 4af2fae9ca183e9f4915e5996802c1deb7bd5382 Mon Sep 17 00:00:00 2001 From: Hassan Alin Ali Date: Mon, 28 Jan 2019 23:19:41 +0100 Subject: [PATCH 74/78] Improved sample code --- docs/examples.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index cf01ae2..282d145 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -27,10 +27,10 @@ number+=5 # Operating Assignment 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]) From aee6167680a8f5cdec103c1d3f70550615718805 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 30 Jan 2019 20:49:53 +0100 Subject: [PATCH 75/78] Modified TODO for 0.4.0 --- docs/TODO.md | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 1298d91..f5ccb51 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,45 +1,25 @@ # TODO ## Fixes -- [x] Fix type declaration for lists and tuples -- [x] Fix input function -- [x] Fix not being able to return user-defined structs and classes -- [x] Fix not being able to overload operators on user-defined structs and classes - [ ] Fix base unary operators being applied before user defined ones -- [x] Fix structs and classes types not being implicitly defined on assignment -- [x] Fix pytest for windows -- [ ] Fix unicode on windows bash +- [ ] Fix unicode on windows ## Improvements - [ ] Improve warning messages - [ ] Add indentation related errors -- [x] Add docs for as and is -- [x] Add docs for classes and structs - [ ] Add docs for mixed arithmetic - [ ] Remove clang as a dependency - [ ] Move error messages from source files to typechecker -- [x] Fix array types not working on empty lists -- [x] 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 -- [x] Allow default values for struct and class fields - [ ] Use dataclasses and static typing as much as possible in source code -- [x] Allow any type for lists/tuples (currently only int) - [ ] String/Lists are currently unsupported on: input function, operators, etc. -- [ ] Find a way to use pointers and null for FFI but restrict or not allow it in normal Lesma -- [x] Add basic operators for Enums +- [ ] Find a way to use pointers and null for FFI but restrict or disallow it in normal Lesma ## Features -- [x] Implement Tuples -- [x] Implement Classes - [ ] Implement Class inheritance - [ ] Implement Dictionary - [ ] Implement 'in' as a boolean result -- [x] Implement anonymous functions - [ ] Implement Closure - [ ] Implement string interpolation -- [x] Implement Enums -- [ ] Implement lambda functions -- [x] Implement inf -- [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 From d0c171c86612f77cbae072296883cbd98e26bc21 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 30 Jan 2019 20:53:04 +0100 Subject: [PATCH 76/78] Updated 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 15d9d4f..184ab0e 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.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) 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/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index cf1ab0d..b7a6c2b 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -1100,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) From 3f56ee98bf45175e8b01d51bc750a1f26159c736 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 30 Jan 2019 20:57:42 +0100 Subject: [PATCH 77/78] Updated examples to show some of the new features --- docs/examples.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/examples.md b/docs/examples.md index 282d145..8b24d37 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -25,6 +25,8 @@ 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, mutable @@ -183,12 +185,15 @@ 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 From 7a9938a5b5caa8b92511a77d8f0a6442a4e6d3d0 Mon Sep 17 00:00:00 2001 From: Alin Ali Hassan Date: Wed, 30 Jan 2019 21:12:07 +0100 Subject: [PATCH 78/78] Refactoring --- src/lesma/compiler/code_generator.py | 6 +++--- src/lesma/parser.py | 6 +++--- tests/test_all.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lesma/compiler/code_generator.py b/src/lesma/compiler/code_generator.py index b7a6c2b..1d41192 100644 --- a/src/lesma/compiler/code_generator.py +++ b/src/lesma/compiler/code_generator.py @@ -637,9 +637,9 @@ def visit_dotaccess(self, node): 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 - else: - 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)) + + 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): right = self.visit(node.right) diff --git a/src/lesma/parser.py b/src/lesma/parser.py index 96ebced..364ba36 100644 --- a/src/lesma/parser.py +++ b/src/lesma/parser.py @@ -723,9 +723,9 @@ def factor(self): if preview.value == DOT: if self.preview(2).type == NAME and self.preview(3).value == LPAREN: return self.property_or_method(self.next_token()) - else: - self.next_token() - return self.dot_access(token) + + self.next_token() + return self.dot_access(token) elif token.value in (PLUS, MINUS, BINARY_ONES_COMPLIMENT): self.next_token() return UnaryOp(token.value, self.factor(), self.line_num) diff --git a/tests/test_all.py b/tests/test_all.py index 63c6545..356e471 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -7,9 +7,9 @@ def get_tests(): tests = [] path = os.path.dirname(__file__) - for file in os.listdir(os.path.join(path, "io")): - if file.endswith(".les"): - tests.append(os.path.basename(file).split('.')[0]) + for unittest in os.listdir(os.path.join(path, "io")): + if unittest.endswith(".les"): + tests.append(os.path.basename(unittest).split('.')[0]) return tests