From abd328fd852af8523bcc7521c4feefaab8053d94 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 12:48:53 +0200 Subject: [PATCH 1/4] add: `null` datatype --- core/builtin_classes/file_object.py | 2 +- core/builtin_funcs.py | 28 +++++++++++++++------- core/datatypes.py | 37 +++++++++++++++++++++++------ core/interpreter.py | 26 ++++++++++---------- core/parser.py | 2 +- examples/operator_overloading.rn | 2 +- tests/closures.rn | 2 +- 7 files changed, 66 insertions(+), 33 deletions(-) diff --git a/core/builtin_classes/file_object.py b/core/builtin_classes/file_object.py index f1efba1..286532a 100644 --- a/core/builtin_classes/file_object.py +++ b/core/builtin_classes/file_object.py @@ -81,7 +81,7 @@ def close(ctx): res = RTResult() self = ctx.symbol_table.get("this") self.file.close() - return res.success(Number.null()) + return res.success(Null.null()) @args([]) @method diff --git a/core/builtin_funcs.py b/core/builtin_funcs.py index 7bd66e5..1ed2735 100755 --- a/core/builtin_funcs.py +++ b/core/builtin_funcs.py @@ -70,7 +70,7 @@ def execute_print(self, exec_ctx): value = exec_ctx.symbol_table.get("value") print(value) - return RTResult().success(Number.null()) + return RTResult().success(Null.null()) @args(["value"]) def execute_print_ret(self, exec_ctx): @@ -95,7 +95,7 @@ def execute_input_int(self, exec_ctx): @args([]) def execute_clear(self, exec_ctx): os.system("cls" if os.name == "nt" else "clear") - return RTResult().success(Number.null()) + return RTResult().success(Null.null()) @args(["value"]) def execute_is_number(self, exec_ctx): @@ -143,7 +143,7 @@ def execute_arr_append(self, exec_ctx): return RTResult().failure(RTError(self.pos_start, self.pos_end, "First argument must be array", exec_ctx)) array_.elements.append(value) - return RTResult().success(Number.null()) + return RTResult().success(Null.null()) @args(["array", "index"], [None, Number(-1)]) def execute_arr_pop(self, exec_ctx): @@ -181,7 +181,7 @@ def execute_arr_extend(self, exec_ctx): return RTResult().failure(RTError(self.pos_start, self.pos_end, "Second argument must be array", exec_ctx)) arrayA.elements.extend(arrayB.elements) - return RTResult().success(Number.null()) + return RTResult().success(Null.null()) @args(["array", "index"]) def execute_arr_find(self, exec_ctx): @@ -350,7 +350,7 @@ def execute_pyapi(self, exec_ctx): res.register(PyAPI(code.value).set_pos(self.pos_start, self.pos_end).set_context(self.context).pyapi(ns)) if res.should_return(): return res - return res.success(Number.null()) + return res.success(Null.null()) @args([]) def execute_sys_args(self, exec_ctx): @@ -416,12 +416,21 @@ def execute_require(self, exec_ctx): ) if should_exit: - return RTResult().success_exit(Number.null()) - return RTResult().success(Number.null()) + return RTResult().success_exit(Null.null()) + return RTResult().success(Null.null()) @args([]) def execute_exit(self, exec_ctx): - return RTResult().success_exit(Number.null()) + return RTResult().success_exit(Null.null()) + + @args(["value"]) + def execute_is_null(self, ctx): + value = ctx.symbol_table.get("value") + + if isinstance(value, Null): + return RTResult().success(Boolean(True)) + else: + return RTResult().success(Boolean(False)) def run( @@ -470,7 +479,7 @@ def create_global_symbol_table() -> SymbolTable: import core.builtin_classes as bic ret = SymbolTable() - ret.set("null", Number.null()) + ret.set("null", Null.null()) ret.set("false", Boolean.false()) ret.set("true", Boolean.true()) ret.set("print", BuiltInFunction("print")) @@ -489,6 +498,7 @@ def create_global_symbol_table() -> SymbolTable: ret.set("is_bool", BuiltInFunction("is_bool")) ret.set("is_array", BuiltInFunction("is_array")) ret.set("is_fun", BuiltInFunction("is_fun")) + ret.set("is_null", BuiltInFunction("is_null")) # Internal array methods ret.set("arr_append", BuiltInFunction("arr_append")) ret.set("arr_pop", BuiltInFunction("arr_pop")) diff --git a/core/datatypes.py b/core/datatypes.py index 193337a..9d3a820 100755 --- a/core/datatypes.py +++ b/core/datatypes.py @@ -279,10 +279,6 @@ def __str__(self) -> str: def __repr__(self) -> str: return str(self.value) - @classmethod - def null(cls) -> Number: - return cls(0) - @classmethod def one(cls) -> Number: return cls(1) @@ -820,7 +816,7 @@ def _radonify(value: object) -> Value: case False: return Boolean.false() case None: - return Number.null() + return Null.null() case _ if inspect.isfunction(value): from core.builtin_funcs import BuiltInFunction, args # Lazy import @@ -924,7 +920,7 @@ def pyapi(self, ns: HashMap) -> RTResult[Value]: self.context, ) ) - return RTResult[Value]().success(Number.null()) + return RTResult[Value]().success(Null.null()) def copy(self) -> PyAPI: copy = PyAPI(self.code) @@ -1243,7 +1239,7 @@ def execute(self, args: list[Value], kwargs: dict[str, Value]) -> RTResult[Value if res.should_return() and res.func_return_value is None: return res - ret_value = (value if self.should_auto_return else None) or res.func_return_value or Number.null() + ret_value = (value if self.should_auto_return else None) or res.func_return_value or Null.null() return res.success(ret_value) def copy(self) -> Function: @@ -1274,3 +1270,30 @@ def copy(self) -> Module: def __repr__(self) -> str: return f"" + + +class Null(Value): + def __repr__(self) -> str: + return "null" + + def copy(self) -> Null: + return self + + def is_true(self) -> bool: + return False + + def get_comparison_eq(self, other: Value) -> ResultTuple: + if isinstance(other, Null): + return Boolean.true(), None + else: + return Boolean.false(), None + + def get_comparison_ne(self, other: Value) -> ResultTuple: + if isinstance(other, Null): + return Boolean.false(), None + else: + return Boolean.true(), None + + @classmethod + def null(cls) -> Null: + return cls() diff --git a/core/interpreter.py b/core/interpreter.py index f16c4fb..ba9ecea 100755 --- a/core/interpreter.py +++ b/core/interpreter.py @@ -171,7 +171,7 @@ def visit_IncludeNode(self, node: IncludeNode, context: Context) -> RTResult[Val ) if should_exit: - return RTResult[Value]().success_exit(Number.null()) + return RTResult[Value]().success_exit(Null.null()) assert isinstance(node.module.value, str), "this could be a bug in the parser" module = Module(node.module.value, module_name, symbol_table) res = RTResult[Value]() @@ -277,7 +277,7 @@ def visit_IfNode(self, node: IfNode, context: Context) -> RTResult[Value]: if res.should_return(): return res assert expr_value is not None - return res.success(Number.null() if should_return_null else expr_value) + return res.success(Null.null() if should_return_null else expr_value) if node.else_case is not None: expr, should_return_null = node.else_case @@ -285,9 +285,9 @@ def visit_IfNode(self, node: IfNode, context: Context) -> RTResult[Value]: if res.should_return(): return res assert expr_value is not None - return res.success(Number.null() if should_return_null else expr_value) + return res.success(Null.null() if should_return_null else expr_value) - return res.success(Number.null()) + return res.success(Null.null()) def visit_ForNode(self, node: ForNode, context: Context) -> RTResult[Value]: res = RTResult[Value]() @@ -363,7 +363,7 @@ def condition(): elements.append(value) return res.success( - Number.null() + Null.null() if node.should_return_null else Array(elements).set_context(context).set_pos(node.pos_start, node.pos_end) ) @@ -395,7 +395,7 @@ def visit_WhileNode(self, node: WhileNode, context: Context) -> RTResult[Value]: elements.append(value) return res.success( - Number.null() + Null.null() if node.should_return_null else Array(elements).set_context(context).set_pos(node.pos_start, node.pos_end) ) @@ -473,7 +473,7 @@ def visit_ReturnNode(self, node: ReturnNode, context: Context) -> RTResult[Value if res.should_return(): return res else: - value = Number.null() + value = Null.null() assert value is not None return res.success_return(value) @@ -502,9 +502,9 @@ def visit_TryNode(self, node: TryNode, context): res.error.pos_start, res.error.pos_end, res.error.details, res.error.context, handled_error ) ) - return res.success(Number.null()) + return res.success(Null.null()) else: - return res.success(Number.null()) + return res.success(Null.null()) def visit_ForInNode(self, node: ForInNode, context: Context) -> RTResult[Value]: res = RTResult[Value]() @@ -536,7 +536,7 @@ def visit_ForInNode(self, node: ForInNode, context: Context) -> RTResult[Value]: elements.append(element) if should_return_null: - return res.success(Number.null()) + return res.success(Null.null()) return res.success(Array(elements).set_context(context).set_pos(node.pos_start, node.pos_end)) def visit_SliceGetNode(self, node: SliceGetNode, context: Context) -> RTResult[Value]: @@ -766,18 +766,18 @@ def visit_SwitchNode(self, node: SwitchNode, context: Context) -> RTResult[Value if res.should_fallthrough: should_continue = True continue - return res.success(Number.null()) + return res.success(Null.null()) should_continue = False if node.default is not None: res.register(self.visit(node.default, context)) if res.should_return(): return res - return res.success(Number.null()) + return res.success(Null.null()) return res.failure(RTError(node.pos_start, node.subject_node.pos_end, "No cases matched", context)) def visit_FallthroughNode(self, node: FallthroughNode, context: Context) -> RTResult[Value]: - return RTResult[Value]().success(Number.null()).fallthrough() + return RTResult[Value]().success(Null.null()).fallthrough() def visit_AttrAccessNode(self, node: AttrAccessNode, context: Context) -> RTResult[Value]: res = RTResult[Value]() diff --git a/core/parser.py b/core/parser.py index a555895..4d29e87 100755 --- a/core/parser.py +++ b/core/parser.py @@ -1528,7 +1528,7 @@ def success_exit(self, exit_value: T) -> RTResult[T]: def fallthrough(self) -> RTResult[T]: # No `self.reset()` because this is meant to be used in conjunction with other methods - # e.g. `res.success(Number.null()).fallthrough()` + # e.g. `res.success(Null.null()).fallthrough()` self.should_fallthrough = True return self diff --git a/examples/operator_overloading.rn b/examples/operator_overloading.rn index a60788f..2405261 100644 --- a/examples/operator_overloading.rn +++ b/examples/operator_overloading.rn @@ -13,7 +13,7 @@ class WrongOp { } fun dump(name = null) { - if name == null { + if is_null(name) { print("WrongOp("+this.x+")") } else { print(name+" = WrongOp("+this.x+")") diff --git a/tests/closures.rn b/tests/closures.rn index b251be1..57a6ec2 100644 --- a/tests/closures.rn +++ b/tests/closures.rn @@ -2,7 +2,7 @@ fun make_var() { val = null return fun(new_val=null) { - if new_val == null { + if is_null(new_val) { return val } else { nonlocal val = new_val From 6a36ceee2ea380be4a9de10541ef9478f3f08038 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 12:49:07 +0200 Subject: [PATCH 2/4] refactor: move `Requests` test into examples Tests should not require an internet connection. --- {tests => examples}/requests-tests.rn | 0 tests/requests-tests.rn.json | 1 - 2 files changed, 1 deletion(-) rename {tests => examples}/requests-tests.rn (100%) delete mode 100644 tests/requests-tests.rn.json diff --git a/tests/requests-tests.rn b/examples/requests-tests.rn similarity index 100% rename from tests/requests-tests.rn rename to examples/requests-tests.rn diff --git a/tests/requests-tests.rn.json b/tests/requests-tests.rn.json deleted file mode 100644 index 40801ef..0000000 --- a/tests/requests-tests.rn.json +++ /dev/null @@ -1 +0,0 @@ -{"code": 0, "stdout": "API testing passed!\n", "stderr": ""} \ No newline at end of file From 48d76e53ea92a5de6f98abcaa7a1615c935f0676 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 12:51:37 +0200 Subject: [PATCH 3/4] Re-record tests --- tests/closures.rn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/closures.rn.json b/tests/closures.rn.json index 4014694..0740647 100644 --- a/tests/closures.rn.json +++ b/tests/closures.rn.json @@ -1 +1 @@ -{"code": 0, "stdout": "0\n0\n69\n0\n69\n420\n", "stderr": ""} \ No newline at end of file +{"code": 0, "stdout": "null\nnull\n69\nnull\n69\n420\n", "stderr": ""} \ No newline at end of file From 6132b2e8a1130e8016708661e5507171879d3b18 Mon Sep 17 00:00:00 2001 From: angelcaru Date: Wed, 1 May 2024 13:17:36 +0200 Subject: [PATCH 4/4] Revert "refactor: move `Requests` test into examples" This reverts commit 6a36ceee2ea380be4a9de10541ef9478f3f08038. --- {examples => tests}/requests-tests.rn | 0 tests/requests-tests.rn.json | 1 + 2 files changed, 1 insertion(+) rename {examples => tests}/requests-tests.rn (100%) create mode 100644 tests/requests-tests.rn.json diff --git a/examples/requests-tests.rn b/tests/requests-tests.rn similarity index 100% rename from examples/requests-tests.rn rename to tests/requests-tests.rn diff --git a/tests/requests-tests.rn.json b/tests/requests-tests.rn.json new file mode 100644 index 0000000..40801ef --- /dev/null +++ b/tests/requests-tests.rn.json @@ -0,0 +1 @@ +{"code": 0, "stdout": "API testing passed!\n", "stderr": ""} \ No newline at end of file