From 9d3c31f0c9f673f7a918545e794b69cff29fa688 Mon Sep 17 00:00:00 2001 From: NICUP14 Date: Mon, 2 Sep 2024 14:07:43 +0200 Subject: [PATCH] Fixed fun lvalue refs; Added implicit refs; Updated README; Added mlpx tool. --- BUG.md | 2 + Makefile | 2 +- README.md | 3 + TODO.md | 48 ++++--- include/stdlib/builtin/for.ml | 6 +- include/stdlib/macro.ml | 15 +- include/stdlib/string.ml | 36 +++-- mlpx | 250 ++++++++++++++++++++++++++++++++++ samples/calc/Makefile | 6 +- samples/calc/in.txt | 1 + samples/calc/src/def.ml | 18 +++ samples/calc/src/lex.ml | 71 ++++++++++ samples/calc/src/main.ml | 94 +------------ samples/task-mgr/src/task.ml | 2 +- src/Def.py | 14 +- src/Parser.py | 146 ++++++++++++-------- src/backend/c/CWalker.py | 3 +- tests/test/src/main.ml | 6 +- 18 files changed, 513 insertions(+), 210 deletions(-) create mode 100644 mlpx create mode 100644 samples/calc/in.txt create mode 100644 samples/calc/src/def.ml create mode 100644 samples/calc/src/lex.ml diff --git a/BUG.md b/BUG.md index c6907a2..0eb25c2 100644 --- a/BUG.md +++ b/BUG.md @@ -2,6 +2,8 @@ Solved: 19/23 +- [ ] `Parser.ref` does not implicit cast-to-ref (add `try_cast_ref`) +- [ ] Functions like `input` are predeferred. - [X] Using an iterator `i` in a for loop does not compile correctly. - [X] Nested UFCS expressions (s.equals("Hello".str)) - [X] The type of an overloaded function with a diff type than the first takes the type of the first overloaded function diff --git a/Makefile b/Makefile index eac2e1e..58d283f 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ CFLAGS += -g ML = python ../../src/Main.py MLLIB ?= "../../include" MLFLAGS ?= -MLFLAGS += -C -c +MLFLAGS += -C # Recipes default: clean def diff --git a/README.md b/README.md index 6356019..feb5e99 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The language is designed to closely **match c features** along with some **zero- ## Goodies +* [RAII](QUICKSTART.md#raii) * [Builtins](QUICKSTART.md#builtins) * [Booleans](QUICKSTART.md#primitive-types) * [References](QUICKSTART.md#reference-type) @@ -46,6 +47,8 @@ The language is designed to closely **match c features** along with some **zero- * [Imports](QUICKSTART.md#import-statements) * [Namespaces](QUICKSTART.md#namespace-statements) * [Hygienic macros](QUICKSTART.md#macros) +* [For-each loops](QUICKSTART.md#for-loops) +* Generic functions * [Function overloading](QUICKSTART.md#function-overloading) * [Uniform function call syntax (UFCS)](QUICKSTART.md#uniform-function-call-syntax-ufcs) * [Multi-line statements](QUICKSTART.md#multi-line-statements) diff --git a/TODO.md b/TODO.md index 49b11c2..1e40b52 100644 --- a/TODO.md +++ b/TODO.md @@ -1,39 +1,43 @@ # TODO -Solved: 20/32 +Solved: 28/40 -- [ ] Fix defers and variable assignments for control sturctures (`for`, ...) -- [X] Fix references causing errors in c backend (`&fun(...)`) -- [ ] Suggestive errors for non-existent members -- [X] Separate macro (`delimit(" " , args)`) -- [ ] Function pointer (`&my_fun`) -- [ ] Add for-loop & RAII documentation to QUICKSTART. +- [ ] Add roadmap to README. +- [ ] Add generic functions to QUICKSTART. +- [ ] Document `mlpx` in `MLPX.md` and add it to README. +- [ ] Create `inject_ref` to replace `inject_copy` for `Parser._fun_call`. +- [X] Fix defers and variable assignments for control sturctures (`for`, ...). +- [X] Fix references causing errors in c backend (`&fun(...)`). +- [ ] Suggestive errors for non-existent members. +- [X] Separate macro (`delimit(" " , args)`). +- [ ] Function pointer (`&my_fun`). +- [X] Add for-loop & RAII documentation to QUICKSTART. - [ ] Improve safety and memory safety! -- [ ] Simplify compiling process (`compile.py`) -- [ ] Add block section to README -- [X] Add namespace section to README -- [X] Replace "minimal" by "easy to learn", "gentle curve" in README -- [X] Add section at the start of README about lang-specific features -- [ ] Add multi-lever pointers/refs. (vtype: List[ckind]) +- [X] Simplify compiling process (`mlpx.py`) +- [ ] Add block section to README. +- [X] Add namespace section to README. +- [X] Replace "minimal" by "easy to learn", "gentle curve" in README. +- [X] Add section at the start of README about lang-specific features. +- [ ] Add multi-lever pointers/refs (vtype: List[ckind]). - [ ] Add const, unsigned and floating point types. - [X] `end` no longed needed at bottom of module. - [ ] Global arrays. -- [X] C generator backend (CWalker.py) -- [X] ML generator backend (GenStr.py -> MLWalker.py) -- [ ] ASM generator backend (Gen.py -> GenASM.py) +- [X] C generator backend (CWalker.py). +- [X] ML generator backend (GenStr.py -> MLWalker.py). +- [ ] ASM generator backend (Gen.py -> GenASM.py). - [X] Fix type system. - [X] Widen during parsing. - [X] Static type analysis. - [X] Check args in function call. -- [X] Include section in README regarding miscellaneous features (references, fixed-len pointers, namespaces) -- [X] Update README (#1) (new builtins, elif, ufcs, alias) -- [X] Update README (#2) (new operators, function overloading, structs) +- [X] Include section in README regarding miscellaneous features (references, fixed-len pointers, namespaces). +- [X] Update README (#1) (new builtins, elif, ufcs, alias). +- [X] Update README (#2) (new operators, function overloading, structs). - [X] Fix array access. - [X] Fix array variable offset. -- [X] Implement stack alignment. (align by 16-bytes) -- [X] Define operator token types (binary, unary) -- [X] Implement right-assoc +- [X] Implement stack alignment (align by 16-bytes). +- [X] Define operator token types (binary, unary). +- [X] Implement right-assoc. - [X] Implement lazy loading (gen). - [X] Add an output file parameter to the Gen module. - [X] Improve Def.rev_type_of. diff --git a/include/stdlib/builtin/for.ml b/include/stdlib/builtin/for.ml index 1d0b0a3..c859181 100644 --- a/include/stdlib/builtin/for.ml +++ b/include/stdlib/builtin/for.ml @@ -44,7 +44,7 @@ end fun lines(arg: c_stream): file_range let s = empty_str - s = extend(s, 256) + s = extend(&s, 256) let succ = read_line(arg, s, 256) ret file_range(arg, c_str(s), succ) @@ -56,7 +56,7 @@ end fun next(arg: file_range&): str let s = empty_str - s = extend(s, 256) + s = extend(&s, 256) let succ = read_line(arg.file_range_st, s, 256) arg.file_range_str = c_str(s) @@ -89,7 +89,7 @@ struct c_str_range end fun iter(arg: str&): c_str_range - ret c_str_range(c_str(arg), 0, 0, len(arg)) + ret c_str_range(c_str(arg), 0, 0, len(&arg)) end fun iter(arg: c_str): c_str_range diff --git a/include/stdlib/macro.ml b/include/stdlib/macro.ml index 6ab8f4d..6b9be76 100644 --- a/include/stdlib/macro.ml +++ b/include/stdlib/macro.ml @@ -1,8 +1,10 @@ -macro repeat(_n, _body) - for _repeat_it in range(_n) - _body - end -end +import stdlib.builtin.for + +# macro repeat(_n, _body) +# for _repeat_it in range(_n) +# _body +# end +# end # Inserts a delimiter between arguments macro delimit(_delim, _arg) @@ -24,6 +26,9 @@ macro reverse(_arg1, _arg2, _arg3) end # Miscellaneous +macro ref(_arg) + &_arg +end macro not(_arg) false if _arg else true end diff --git a/include/stdlib/string.ml b/include/stdlib/string.ml index 0f98807..88c219b 100644 --- a/include/stdlib/string.ml +++ b/include/stdlib/string.ml @@ -2,6 +2,7 @@ # Provides a functional-like ML frontend of the sds c library. # WARNING: Relies on the sds bindings for ML (string-backend.ml). +import stdlib.macro import stdlib.debug import stdlib.c.cstdlib import stdlib.c.cstdarg @@ -29,7 +30,7 @@ fun str(s: int8*): str ret sdsnew(s) end -fun str(s: str): str +fun str(s: str&): str ret str(c_str(s)) end @@ -51,15 +52,10 @@ end # Grow the sds to have the specified length. Bytes that were not part of the original length of the sds will be set to zero. # # If the specified length is smaller than the current length, no operation is performed. -fun extend(s: str, size: int64): str +fun extend(s: str&, size: int64): str ret sdsgrowzero(s, size) end -# Duplicate an sds string. -fun copy(s: str): str - ret sdsdup(move(s)) -end - # Modify an sds string in-place to make it empty (zero length). # However all the existing buffer is not discarded but set as free space # so that next append operations will not require allocations up to the @@ -75,7 +71,7 @@ fun copy(s: str&, t: str&): str end # Return the length of the sds string. -fun len(s: str): int64 +fun len(s: str&): int64 ret sdslen(s) end @@ -95,7 +91,7 @@ end # s = sdsnew("Hello World"); # sdsrange(s,1,-1); => "ello World" fun substr(s: str&, start: int64, send: int64): str - let tmp = str(s) + let tmp = str(&s) sdsrange(tmp, start, send) ret tmp end @@ -107,7 +103,7 @@ end # After the call, the modified sds string is no longer valid and all the # references must be substituted with the new pointer returned by the call. fun concat(s: str&, t: str&): str - let tmp = str(s) + let tmp = str(&s) ret sdscatsds(tmp, t) end @@ -131,7 +127,7 @@ fun concat_from(s: str&, fmt: int8*, ...): str let listx: va_list va_start(listx, fmt) - let tmp = str(s) + let tmp = str(&s) ret sdscatvprintf(tmp, fmt, move(listx)) end @@ -149,8 +145,8 @@ end # printf("%s\n", s); # # Output will be just "HelloWorld". -fun trim(s: str, cset: int8*): str - let tmp = str(s) +fun trim(s: str&, cset: int8*): str + let tmp = str(&s) ret sdstrim(tmp, cset) end @@ -170,11 +166,11 @@ fun compare(s: str&, s2: str&): int32 end # Returns whether two sds strings are equal. -fun equals(s: str, s2: str): bool +fun equals(s: str&, s2: str&): bool ret sdscmp(s, s2) == 0 end -fun equals(s: str, cs2: c_str): bool +fun equals(s: str&, cs2: c_str): bool let s2 = str(cs2) ret sdscmp(s, s2) == 0 end @@ -217,8 +213,8 @@ fun split(cs: c_str, sep: c_str, cnt: c_int*): sds* end # Overload; Split 's' with separator in 'sep'. An array of sds strings is returned. *count will be set by reference to the number of tokens returned. -fun split(s: str, sep: str, cnt: c_int*): sds* - let arr: sds* = sdssplitlen(c_str(s), len(s), c_str(sep), len(sep), cnt) +fun split(s: str&, sep: str&, cnt: c_int*): sds* + let arr: sds* = sdssplitlen(c_str(s), len(&s), c_str(sep), len(&sep), cnt) if arr == null panic("Cannot split string.") end @@ -227,8 +223,8 @@ fun split(s: str, sep: str, cnt: c_int*): sds* end # Overload; Split 's' with separator in 'sep'. An array of sds strings is returned. *count will be set by reference to the number of tokens returned. -fun split(s: str, sep: c_str, cnt: c_int*): sds* - let arr: sds* = sdssplitlen(c_str(s), len(s), sep, strlen(sep), cnt) +fun split(s: str&, sep: c_str, cnt: c_int*): sds* + let arr: sds* = sdssplitlen(c_str(s), len(&s), sep, strlen(sep), cnt) if arr == null panic("Cannot split string.") end @@ -259,7 +255,7 @@ end # Create an sds string from a string using clone. fun to_str(value: str&): str - ret value.copy + ret ref(value).copy end # Create an sds string from a C string literal. diff --git a/mlpx b/mlpx new file mode 100644 index 0000000..ae072f9 --- /dev/null +++ b/mlpx @@ -0,0 +1,250 @@ +#!/bin/python + +import os +import sys +import optparse +from typing import List + +quiet = False +msg_enabled = True +color_enabled = True +ml_path = '.' +ml_flags = '-C' +c_flags = '-g -O2' +c_compiler_path = 'gcc' +ml_compiler_path = f'python3 {os.path.join(ml_path, "src/Main.py")}' +c_include_path = os.path.join(ml_path, 'skel/include') +ml_include_path = os.path.join(ml_path, 'include') +proj_name = 'main' +build_dir = 'bin' +source_dir = '.' +build_opt = 'default' +source = '' + + +class Color: + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + WARNING = '\033[35m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +def c_suff(s): return add_suffix(s, 'c') + + +def ml_suff(s): return add_suffix(s, 'ml') + + +def build_clean(args: List[str]): + def clean(arg: str): + print_ok(f'Cleaning {arg}') + if os.path.exists(f'{arg}'): + execute(f'rm -ivrf {arg}') + + if len(args) == 0: + clean(build_dir) + return + + for arg in args: + clean(arg) + + +def find_err_args(args: List[str]) -> bool: + err_args = [] + for arg in args: + if not os.path.exists(arg): + err_args.append(arg) + + return err_args + + +def build_default(args: List[str]): + print_ok(f'Running default option on {source_dir}') + ml_cmd = f'{ml_compiler_path} {ml_flags} -I {ml_include_path} -o {os.path.join(build_dir, c_suff(proj_name))} {" ".join(args)}' + c_cmd = f'{c_compiler_path} {c_flags} {os.path.join(c_include_path, "*.c")} -I {c_include_path} {os.path.join(build_dir, c_suff(proj_name))} -o {os.path.join(build_dir, proj_name)}' + + if not os.path.exists(build_dir): + os.makedirs(build_dir) + execute(ml_cmd) + execute(c_cmd) + + +def build_debug(args: List[str]): + print_ok(f'Running debug option on {source_dir}') + cmd = f'{ml_compiler_path} -d -I {ml_include_path} {" ".join(args)}' + execute(cmd) + + +def build_cdebug(args: List[str]): + print_ok(f'Running cdebug option on {source_dir}') + cmd = f'{ml_compiler_path} -I {ml_include_path} {" ".join(args)}' + execute(cmd) + + +OPTION_MAP = { + 'clean': build_clean, + 'default': build_default, + 'debug': build_debug, + 'cdebug': build_cdebug, + 'def': build_default, + 'dbg': build_debug, + 'cdbg': build_cdebug, +} + + +def color_str(color: Color, msg: str): + if not color_enabled: + return msg + return f'{color}{msg}{Color.ENDC}' + + +def print_ok(msg: str): + if not msg_enabled: + return + print(f'> {color_str(Color.GREEN, msg)}', + file=sys.stderr) + + +def print_warning(msg: str): + print(f'{color_str(Color.WARNING, f"WARNING: {msg}")}', + file=sys.stderr) + exit(1) + + +def print_error(msg: str): + print(f'{color_str(Color.FAIL, f"ERROR: {msg}")}', + file=sys.stderr) + exit(1) + + +def execute(cmd: str): + if not quiet: + print(cmd) + os.system(cmd) + + +def add_suffix(source: str, suff: str) -> str: + return f'{source}.{suff}' + + +def handle_build(args: List[str]): + if build_opt not in OPTION_MAP: + print_error(f'Invalid option {build_opt}.') + + build_args = args if len(args) > 0 else [build_opt] + args = [source] + + err_args = find_err_args(args) + if len(err_args): + print_error( + f'Source files {", ".join(map(repr,err_args))} do not exist.') + + for arg in build_args: + if arg not in OPTION_MAP: + print_error(f'Invalid option {arg}.') + + OPTION_MAP.get(arg)(args) + + +def handle_run(args: List[str]): + if len(args) == 0: + args = [source] + + if build_opt not in OPTION_MAP: + print_error(f'Invalid option: {build_opt}') + + OPTION_MAP.get(build_opt)(args) + + +def handle_clean(args: List[str]): + build_clean(args) + + +def handle_rclean(args: List[str]): + # Iterate over all items in the directory + if len(args) == 0: + print_warning('No directory specified') + return + + for arg in args: + if not os.path.isdir(arg): + print_warning(f'Directory {arg} does not exist, skipping...') + continue + + for dir in os.listdir(arg): + item_path = os.path.join(arg, + os.path.join(dir, build_dir)) + + # If the item is a directory, recursively call the function + if os.path.isdir(item_path): + handle_rclean([item_path]) + handle_clean([item_path]) + + +def handle_cmd(parts: List[str]): + cmd = parts[0] + args = parts[1:] + + if cmd == 'run': + handle_run(args) + elif cmd == 'build': + handle_build(args) + elif cmd == 'rclean': + handle_rclean(args) + elif cmd == 'clean': + handle_clean(args) + elif cmd == 'init': + pass + else: + print_error(f'Invalid request: {cmd}') + + +if __name__ == '__main__': + desc = ', '.join(['The mini language project manager extension', + 'Version: 1.0.0', + 'Source: https://github.com/NICUP14/MiniLang.git']) + + parser = optparse.OptionParser(description=desc) + parser.add_option('-C', '--proj-path', default=source_dir, + help='Specify the project path (default=".").') + parser.add_option('-p', '--path', default=ml_path, + help='Specify the MiniLang project path (default=".").') + parser.add_option('-b', '--build-dir', default=build_dir, + help='Specify the build directory (default="bin").') + parser.add_option('-o', '--build-opt', default=build_opt, + help=f'Specify the build option; Chooise between: {", ".join(OPTION_MAP.keys())} (default="default").') + parser.add_option('-n', '--name', default=proj_name, + help='Specify the project\'s name (default="main").') + parser.add_option('-i', '--include', default=ml_include_path, + help='Speficy the MiniLang compiler include path (default="include").') + parser.add_option('-I', '--c-include', default=c_include_path, + help='Specify the C compiler include path (default="skel/include").') + parser.add_option('-q', '--quiet', action='store_true', + help='Run in quiet mode; Does not show compile commands. Overrides "-m".') + parser.add_option('-m', '--no-msg', action='store_true', + help='Run in script mode; Does not show mlpx logs.') + parser.add_option('-c', '--no-color', action='store_true', + help='Run in text mode; Does not use colors.') + values, cmd_list = parser.parse_args() + values_dict = vars(values) + + quiet = values_dict.get('quiet') + msg_enabled = not quiet and not values_dict.get('no_msg') + color_enabled = not values_dict.get('no_color') + ml_path = values_dict.get('path') + c_include_path = values_dict.get('c_include') + ml_include_path = values_dict.get('include') + proj_name = values_dict.get('name') + build_dir = values_dict.get('build_dir') + build_opt = values_dict.get('build_opt') + + source_dir = values_dict.get('proj_path') + build_dir = os.path.join(source_dir, build_dir) + source = os.path.join(source_dir, 'src/main.ml') + + handle_cmd(cmd_list) + print_ok('Done!') diff --git a/samples/calc/Makefile b/samples/calc/Makefile index 58d283f..d0b1832 100644 --- a/samples/calc/Makefile +++ b/samples/calc/Makefile @@ -12,7 +12,7 @@ ASM_OBJECTS = $(patsubst $(SOURCE_DIR)/%.ml, $(BUILD_DIR)/%.S, $(SOURCES)) # C compiler parameters CC = gcc CFLAGS ?= -CFLAGS += -g +CFLAGS += -g -O2 # Compiler parameters ML = python ../../src/Main.py @@ -37,7 +37,7 @@ asm: assemble _c: $(SOURCES) @mkdir -p $(BUILD_DIR) - $(ML) $(MLFLAGS) -I $(MLLIB) -I src $(SOURCES) > $(BUILD_DIR)/$(PROJECT).c + $(ML) $(MLFLAGS) -I $(MLLIB) -I src $(SOURCES) -o $(BUILD_DIR)/$(PROJECT).c $(CC) $(CFLAGS) -I include include/*.c $(BUILD_DIR)/$(PROJECT).c -o $(BUILD_DIR)/$(PROJECT) _cdebug: $(SOURCES) @@ -62,4 +62,4 @@ $(BUILD_DIR)/%.S: $(SOURCE_DIR)/%.ml $(ML) $(MLFLAGS) -I $(MLLIB) -I src $< -o $@ clean: - @rm -vrf $(BUILD_DIR) \ No newline at end of file + @rm -vrf $(BUILD_DIR) diff --git a/samples/calc/in.txt b/samples/calc/in.txt new file mode 100644 index 0000000..3933a26 --- /dev/null +++ b/samples/calc/in.txt @@ -0,0 +1 @@ +11 + 12 diff --git a/samples/calc/src/def.ml b/samples/calc/src/def.ml new file mode 100644 index 0000000..3e0a22a --- /dev/null +++ b/samples/calc/src/def.ml @@ -0,0 +1,18 @@ +struct _tok_type + err: int64 + num: int64 + add: int64 + sub: int64 + div: int64 + mult: int64 +end + +struct tok + type: int64 + value: c_str +end + +macro tok_type + _tok_type( + 0, 1, 2, 3, 4, 5) +end diff --git a/samples/calc/src/lex.ml b/samples/calc/src/lex.ml new file mode 100644 index 0000000..f725f0d --- /dev/null +++ b/samples/calc/src/lex.ml @@ -0,0 +1,71 @@ +import stdlib.str_list +import stdlib.io.read +import stdlib.io.print +import src.def + +fun rev_tok_type_of(type: int64): str + let cs: c_str = null + if type == tok_type.err + cs = "err" + elif type == tok_type.num + cs = "num" + elif type == tok_type.add + cs = "+" + elif type == tok_type.sub + cs = "-" + elif type == tok_type.div + cs = "/" + elif type == tok_type.mult + cs = "*" + else + panic("Invalid token type") + end + + ret cs.str +end + +fun _print(cs: c_stream, arg: tok&) + let rev = rev_tok_type_of(arg.type) + print("tok(type = ", &rev, ", value = ", arg.value, ")") +end + +fun is_num(s: str&) + for ch in s + if isdigit(ch) == 0 + ret false + end + end + + ret true +end + +fun tok_type_of(s: str&): int64 + if is_num(&s) + ret tok_type.num + elif s.equals("+") + ret tok_type.add + elif s.equals("-") + ret tok_type.sub + elif s.equals("/") + ret tok_type.div + elif s.equals("*") + ret tok_type.mult + end + + ret tok_type.err +end + +fun tok(value: str&) + ret tok(tok_type_of(&value), c_str(value)) +end + +fun lex(ln: str&) + let cnt: int32 = 0 + let arr = ln.trim("\n").split(" ", &cnt) + let list = str_list(cnt, arr) + + for it in list + let tk = tok(&it) + println(&tk) + end +end \ No newline at end of file diff --git a/samples/calc/src/main.ml b/samples/calc/src/main.ml index 4f8ce4b..6cb9c65 100644 --- a/samples/calc/src/main.ml +++ b/samples/calc/src/main.ml @@ -1,92 +1,8 @@ -import stdlib.str_list -import stdlib.io.read -import stdlib.io.print +import src.lex -struct _tok_type - err: int64 - num: int64 - add: int64 - sub: int64 - div: int64 - mult: int64 -end - -struct tok - type: int64 - value: c_str -end - -macro tok_type - _tok_type( - 0, 1, 2, 3, 4, 5) -end - -fun rev_tok_type_of(type: int64): str - let cs: c_str = null - if type == tok_type.err - cs = "err" - elif type == tok_type.num - cs = "num" - elif type == tok_type.add - cs = "+" - elif type == tok_type.sub - cs = "-" - elif type == tok_type.div - cs = "/" - elif type == tok_type.mult - cs = "*" - else - panic("Invalid token type") - end - - ret cs.str -end - -fun _print(cs: c_stream, arg: tok&) - let rev = rev_tok_type_of(arg.type) - print("tok(type = ", &rev, ", value = ", arg.value, ")") -end - -fun is_num(s: str&) - for ch in s - if isdigit(ch) == 0 - ret false - end - end - - ret true -end - -fun tok_type_of(s: str&): int64 - if is_num(&s) - ret tok_type.num - elif s.equals("+") - ret tok_type.add - elif s.equals("-") - ret tok_type.sub - elif s.equals("/") - ret tok_type.div - elif s.equals("*") - ret tok_type.mult - end - - ret tok_type.err -end - -fun tok(value: str&) - ret tok(tok_type_of(&value), c_str(value)) -end - -fun lex(ln: str&) - let cnt: int32 = 0 - let arr = ln.trim("\n").split(" ", &cnt) - let list = str_list(cnt, arr) - - for it in list - let tk = tok(&it) - println(&tk) - end - +struct X + x: int32 + b: range end fun main: int32 @@ -94,7 +10,7 @@ fun main: int32 alloc_start(bos) let s = input - lex(&s) + lex(s.trim(" ")) ret 0 end \ No newline at end of file diff --git a/samples/task-mgr/src/task.ml b/samples/task-mgr/src/task.ml index 6d54b06..16ce67a 100644 --- a/samples/task-mgr/src/task.ml +++ b/samples/task-mgr/src/task.ml @@ -72,7 +72,7 @@ fun find_task(tsk_list: task_list, tsk_name: str): int64 end fun add_task(tsk_list: task_list, tsk: task) - let idx = tsk_list.find_task(tsk.name) + let idx = find_task(tsk_list, tsk.name) if idx != 0 - 1 if task_at(tsk_list, idx).visible ret false diff --git a/src/Def.py b/src/Def.py index e647a1f..5b0f680 100644 --- a/src/Def.py +++ b/src/Def.py @@ -1191,13 +1191,16 @@ def gen_compatible(sig: FunctionSignature, arg_types: List[VariableType]): return True -def _find_signature(fun: Function, arg_types: List[VariableType], check_len: bool = True) -> Optional[FunctionSignature]: +def _find_signature(fun: Function, arg_types: List[VariableType], check_len: bool = True, check_refs: bool = False) -> Optional[FunctionSignature]: cnt = 0 sig = None def is_generic(sig: FunctionSignature): return sig.is_generic + def matches_ref(arg_type: VariableType, sig_arg_type: VariableType): + return ref_of(arg_type) == sig_arg_type + # Looks for an exact match for signature in fun.signatures: if not is_generic(signature) and arg_types == signature.arg_types: @@ -1221,7 +1224,7 @@ def is_generic(sig: FunctionSignature): if not is_generic(signature) and (fun.is_variadic or not check_len or len(arg_types) == signature.arg_cnt): compatible = True for arg_type, fun_arg_type in zip(arg_types, signature.arg_types): - if not type_compatible(NodeKind.FUN_CALL, arg_type.ckind, fun_arg_type.ckind) or (arg_type.name != fun_arg_type.name and arg_type != any_type and fun_arg_type != any_type): + if not (type_compatible(NodeKind.FUN_CALL, arg_type.ckind, fun_arg_type.ckind) or (check_refs and matches_ref(arg_type, fun_arg_type))) or (arg_type.name != fun_arg_type.name and arg_type != any_type and fun_arg_type != any_type): compatible = False break @@ -1333,6 +1336,12 @@ def cmp_modf_of(kind: NodeKind) -> str: return modf_map[kind] +def get_counter(): + global counter + counter += 1 + return counter + + REG_TABLE = ( ('%rbx', '%ebx', '%bx', '%bl'), ('%rcx', '%ecx', '%cx', '%cl'), @@ -1388,6 +1397,7 @@ def cmp_modf_of(kind: NodeKind) -> str: stdout = sys.stdout +counter = 0 var_off = 0 block_cnt = 0 macro_map: Dict[str, Macro] = dict() diff --git a/src/Parser.py b/src/Parser.py index cc58d73..b8eeb16 100644 --- a/src/Parser.py +++ b/src/Parser.py @@ -49,6 +49,7 @@ from Def import bool_type from Def import default_type from Def import str_type +from Def import get_counter from Def import print_error from Def import print_warning from Def import check_ident @@ -582,34 +583,44 @@ def is_in_sig(arg_type: VariableType): return sig is not None - def try_cast_rv_ref(node: Node): + def try_cast_ref(node: Node): var_type = None + is_ref = False is_rv_ref = False + # Explicit move if node.kind == NodeKind.MOVE: if is_in_sig(rv_ref_of(node.ntype)): is_rv_ref = True var_type = node.ntype + # Implicit move if node.kind == NodeKind.IDENT: if Def.ident_map.get(node.value) == VariableMetaKind.RV_REF and is_in_sig(type_of_ident(node.value)): is_rv_ref = True var_type = type_of_ident(node.value) - if is_rv_ref: + if not is_in_sig(node.ntype): + is_ref = True + var_type = ref_of(node.ntype) + + if is_rv_ref or is_ref: if node.kind != NodeKind.FUN_CALL: - return Node(NodeKind.REF, rv_ref_of(var_type), '&', node) + return Node(NodeKind.REF, rv_ref_of(var_type) if is_rv_ref else ref_of(var_type), '&', node) tmp_decl, node = self.ref(node) to_predeferred(tmp_decl) - node.ntype = rev_type_of(var_type) + node.ntype = rv_ref_of( + var_type) if is_rv_ref else ref_of(var_type) return node return node def inject_copy_arg(node: Node): - node = try_cast_rv_ref(node) + node = try_cast_ref(node) + arg_types.append(node.ntype) + if node.kind == NodeKind.MOVE: return node @@ -670,7 +681,9 @@ def get_type(node: Node) -> Optional[VariableType]: return node.ntype arg_types = list(map(get_type, args_to_list(node))) - sig = _find_signature(fun, arg_types, check_len=check_len) + sig = _find_signature( + fun, arg_types, check_len=check_len) + ref_sig = _find_signature(fun, arg_types, check_refs=True) ret_type = sig.ret_type if sig else any_type if sig is not None and ( @@ -678,8 +691,13 @@ def get_type(node: Node) -> Optional[VariableType]: self._infer_gen_types(sig, node) self._gen_fun_call(sig) + # Exhausive match + # !BUG: Creates buggy behaviour do to implicit ptr-ref cast + if ref_sig: + node = self.inject_copy(fun.name, node) + node_stack.append( - Node(kind, ret_type, fun_name, self.inject_copy(fun_name, node))) + Node(kind, ret_type, fun_name, node)) return node_stack @@ -689,7 +707,7 @@ def ref(self, node: Node) -> tuple[Node, Node]: return None, ref_node(node) tmp_name = full_name_of_var( - f'{node.value}ref_{self.lineno}', force_local=True) + f'{node.value}_tmp_ref_{get_counter()}', force_local=True) def get_type(node: Node): return node.ntype @@ -698,7 +716,8 @@ def get_type(node: Node): sig = _find_signature( fun, list(map(get_type, args_to_list(node.left)))) if not sig: - print_error('ref', '...') + print_error('ref', + f'No signature of {fun.name} matches {list(map(Def.rev_type_of, map(get_type, args_to_list(node.left))))} out of {[list(map(Def.rev_type_of, sig.arg_types)) for sig in fun.signatures]}') tmp_decl = self.declare( tmp_name, sig.ret_type, sig.ret_type.elem_ckind, init_node=node) @@ -912,7 +931,7 @@ def to_tree(self, tokens: list[Token]) -> Node: if kind == NodeKind.ELEM_ACC and Def.ident_map.get(right.value) == VariableMetaKind.NAMESPACE: print_error( - 'to_tree', f'Cannot use a qualified namespace function as a UFCS expression. TIP: Consider using an alias. (namespace={right.value})', parser=self) + 'to_tree', f'Cannot use a qualified namespace function as a FCS expression. TIP: Consider using an alias. (namespace={right.value})', parser=self) if kind == NodeKind.ELEM_ACC and right.kind == NodeKind.FUN_CALL: # Fix for single-arg UFCS expressions @@ -932,7 +951,8 @@ def to_tree(self, tokens: list[Token]) -> Node: args = Node(NodeKind.GLUE, void_type, '', Node( NodeKind.GLUE, void_type, '', None, left), right.left) - new_args = self.merge_fun_call(args) + new_args = self.inject_copy( + fun.name, self.merge_fun_call(args)) sig = Def.find_signature(fun, new_args) if sig: @@ -1067,58 +1087,65 @@ def parse_type(self) -> ParsedType: def parse_type_list(self): pass - def statement(self) -> Optional[Node]: + def statement(self, add_predef: bool = True) -> Optional[Node]: token = self.curr_token() + node = None if token.kind == TokenKind.KW_LET: self.next_token() - return self.declaration() - if token.kind == TokenKind.KW_IF: + node = self.declaration() + elif token.kind == TokenKind.KW_IF: self.next_token() - return self.if_statement() - if token.kind == TokenKind.KW_FOR: + node = self.if_statement(False) + elif token.kind == TokenKind.KW_FOR: self.next_token() - return self.for_statement() - if token.kind == TokenKind.KW_WHILE: + node = self.for_statement(False) + elif token.kind == TokenKind.KW_WHILE: self.next_token() - return self.while_statement() - if token.kind == TokenKind.KW_FUN: + node = self.while_statement(False) + elif token.kind == TokenKind.KW_FUN: self.next_token() - return self.fun_declaration(is_extern=False) - if token.kind == TokenKind.KW_STRUCT: + node = self.fun_declaration(is_extern=False) + elif token.kind == TokenKind.KW_STRUCT: self.next_token() - return self.struct_declaration() - if token.kind == TokenKind.KW_RET: + node = self.struct_declaration() + elif token.kind == TokenKind.KW_RET: self.next_token() - return self.ret_statement() - if token.kind == TokenKind.KW_EXTERN: + node = self.ret_statement() + elif token.kind == TokenKind.KW_EXTERN: self.next_token() if self.curr_token().kind == TokenKind.KW_FUN: self.match_token(TokenKind.KW_FUN) - return self.fun_declaration(is_extern=True) + node = self.fun_declaration(is_extern=True) else: self.match_token(TokenKind.KW_STRUCT) - return self.struct_declaration(is_extern=True) - if token.kind == TokenKind.KW_ALIAS: + node = self.struct_declaration(is_extern=True) + elif token.kind == TokenKind.KW_ALIAS: self.next_token() - return self.alias_definition() - if token.kind == TokenKind.KW_IMPORT: + node = self.alias_definition() + elif token.kind == TokenKind.KW_IMPORT: self.next_token() - return self.import_statement() - if token.kind == TokenKind.KW_NAMESPACE: + node = self.import_statement() + elif token.kind == TokenKind.KW_NAMESPACE: self.next_token() - return self.namespace_statement() - if token.kind == TokenKind.KW_DEFER: + node = self.namespace_statement() + elif token.kind == TokenKind.KW_DEFER: self.next_token() - return self.defer_statement() - if token.kind == TokenKind.KW_BLOCK: + node = self.defer_statement() + elif token.kind == TokenKind.KW_BLOCK: self.next_token() - return self.block_statement() - if token.kind == TokenKind.KW_MACRO: + node = self.block_statement() + elif token.kind == TokenKind.KW_MACRO: self.next_token() - return self.macro_statement() + node = self.macro_statement() + else: + node = self.token_list_to_tree() + + # Inserts predeferred statements right before the fun's body + if add_predef and Def.predeferred is not None: + node = glue_statements([Def.predeferred, node]) + Def.predeferred = None - node = self.token_list_to_tree() return node def program_statement(self) -> Optional[Node]: @@ -1134,14 +1161,14 @@ def program_statement(self) -> Optional[Node]: return node - def compound_statement(self) -> Optional[Node]: + def compound_statement(self, add_predef: bool = True) -> Optional[Node]: node = None while not self.no_more_lines(check_next_lines=True) and self.curr_token().kind not in (TokenKind.KW_END, TokenKind.KW_ELSE, TokenKind.KW_ELIF): if node is None: - node = self.statement() + node = self.statement(add_predef) else: node = Node(NodeKind.GLUE, void_type, - '', node, self.statement()) + '', node, self.statement(add_predef)) self.next_line() return node @@ -1253,7 +1280,7 @@ def inject_cond(self, node: Node) -> Node: return Node(NodeKind.OP_EQ, bool_type, '==', node, Node(NodeKind.TRUE_LIT, bool_type, 'true')) - def while_statement(self) -> Optional[Node]: + def while_statement(self, add_predef: bool) -> Optional[Node]: cond_node = self.token_list_to_tree() if cond_node.ntype not in (any_type, bool_type): print_error('while_statement', @@ -1261,13 +1288,14 @@ def while_statement(self) -> Optional[Node]: self.next_line() cond_node = self.inject_cond(cond_node) - body = self.compound_statement() + body = self.compound_statement(add_predef) node = Node(NodeKind.GLUE, void_type, '', Node( NodeKind.WHILE, void_type, '', cond_node, body), Node(NodeKind.END, void_type, 'end')) + return node - def for_statement(self) -> Optional[Node]: + def for_statement(self, add_predef: bool) -> Optional[Node]: iter_name = full_name_of_var( self.match_token(TokenKind.IDENT).value, force_local=True) self.match_token(TokenKind.KW_IN) @@ -1276,7 +1304,7 @@ def for_statement(self) -> Optional[Node]: iter_ident = Node(NodeKind.IDENT, default_type, iter_name) target_node = self.token_list_to_tree() - target_name = full_name_of_var(f'for{self.lineno}_target') + target_name = full_name_of_var(f'tmp_for_target_{get_counter()}') target_ident = Node(NodeKind.IDENT, target_node.ntype, target_name) self.next_line() @@ -1365,7 +1393,7 @@ def add_prefix(elem_name: str, name: str = name) -> str: Def.ptr_map[name] = Pointer( name, 0, VariableType(vtype.elem_ckind, name=vtype.name), Def.var_off, ptr_type_of(meta_kind)) - body = self.compound_statement() + body = self.compound_statement(add_predef) if target_node.kind == NodeKind.FUN_CALL: tmp_decl, target_ref = self.ref(target_node) @@ -1410,7 +1438,7 @@ def add_prefix(elem_name: str, name: str = name) -> str: return glue_statements([pre_cond, Node(NodeKind.FOR, void_type, '', body, post_cond, cond), end]) # An if_statement helper to parse multiple elif statements - def elif_statement(self) -> Optional[Node]: + def elif_statement(self, add_predef: bool) -> Optional[Node]: node = None cond_node = None while not self.no_more_lines() and self.curr_token().kind not in (TokenKind.KW_END, TokenKind.KW_ELSE): @@ -1420,7 +1448,7 @@ def elif_statement(self) -> Optional[Node]: self.next_line() end_node = Node(NodeKind.END, void_type, '') elif_node = glue_statements([Node(NodeKind.ELIF, void_type, '', - self.compound_statement(), None, cond_node), end_node]) + self.compound_statement(add_predef), None, cond_node), end_node]) if node is None: node = elif_node @@ -1430,7 +1458,7 @@ def elif_statement(self) -> Optional[Node]: return node - def if_statement(self) -> Optional[Node]: + def if_statement(self, add_predef: bool) -> Optional[Node]: cond_node = self.token_list_to_tree() if cond_node.ntype not in (any_type, bool_type): print_error('if_statement', @@ -1441,17 +1469,17 @@ def if_statement(self) -> Optional[Node]: end_node = Node(NodeKind.END, void_type, '') true_node = glue_statements([Node(NodeKind.IF, void_type, '', - self.compound_statement(), None, cond_node), end_node]) + self.compound_statement(add_predef), None, cond_node), end_node]) elif_node = None if self.curr_token().kind == TokenKind.KW_ELIF: - elif_node = self.elif_statement() + elif_node = self.elif_statement(add_predef) false_node = None if self.curr_token().kind == TokenKind.KW_ELSE: self.next_line() false_node = glue_statements([Node(NodeKind.ELSE, void_type, '', - self.compound_statement()), end_node]) + self.compound_statement(add_predef)), end_node]) nodes = [true_node] if elif_node is not None: @@ -1515,8 +1543,6 @@ def ret_statement(self) -> Optional[Node]: if node and node.kind == NodeKind.IDENT and node.ntype.ckind == struct_ckind: Def.returned.append(node.value) - # TODO: Create a unified method to declare variables (declare_statement -> declare) - # ? Dirty fix which issues gcc warnings # Stores the return in a temp (store -> destr -> ret) full_name = full_name_of_var( f'ret_{self.lineno}', force_local=True) @@ -2333,8 +2359,8 @@ def declaration(self, is_struct: bool = False) -> Optional[Node]: elem_cnt, is_local, is_struct) # Inserts predeferred statements right after declarations - if Def.predeferred is not None: - node = glue_statements([Def.predeferred, node]) - Def.predeferred = None + # if Def.predeferred is not None: + # node = glue_statements([Def.predeferred, node]) + # Def.predeferred = None return node diff --git a/src/backend/c/CWalker.py b/src/backend/c/CWalker.py index aadbedc..13890db 100644 --- a/src/backend/c/CWalker.py +++ b/src/backend/c/CWalker.py @@ -133,7 +133,8 @@ def get_type(node: Node): return node.ntype print_error('c_walker_step', - f'No signature of {fun.name} matches {list(map(Def.rev_type_of, map(get_type, args_to_list(node.left))))} out of {[list(map(Def.rev_type_of, sig.arg_types)) for sig in fun.signatures]}') + ' '.join([f'{fun.name}({fun_call_tree_str(node.left, _c_walk)})', + f'\nNo signature of {fun.name} matches {list(map(Def.rev_type_of, map(get_type, args_to_list(node.left))))} out of {[list(map(Def.rev_type_of, sig.arg_types)) for sig in fun.signatures]}'])) # ? For easy debugging of signatures # def get_type(node: Node): diff --git a/tests/test/src/main.ml b/tests/test/src/main.ml index f036b37..122bb98 100644 --- a/tests/test/src/main.ml +++ b/tests/test/src/main.ml @@ -6,10 +6,10 @@ fun main let bos = 0 alloc_start(bos) - let arr: int64[5]* = null - arr.alloc + let a = input + let b = str(a.str) + println a - free(arr) # let s: str& = &empty_str # println(delimit(" ", "Hi", "my", "name", "is", "Nicu")) # for i in range(10)