Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a primitive version of the Argparse module #131

Merged
merged 12 commits into from
May 1, 2024
4 changes: 2 additions & 2 deletions core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
7 changes: 6 additions & 1 deletion core/builtin_classes/file_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)])
Expand Down
11 changes: 0 additions & 11 deletions core/builtin_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(Array(argv))
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
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 5 additions & 0 deletions core/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 8 additions & 1 deletion core/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion core/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion core/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions core/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 5 additions & 18 deletions examples/args_test.rn
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
import 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())

# 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()
44 changes: 44 additions & 0 deletions examples/simple-grep.rn
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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", 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", conversor=int)
args = parser.parse(argv[:])

if args["--help"] {
print(parser.usage(argv[0]))
exit()
}

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[: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()

71 changes: 49 additions & 22 deletions radon.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,6 +16,8 @@
pass

import core as base_core
from core.parser import Context
from core.lexer import Position


def shell() -> None:
Expand Down Expand Up @@ -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, "<argv>", "<argv>")
base_core.global_symbol_table.set("argv", base_core.radonify(argv, pos, pos, Context("<global>")))
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())
Expand All @@ -81,18 +111,15 @@ def main(argv: list[str]) -> None:
if should_exit:
exit()

elif args.command:
(result, error, should_exit) = base_core.run("<stdin>", args.command)
elif command is not None:
(result, error, should_exit) = base_core.run("<cli>", 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)
Loading