Skip to content
This repository was archived by the owner on Sep 1, 2022. It is now read-only.

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
alinalihassan committed Dec 29, 2018
2 parents 283faeb + 8b60c21 commit 4ce766a
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.1.2-brightgreen.svg)](https://github.com/hassanalinali/Lesma/blob/master/LICENSE.md)
[![Version](https://img.shields.io/badge/version-0.2.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)

Expand Down
1 change: 1 addition & 0 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- [ ] Add docs for as and is
- [ ] Remove clang as a dependency
- [x] Change from anonymous structs to identified (to allow proper struct types)
- [ ] Move error messages from source files to typechecker

## Features
- [ ] Implement Null (maybe)
Expand Down
16 changes: 15 additions & 1 deletion docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,27 @@ def operator - (x: int, y:int) -> int # Two parameters overloads binary operati
def operator - (x: int) -> int # One parameters overloads binary operations
return 0 - x + 1

# Extern functions (FFI)
def extern abs(x: int32) -> int32 # from C's stdlib

print(abs(-5 as int32)) # ints are int64 by default in Lesma, they're int32 in C

# or you can just let Lesma convert between "compatible" types such as numbers
print(abs(-5))

# Named parameters and defaults
def optional_params(x: int, y: int32 = 5, z: double = 9) -> int
# Lesma takes care of casting the return type between "compatible" types
return x + z

# Enums
enum Colors
GREEN
RED
BLUE
YELLOW


# Structs
struct Circle
radius: int
x: int
Expand All @@ -173,6 +186,7 @@ cir: Circle = {radius=5, x=2, y=4}

print(cir.radius)

# Classes
class Vehicle
# Constructor
new(year: int, color: str)
Expand Down
2 changes: 1 addition & 1 deletion src/les.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def _compile(arg_list):


if __name__ == "__main__":
args = docopt(__doc__, version='0.1.2')
args = docopt(__doc__, version='0.2.0')

if args['compile']:
_compile(args)
Expand Down
8 changes: 8 additions & 0 deletions src/lesma/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def __init__(self, name, return_type, parameters, body, line_num, parameter_defa
# __repr__ = __str__


class ExternFuncDecl(AST):
def __init__(self, name, return_type, parameters, line_num, varargs=None):
self.name = name
self.return_type = return_type
self.parameters = parameters
self.varargs = varargs
self.line_num = line_num

class AnonymousFunc(AST):
def __init__(self, return_type, parameters, body, line_num, parameter_defaults=None, varargs=None):
self.return_type = return_type
Expand Down
52 changes: 47 additions & 5 deletions src/lesma/compiler/code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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.type_checker import types_compatible
import lesma.compiler.llvmlite_custom
from lesma.visitor import NodeVisitor
from lesma.utils import *
Expand Down Expand Up @@ -73,6 +74,22 @@ def visit_anonymousfunc(self, node):
def visit_funcdecl(self, node):
self.funcdecl(node.name, node)

def visit_externfuncdecl(self, node):
self.externfuncdecl(node.name, node)

def externfuncdecl(self, name, node):
return_type = node.return_type
parameters = node.parameters
varargs = node.varargs
ret_type = type_map[return_type.value]
args = [type_map[param.value] for param in parameters.values()]
func_type = ir.FunctionType(ret_type, args, varargs)
func_type.parameters = parameters
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)
self.define(name, func, 1)

def funcdecl(self, name, node):
self.start_function(name, node.return_type, node.parameters, node.parameter_defaults, node.varargs)
for i, arg in enumerate(self.current_function.args):
Expand All @@ -86,6 +103,7 @@ def funcdecl(self, name, node):
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)
self.store(val, RET_VAR)
self.branch(self.exit_blocks[-1])
return True
Expand All @@ -98,26 +116,49 @@ def visit_funccall(self, node):
name = name.name
else:
name = node.name

if len(node.arguments) < len(func_type.args):
args = []
args_supplied = []
for x, arg in enumerate(func_type.arg_order):
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 in node.named_arguments:
args.append(self.visit(node.named_arguments[arg]))
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.visit(func_type.parameter_defaults[arg]))

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 = [self.visit(arg) for arg in node.arguments]
args = []
for i, arg in enumerate(node.arguments):
args.append(self.comp_cast(self.visit(arg), func_type.args[i], node))
return self.call(name, args)

def comp_cast(self, arg, typ, node):
if types_compatible(str(arg.type), typ):
return cast_ops(self, arg, typ, node)

return arg

def visit_compound(self, node):
ret = None
for child in node.children:
Expand Down Expand Up @@ -624,6 +665,7 @@ def start_function(self, name, return_type, parameters, parameter_defaults=None,
ret_type = type_map[return_type.value]
args = [type_map[param.value] for param in parameters.values()]
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:
Expand Down
5 changes: 4 additions & 1 deletion src/lesma/compiler/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ def int_ops(compiler, op, left, right, node):
elif op == BINARY_RIGHT_SHIFT:
return compiler.builder.lshr(left, right)
elif op in (EQUALS, NOT_EQUALS, LESS_THAN, LESS_THAN_OR_EQUAL_TO, GREATER_THAN, GREATER_THAN_OR_EQUAL_TO):
cmp_res = compiler.builder.icmp_signed(op, left, right, 'cmptmp')
if left.type.signed:
cmp_res = compiler.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')
else:
raise SyntaxError('Unknown binary operator', node.op)
Expand Down
1 change: 1 addition & 0 deletions src/lesma/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
CASE = 'case'
DEFAULT = 'default'
OPERATOR = 'operator'
EXTERN = 'extern'
DEF = 'def'
CONST = 'const'
NEW = 'new' # TODO
Expand Down
14 changes: 12 additions & 2 deletions src/lesma/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from lesma.ast import *
from lesma.grammar import *
from lesma.compiler.__init__ import type_map

from lesma.utils import error

class Parser(object):
def __init__(self, lexer):
Expand Down Expand Up @@ -104,13 +104,18 @@ def alias_declaration(self):

def function_declaration(self):
op_func = False
extern_func = False
self.eat_value(DEF)
if self.current_token.value == LPAREN:
name = ANON
elif self.current_token.value == OPERATOR:
self.eat_value(OPERATOR)
op_func = True
name = self.next_token()
elif self.current_token.value == EXTERN:
self.eat_value(EXTERN)
extern_func = True
name = self.next_token()
else:
name = self.next_token()
self.eat_value(LPAREN)
Expand All @@ -129,6 +134,8 @@ def function_declaration(self):
params[param_name] = param_type
if self.current_token.value != RPAREN:
if self.current_token.value == ASSIGN:
if extern_func:
error("Extern functions cannot have defaults")
self.eat_value(ASSIGN)
param_defaults[param_name] = self.expr()
if self.current_token.value == ELLIPSIS:
Expand All @@ -153,6 +160,9 @@ def function_declaration(self):
else:
return_type = self.type_spec()

if extern_func:
return ExternFuncDecl(name.value, return_type, params, self.line_num, vararg)

self.eat_type(NEWLINE)
self.indent_level += 1
stmts = self.compound_statement()
Expand All @@ -161,7 +171,7 @@ def function_declaration(self):
return AnonymousFunc(return_type, params, stmts, self.line_num, param_defaults, vararg)
if op_func:
if len(params) not in (1, 2): # TODO: move this to type checker
raise SyntaxError("Operators can either be unary or binary, and the number of parameters do not match")
error("Operators can either be unary or binary, and the number of parameters do not match")

name.value = 'operator' + '.' + name.value
for param in params:
Expand Down
38 changes: 35 additions & 3 deletions src/lesma/type_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,38 @@ def visit_aliasdeclaration(self, node):
typ = AliasSymbol(node.name, node.collection.value)
self.define(typ.name, typ)

def visit_externfuncdecl(self, node):
func_name = node.name
func_type = self.search_scopes(node.return_type.value)
if func_type and func_type.name == FUNC:
func_type.return_type = self.visit(node.return_type.func_ret_type)
self.define(func_name, FuncSymbol(func_name, func_type, node.parameters, None))
self.new_scope()
if node.varargs:
varargs_type = self.search_scopes(LIST)
varargs_type.type = node.varargs[1].value
varargs = CollectionSymbol(node.varargs[0], varargs_type, self.search_scopes(node.varargs[1].value))
varargs.val_assigned = True
self.define(varargs.name, varargs)
for k, v in node.parameters.items():
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):
var_type.accessed = True
if var_type.type is self.search_scopes(FUNC):
sym = FuncSymbol(k, var_type.type.return_type, None, None)
else:
raise NotImplementedError
else:
sym = VarSymbol(k, var_type)
sym.val_assigned = True
self.define(sym.name, sym)

func_symbol = FuncSymbol(func_name, func_type, node.parameters, None)
self.define(func_name, func_symbol, 1)
self.drop_top_scope()


def visit_funcdecl(self, node):
func_name = node.name
Expand Down Expand Up @@ -336,7 +368,7 @@ def visit_funcdecl(self, node):
self.return_flag = False
for ret_type in return_types:
infered_type = self.infer_type(ret_type)
if infered_type is not func_type:
if infered_type is not func_type and not types_compatible(infered_type, func_type):
error('file={} line={}: The actual return type does not match the declared return type: {}'.format(self.file_name, node.line_num, func_name))
elif func_type is not None:
error('file={} line={}: No return value was specified for function: {}'.format(self.file_name, node.line_num, func_name))
Expand Down Expand Up @@ -371,15 +403,15 @@ def visit_funccall(self, node):
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:
if not types_compatible(var, param_ss) and (param_ss != self.search_scopes(ANY) and param.value != var.name and param.value != var.type.name):
raise TypeError # TODO: Make this an actual error
else:
func_param_keys = list(func.parameters.keys())
if func_param_keys[x] not in node.named_arguments.keys() and func_param_keys[x] not in func.parameter_defaults.keys():
error('file={} line={}: Missing arguments to function: {}'.format(self.file_name, node.line_num, repr(func_name)))
else:
if func_param_keys[x] in node.named_arguments.keys():
if param.value != self.visit(node.named_arguments[func_param_keys[x]]).name:
if not types_compatible(param.value, self.visit(node.named_arguments[func_param_keys[x]]).name):
raise TypeError
if func is None:
error('file={} line={}: Name Error: {}'.format(self.file_name, node.line_num, repr(func_name)))
Expand Down

0 comments on commit 4ce766a

Please sign in to comment.