From 6485e63558a766b6541aafd0e042ba8a28f3266c Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 14:57:18 +0200 Subject: [PATCH 01/11] add: primitive Argparser --- core/builtin_funcs.py | 2 +- core/datatypes.py | 5 ++ core/lexer.py | 2 +- core/parser.py | 1 - core/tokens.py | 4 +- examples/args_test.rn | 23 ++---- stdlib/Argparser.rn | 171 ++++++++++++++++++++++++++++++------------ 7 files changed, 139 insertions(+), 69 deletions(-) diff --git a/core/builtin_funcs.py b/core/builtin_funcs.py index 1ed2735..664b28a 100755 --- a/core/builtin_funcs.py +++ b/core/builtin_funcs.py @@ -357,7 +357,7 @@ def execute_sys_args(self, exec_ctx): from sys import argv # Lazy import try: - return RTResult().success(Array(argv)) + return RTResult().success(radonify(argv, self.pos_start, self.pos_end, exec_ctx)) except Exception as e: print(e) return RTResult().failure(RTError(self.pos_start, self.pos_end, "Could't run the sys_args", exec_ctx)) diff --git a/core/datatypes.py b/core/datatypes.py index 9d3a820..79b8b2e 100755 --- a/core/datatypes.py +++ b/core/datatypes.py @@ -449,6 +449,11 @@ def set_index(self, index: Value, value: Value) -> ResultTuple: ) return self, None + def contains(self, other: Value) -> ResultTuple: + if not isinstance(other, String): + return None, self.illegal_operation(other) + return Boolean(other.value in self.value), None + def is_true(self) -> bool: return len(self.value) > 0 diff --git a/core/lexer.py b/core/lexer.py index 18bbd2d..212718b 100755 --- a/core/lexer.py +++ b/core/lexer.py @@ -82,7 +82,7 @@ def make_tokens(self) -> tuple[list[Token], Optional[Error]]: self.advance() elif self.current_char == "!": token, error = self.make_not_equals() - if error is None: + if error is not None: return [], error assert isinstance(token, Token) tokens.append(token) diff --git a/core/parser.py b/core/parser.py index 4d29e87..a4b9853 100755 --- a/core/parser.py +++ b/core/parser.py @@ -751,7 +751,6 @@ def array_expr(self) -> ParseResult[Node]: if self.current_tok.type == TT_RSQUARE: self.advance(res) - self.skip_newlines() else: elt = res.register(self.expr()) self.skip_newlines() diff --git a/core/tokens.py b/core/tokens.py index 1ca13fd..0d3ad2d 100755 --- a/core/tokens.py +++ b/core/tokens.py @@ -150,8 +150,8 @@ def __init__( self.type = type_ self.value = value - self.pos_start = pos_start - self.pos_end = pos_end if pos_end is not None else pos_start + self.pos_start = pos_start.copy() + self.pos_end = pos_end.copy() if pos_end is not None else pos_start.copy() def matches(self, type_: TokenType, value: TokenValue) -> bool: return self.type == type_ and self.value == value diff --git a/examples/args_test.rn b/examples/args_test.rn index f6ccdbd..8a08db5 100755 --- a/examples/args_test.rn +++ b/examples/args_test.rn @@ -1,21 +1,8 @@ include Argparser -parser = Argparser("Utility Tool", "A simple utility tool.") +parser = Argparser.Argparser() +parser.add_flag("--version", "-v", "Show version") +parser.add_named("--output", "Output file") +parser.add_pos_opt("filename", "File") +print(parser.parse(["./program.rn", "a", "--output", "b"])) -# Callback function -fun version() -> "1.0.0" -fun author() -> "Md. Almas Ali" - -# Add commands -parser.add_command("-v", "Version information", 0, version) -parser.add_command("-a", "About author", 0, author) - -# Get args lists -# print(parser.get_args()) - -# Get help text -# print(parser.get_help()) - -# print(parser.get_commands()) - -parser.parse() diff --git a/stdlib/Argparser.rn b/stdlib/Argparser.rn index 89833e5..48b8a39 100755 --- a/stdlib/Argparser.rn +++ b/stdlib/Argparser.rn @@ -1,62 +1,141 @@ -class Argparser { - fun __constructor__(description) { - this.description = description - this.options = { - "--help": "Show this help message and exit" + +_i = 0 +FULLNAME_INVALID = _i++ +FULLNAME_FLAG = _i++ +FULLNAME_NAMED = _i++ +_i = 0 + +fun str_starts_with(s, prefix) { + if str_len(s) < str_len(prefix) { + return false + } + for i = 0 to str_len(prefix) { + if s[i] != prefix[i] { + return false } } + return true +} - fun add_option(option, description, is_flag = false) { - this.options[option] = {"description": description, "is_flag": is_flag} +class Argparser { + fun __constructor__(desc="") { + this.desc = desc + this.pos_opts = [] + this.flags = [] + this.named = [] } - fun parse_arguments() { - arguments = sys_args() - parsed_args = {} - current_option = null - index = 1 + fun add_pos_opt(name, desc) { + arr_append(this.pos_opts, {"name": name, "desc": desc}) + return this + } - while index < arr_len(arguments) { - argument = arguments[index] + fun add_flag(fullname, shortname, desc) { + assert str_starts_with(fullname, "--"), "Flags must start with '--'" + assert str_starts_with(shortname, "-"), "Flag shortnames must start with '-'" + assert not str_starts_with(shortname, "--"), "Flag shortnames must not start with '--'" + arr_append(this.flags, {"fullname": fullname, "shortname": shortname[1:], "desc": desc}) + return this + } - if current_option != null { - parsed_args[current_option] = argument - current_option = null - } - elif argument in this.options { - current_option = argument - } - else { - print("Unknown argument: " + argument) - return - } + fun add_named(name, desc) { + assert str_starts_with(name, "--"), "Named arguments must start with '--'" + arr_append(this.named, {"name": name, "desc": desc}) + return this + } - nonlocal index++ + fun usage(program_name) { + ret = "Usage: "+program_name+" \n" + ret += "OPTIONS:\n" + for opt in this.pos_opts { + nonlocal ret += " " + opt["name"] + ": " + opt["desc"] + "\n" } + ret += "FLAGS:\n" + for flag in this.flags { + nonlocal ret += " " + flag["fullname"] + ", " + flag["shortname"] + ": " + flag["desc"] + "\n" + } + for named in this.named { + nonlocal ret += " " + named["name"] + " : " + named["desc"] + "\n" + } + return ret + } - return parsed_args + fun report_error(program_name, msg) { + print(this.usage(program_name)) + print("ERROR: "+msg) + pyapi("exit(1)", {}) } - fun print_help() { - print("Usage:" + sys_args()[0]) - print(this.description) - print("Options:") - for option, data in this.options { - print(" " + option + ": " + data["description"]) + fun flag_by_shortname(shortname) { + for flag in this.flags { + if flag["shortname"] == shortname { + return flag + } } + return null + } + + fun fullname_type(fullname) { + for flag in this.flags { + if flag["fullname"] == fullname { + return FULLNAME_FLAG + } + } + for named in this.named { + if named["name"] == fullname { + return FULLNAME_NAMED + } + } + return FULLNAME_INVALID } -} -#! -argparser = Argparser("This is a simple argparser") -argparser.add_option("--name", "Name of the user") -argparser.add_option("--age", "Age of the user") -argparser.add_option("--is-admin", "Is the user an admin", is_flag=true) - -args = argparser.parse_arguments() -if "--help" in args { - argparser.print_help() -} else { - print(args) + fun parse(args) { + pos_opts = this.pos_opts + flags = this.flags + + program_name = arr_pop(args, 0) + parsed = {} + for pos_opt in pos_opts { + parsed[pos_opt["name"]] = null + } + for flag in flags { + parsed[flag["fullname"]] = false + } + for named in this.named { + parsed[named["name"]] = null + } + pos_opts_idx = 0 + while arr_len(args) > 0 { + arg = arr_pop(args, 0) + if str_starts_with(arg, "-") { + if str_starts_with(arg, "--") { + switch this.fullname_type(arg) { + case FULLNAME_INVALID -> this.report_error(program_name, "unknown flag: '"+arg+"'") + case FULLNAME_FLAG -> parsed[arg] = true + case FULLNAME_NAMED { + value = arr_pop(args, 0) + parsed[arg] = value + } + } + } else { + for letter in arg[1:] { + flag = this.flag_by_shortname(letter) + if is_null(flag) { + this.report_error(program_name, "unknown flag: '-"+letter+"'") + } else { + parsed[flag["fullname"]] = true + } + } + } + } else { + if pos_opts_idx >= arr_len(pos_opts) { + this.report_error(program_name, "unexpected positional argument: '" + arg + "'") + } + arg_name = pos_opts[pos_opts_idx]["name"] + parsed[arg_name] = arg + nonlocal pos_opts_idx++ + } + } + return parsed + } } -!# \ No newline at end of file From 182b7ceb5f81d1b0caff26f0d215924c5da15391 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 15:45:05 +0200 Subject: [PATCH 02/11] Stop using argparse --- core/__init__.py | 4 +-- core/builtin_funcs.py | 11 ------- examples/args_test.rn | 2 +- radon.py | 71 +++++++++++++++++++++++++++++-------------- test.py | 4 +-- 5 files changed, 53 insertions(+), 39 deletions(-) diff --git a/core/__init__.py b/core/__init__.py index 6aa9f1c..816b504 100755 --- a/core/__init__.py +++ b/core/__init__.py @@ -5,7 +5,7 @@ Github: @Almas-Ali """ -from core.builtin_funcs import run +from core.builtin_funcs import run, global_symbol_table, radonify -__all__ = ["run"] +__all__ = ["run", "global_symbol_table", "radonify"] __version__ = "0.0.1" diff --git a/core/builtin_funcs.py b/core/builtin_funcs.py index 664b28a..e1be018 100755 --- a/core/builtin_funcs.py +++ b/core/builtin_funcs.py @@ -352,16 +352,6 @@ def execute_pyapi(self, exec_ctx): return res return res.success(Null.null()) - @args([]) - def execute_sys_args(self, exec_ctx): - from sys import argv # Lazy import - - try: - return RTResult().success(radonify(argv, self.pos_start, self.pos_end, exec_ctx)) - except Exception as e: - print(e) - return RTResult().failure(RTError(self.pos_start, self.pos_end, "Could't run the sys_args", exec_ctx)) - @args([]) def execute_time_now(self, exec_ctx): import time # Lazy import @@ -522,7 +512,6 @@ def create_global_symbol_table() -> SymbolTable: # System methods ret.set("require", BuiltInFunction("require")) ret.set("exit", BuiltInFunction("exit")) - ret.set("sys_args", BuiltInFunction("sys_args")) ret.set("time_now", BuiltInFunction("time_now")) # Built-in classes ret.set("File", bic.BuiltInClass("File", bic.FileObject)) diff --git a/examples/args_test.rn b/examples/args_test.rn index 3e8df08..f9f8b92 100755 --- a/examples/args_test.rn +++ b/examples/args_test.rn @@ -4,5 +4,5 @@ parser = Argparser.Argparser() parser.add_flag("--version", "-v", "Show version") parser.add_named("--output", "Output file") parser.add_pos_opt("filename", "File") -print(parser.parse(["./program.rn", "a", "--output", "b"])) +print(parser.parse(argv)) diff --git a/radon.py b/radon.py index 87d6f71..5478004 100755 --- a/radon.py +++ b/radon.py @@ -1,9 +1,8 @@ #!/usr/bin/python3.12 # By: Md. Almas Ali -import argparse -import pathlib import sys +from typing import IO try: import readline @@ -17,6 +16,8 @@ pass import core as base_core +from core.parser import Context +from core.lexer import Position def shell() -> None: @@ -57,22 +58,51 @@ def shell() -> None: print("KeyboardInterrupt") -def main(argv: list[str]) -> None: - parser = argparse.ArgumentParser(description="Radon programming language") - parser.add_argument( - "-p", - "--hide-file-paths", - help="Don't show file paths in error messages [NOT CURRENTLY WORKING]", - action="store_true", +def usage(program_name: str, stream: IO[str]) -> None: + print( + f"Usage: {program_name} [--source | -s] [--command | -c] [source_file] [--version | -v] [--help | -h]", + file=stream, ) - parser.add_argument("-s", "--source", type=str, help="Radon source file", nargs="*") - parser.add_argument("-c", "--command", type=str, help="Command to execute as string") - parser.add_argument("-v", "--version", help="Version info", action="store_true") - args = parser.parse_args() - if args.source: - source = pathlib.Path(args.source[0]).read_text() - (result, error, should_exit) = base_core.run(args.source[0], source, hide_paths=args.hide_file_paths) + +def main(argv: list[str]) -> None: + program_name = argv.pop(0) + source_file = None + command = None + while len(argv) > 0: + arg = argv.pop(0) + match arg: + case "--help" | "-h": + usage(program_name, sys.stdout) + exit(0) + case "--source" | "-s": + if len(argv) == 0: + usage(program_name, sys.stderr) + print(f"ERROR: {arg} requires an argument", file=sys.stderr) + exit(1) + source_file = argv[0] + break # allow program to use remaining args + case "--version" | "-v": + print(base_core.__version__) + exit(0) + case "--command" | "-c": + if len(argv) == 0: + usage(program_name, sys.stderr) + print(f"ERROR: {arg} requires an argument", file=sys.stderr) + exit(1) + command = argv[0] + break # allow program to use remaining args + case _: + usage(program_name, sys.stderr) + print(f"ERROR: Unknown argument '{arg}'", file=sys.stderr) + exit(1) + + pos = Position(0, 0, 0, "", "") + base_core.global_symbol_table.set("argv", base_core.radonify(argv, pos, pos, Context(""))) + if source_file is not None: + with open(source_file, "r") as f: + source = f.read() + (result, error, should_exit) = base_core.run(source_file, source) if error: print(error.as_string()) @@ -81,18 +111,15 @@ def main(argv: list[str]) -> None: if should_exit: exit() - elif args.command: - (result, error, should_exit) = base_core.run("", args.command) + elif command is not None: + (result, error, should_exit) = base_core.run("", command) if error: print(error.as_string()) - elif args.version: - print(base_core.__version__) - else: shell() if __name__ == "__main__": - main(sys.argv[1:]) + main(sys.argv) diff --git a/test.py b/test.py index 5d153da..bf1b059 100755 --- a/test.py +++ b/test.py @@ -26,9 +26,7 @@ def dump(self, path: str) -> None: def run_test(test: str) -> Output: - proc = subprocess.run( - [sys.executable, "radon.py", "--hide-file-paths", "-s", test], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.run([sys.executable, "radon.py", "-s", test], stdout=subprocess.PIPE, stderr=subprocess.PIPE) return Output(proc.returncode, proc.stdout.decode("utf-8"), proc.stderr.decode("utf-8")) From c837b1f6758252d41df7759d951dd25b9fe74ae3 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 16:20:08 +0200 Subject: [PATCH 03/11] Add simple grep example --- examples/simple-grep.rn | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/simple-grep.rn diff --git a/examples/simple-grep.rn b/examples/simple-grep.rn new file mode 100644 index 0000000..9cbd6da --- /dev/null +++ b/examples/simple-grep.rn @@ -0,0 +1,51 @@ +import Argparser + +fun main() { + parser = Argparser.Argparser() + parser.add_flag("--help", "-h", "Show this help text and exit") + parser.add_pos_opt("query", "String to query") + parser.add_pos_opt("file", "File to query string in") + parser.add_flag("--line-numbers", "-n", "Show line numbers") + parser.add_named("--max-lines", "Maximum amount of lines to show") + args = parser.parse(argv[:]) + + if args["--help"] { + print(parser.usage()) + exit() + } + + if is_null(args["query"]) { + parser.report_error(argv[0], "no query string provided") + } + if is_null(args["file"]) { + parser.report_error(argv[0], "no file provided") + } + + f = File(args["file"], "r") + lines = (String(f.read())).split("\n") + f.close() + + matched_lines = [] + i = 0 + for line in lines { + if args["query"] in line { + arr_append(matched_lines, [i, line]) + } + nonlocal i++ + } + + if not is_null(args["--max-lines"]) { + nonlocal matched_lines = matched_lines[:int(args["--max-lines"])] + } + + for line in matched_lines { + s = line[1] + if args["--line-numbers"] { + nonlocal s = args["file"] + ":" + line[0] + ": " + s + } + print(s) + } +} + +main() + From 0427d48e7cb547f698809ecffdf05eb495e5e00e Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 16:26:35 +0200 Subject: [PATCH 04/11] Fix --help in example --- examples/simple-grep.rn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple-grep.rn b/examples/simple-grep.rn index 9cbd6da..3cd7cb0 100644 --- a/examples/simple-grep.rn +++ b/examples/simple-grep.rn @@ -10,7 +10,7 @@ fun main() { args = parser.parse(argv[:]) if args["--help"] { - print(parser.usage()) + print(parser.usage(argv[0])) exit() } From 0b84c022d14c697bd80d3d3d72229c0b97e7afc1 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 16:33:32 +0200 Subject: [PATCH 05/11] fix crash when no value is provided for named arg --- stdlib/Argparser.rn | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stdlib/Argparser.rn b/stdlib/Argparser.rn index 48b8a39..894632c 100755 --- a/stdlib/Argparser.rn +++ b/stdlib/Argparser.rn @@ -113,6 +113,9 @@ class Argparser { case FULLNAME_INVALID -> this.report_error(program_name, "unknown flag: '"+arg+"'") case FULLNAME_FLAG -> parsed[arg] = true case FULLNAME_NAMED { + if arr_len(args) == 0 { + this.report_error(program_name, "missing value for flag: '"+arg+"'") + } value = arr_pop(args, 0) parsed[arg] = value } From 91d4ef480b5b638c4207f03726064220a836ef5c Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 16:44:46 +0200 Subject: [PATCH 06/11] Add ability to specify default values in argparse --- stdlib/Argparser.rn | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stdlib/Argparser.rn b/stdlib/Argparser.rn index 894632c..0f8fafd 100755 --- a/stdlib/Argparser.rn +++ b/stdlib/Argparser.rn @@ -25,8 +25,8 @@ class Argparser { this.named = [] } - fun add_pos_opt(name, desc) { - arr_append(this.pos_opts, {"name": name, "desc": desc}) + fun add_pos_opt(name, desc, default_=null) { + arr_append(this.pos_opts, {"name": name, "desc": desc, "default": default_}) return this } @@ -38,9 +38,9 @@ class Argparser { return this } - fun add_named(name, desc) { + fun add_named(name, desc, default_=null) { assert str_starts_with(name, "--"), "Named arguments must start with '--'" - arr_append(this.named, {"name": name, "desc": desc}) + arr_append(this.named, {"name": name, "desc": desc, "default": default_}) return this } @@ -96,13 +96,13 @@ class Argparser { program_name = arr_pop(args, 0) parsed = {} for pos_opt in pos_opts { - parsed[pos_opt["name"]] = null + parsed[pos_opt["name"]] = pos_opt["default"] } for flag in flags { parsed[flag["fullname"]] = false } for named in this.named { - parsed[named["name"]] = null + parsed[named["name"]] = named["default"] } pos_opts_idx = 0 while arr_len(args) > 0 { From 95a343449e7cba3a9ac2a97eb0d0fa2988fea778 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 16:51:16 +0200 Subject: [PATCH 07/11] Add a way to specify required arguments --- examples/simple-grep.rn | 11 ++--------- stdlib/Argparser.rn | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/examples/simple-grep.rn b/examples/simple-grep.rn index 3cd7cb0..886cfcd 100644 --- a/examples/simple-grep.rn +++ b/examples/simple-grep.rn @@ -3,8 +3,8 @@ import Argparser fun main() { parser = Argparser.Argparser() parser.add_flag("--help", "-h", "Show this help text and exit") - parser.add_pos_opt("query", "String to query") - parser.add_pos_opt("file", "File to query string in") + parser.add_pos_opt("query", "String to query", required=true) + parser.add_pos_opt("file", "File to query string in", required=true) parser.add_flag("--line-numbers", "-n", "Show line numbers") parser.add_named("--max-lines", "Maximum amount of lines to show") args = parser.parse(argv[:]) @@ -14,13 +14,6 @@ fun main() { exit() } - if is_null(args["query"]) { - parser.report_error(argv[0], "no query string provided") - } - if is_null(args["file"]) { - parser.report_error(argv[0], "no file provided") - } - f = File(args["file"], "r") lines = (String(f.read())).split("\n") f.close() diff --git a/stdlib/Argparser.rn b/stdlib/Argparser.rn index 0f8fafd..45321b7 100755 --- a/stdlib/Argparser.rn +++ b/stdlib/Argparser.rn @@ -25,8 +25,11 @@ class Argparser { this.named = [] } - fun add_pos_opt(name, desc, default_=null) { - arr_append(this.pos_opts, {"name": name, "desc": desc, "default": default_}) + fun add_pos_opt(name, desc, default_=null, required=false) { + if required { + assert is_null(default_), "Required positional arguments cannot have a default value" + } + arr_append(this.pos_opts, {"name": name, "desc": desc, "default": default_, "required": required}) return this } @@ -95,8 +98,12 @@ class Argparser { program_name = arr_pop(args, 0) parsed = {} + required_opts = {} for pos_opt in pos_opts { parsed[pos_opt["name"]] = pos_opt["default"] + if pos_opt["required"] { + required_opts[pos_opt["name"]] = true + } } for flag in flags { parsed[flag["fullname"]] = false @@ -136,9 +143,15 @@ class Argparser { } arg_name = pos_opts[pos_opts_idx]["name"] parsed[arg_name] = arg + required_opts[arg_name] = false nonlocal pos_opts_idx++ } } + for required in required_opts { + if required_opts[required] { + this.report_error(program_name, "missing required argument: '" + required + "'") + } + } return parsed } } From 9ab9e3d8cc2291c8eece91fea0b880eb6cc0abbd Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 16:52:17 +0200 Subject: [PATCH 08/11] Fix Python exception on opening non-existent files --- core/builtin_classes/file_object.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/builtin_classes/file_object.py b/core/builtin_classes/file_object.py index 286532a..fe16c45 100644 --- a/core/builtin_classes/file_object.py +++ b/core/builtin_classes/file_object.py @@ -13,7 +13,12 @@ def constructor(self, path, mode): res = RTResult() if mode.value not in allowed_modes: return res.failure(RTError(mode.pos_start, mode.pos_end, f"Invalid mode '{mode.value}'", mode.context)) - self.file = open(path.value, mode.value) + try: + self.file = open(path.value, mode.value) + except OSError as e: + return res.failure( + RTError(path.pos_start, path.pos_end, f"Could not open file {path.value}: {e}", path.context) + ) return res.success(None) @args(["count"], [Number(-1)]) From 305a434db05e025e5889f9f34e4a1a6b9717027f Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 17:08:45 +0200 Subject: [PATCH 09/11] Add conversors --- core/interpreter.py | 9 ++++++++- examples/simple-grep.rn | 4 ++-- stdlib/Argparser.rn | 37 +++++++++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/core/interpreter.py b/core/interpreter.py index 27eece0..d3f04d0 100755 --- a/core/interpreter.py +++ b/core/interpreter.py @@ -523,8 +523,15 @@ def visit_ForInNode(self, node: ForInNode, context: Context) -> RTResult[Value]: for it_res in it: element = res.register(it_res) - if res.should_return(): + if res.should_return() and not res.loop_should_continue and not res.loop_should_break: return res + + if res.loop_should_break: + break + + if res.loop_should_continue: + continue + assert element is not None context.symbol_table.set(var_name, element) diff --git a/examples/simple-grep.rn b/examples/simple-grep.rn index 886cfcd..9c8bc41 100644 --- a/examples/simple-grep.rn +++ b/examples/simple-grep.rn @@ -6,7 +6,7 @@ fun main() { parser.add_pos_opt("query", "String to query", required=true) parser.add_pos_opt("file", "File to query string in", required=true) parser.add_flag("--line-numbers", "-n", "Show line numbers") - parser.add_named("--max-lines", "Maximum amount of lines to show") + parser.add_named("--max-lines", "Maximum amount of lines to show", conversor=int) args = parser.parse(argv[:]) if args["--help"] { @@ -28,7 +28,7 @@ fun main() { } if not is_null(args["--max-lines"]) { - nonlocal matched_lines = matched_lines[:int(args["--max-lines"])] + nonlocal matched_lines = matched_lines[:args["--max-lines"]] } for line in matched_lines { diff --git a/stdlib/Argparser.rn b/stdlib/Argparser.rn index 45321b7..d3d7d7b 100755 --- a/stdlib/Argparser.rn +++ b/stdlib/Argparser.rn @@ -25,11 +25,14 @@ class Argparser { this.named = [] } - fun add_pos_opt(name, desc, default_=null, required=false) { + fun add_pos_opt(name, desc, default_=null, required=false, conversor=null) { if required { assert is_null(default_), "Required positional arguments cannot have a default value" } - arr_append(this.pos_opts, {"name": name, "desc": desc, "default": default_, "required": required}) + if is_null(conversor) { + nonlocal conversor = fun(x) -> x + } + arr_append(this.pos_opts, {"name": name, "desc": desc, "default": default_, "required": required, "conversor": conversor}) return this } @@ -41,12 +44,24 @@ class Argparser { return this } - fun add_named(name, desc, default_=null) { + fun add_named(name, desc, default_=null, conversor=null) { assert str_starts_with(name, "--"), "Named arguments must start with '--'" - arr_append(this.named, {"name": name, "desc": desc, "default": default_}) + if is_null(conversor) { + nonlocal conversor = fun(x) -> x + } + arr_append(this.named, {"name": name, "desc": desc, "default": default_, "conversor": conversor}) return this } + fun get_named_conversor(name) { + for named in this.named { + if named["name"] == name { + return named["conversor"] + } + } + return null + } + fun usage(program_name) { ret = "Usage: "+program_name+" \n" ret += "OPTIONS:\n" @@ -124,7 +139,12 @@ class Argparser { this.report_error(program_name, "missing value for flag: '"+arg+"'") } value = arr_pop(args, 0) - parsed[arg] = value + conversor = this.get_named_conversor(arg) + try { + parsed[arg] = conversor(value) + } catch as e { + this.report_error(program_name, "invalid value for flag: "+arg+"': " + str(e)) + } } } } else { @@ -142,7 +162,12 @@ class Argparser { this.report_error(program_name, "unexpected positional argument: '" + arg + "'") } arg_name = pos_opts[pos_opts_idx]["name"] - parsed[arg_name] = arg + conversor = pos_opts[pos_opts_idx]["conversor"] + try { + parsed[arg_name] = conversor(arg) + } catch as e { + this.report_error(program_name, "invalid value for argument: '" + arg_name + "': " + str(e)) + } required_opts[arg_name] = false nonlocal pos_opts_idx++ } From de7b02a18d6c2fe746f10f1144fdcc09c014edf6 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 17:09:59 +0200 Subject: [PATCH 10/11] Make the parameter for parse() optional --- examples/args_test.rn | 2 +- stdlib/Argparser.rn | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/args_test.rn b/examples/args_test.rn index f9f8b92..0fc5cc0 100755 --- a/examples/args_test.rn +++ b/examples/args_test.rn @@ -4,5 +4,5 @@ parser = Argparser.Argparser() parser.add_flag("--version", "-v", "Show version") parser.add_named("--output", "Output file") parser.add_pos_opt("filename", "File") -print(parser.parse(argv)) +print(parser.parse()) diff --git a/stdlib/Argparser.rn b/stdlib/Argparser.rn index d3d7d7b..dabb66b 100755 --- a/stdlib/Argparser.rn +++ b/stdlib/Argparser.rn @@ -107,7 +107,10 @@ class Argparser { return FULLNAME_INVALID } - fun parse(args) { + fun parse(args=null) { + if is_null(args) { + nonlocal args = argv[:] + } pos_opts = this.pos_opts flags = this.flags From c24d4eca08960528da80cc62d122cb1f27a45bd2 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 17:14:57 +0200 Subject: [PATCH 11/11] Fix CI --- core/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interpreter.py b/core/interpreter.py index d3f04d0..1b68bfc 100755 --- a/core/interpreter.py +++ b/core/interpreter.py @@ -525,7 +525,7 @@ def visit_ForInNode(self, node: ForInNode, context: Context) -> RTResult[Value]: element = res.register(it_res) if res.should_return() and not res.loop_should_continue and not res.loop_should_break: return res - + if res.loop_should_break: break