From 6510f07cdca39f0c57e0ad25870a2ebff5936594 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 7 Apr 2024 02:01:24 +0900 Subject: [PATCH 1/4] rename Link to Node --- aheui/aheui.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aheui/aheui.py b/aheui/aheui.py index e399dda..0cf145f 100644 --- a/aheui/aheui.py +++ b/aheui/aheui.py @@ -34,7 +34,7 @@ def get_location(pc, stackok, is_queue, program): MINUS1 = bigint.fromlong(-1) -class Link(object): +class Node(object): """Element unit for stack and queue.""" def __init__(self, next, value=MINUS1): @@ -102,7 +102,7 @@ def __init__(self): def push(self, value): # assert(isinstance(value, bigint.Int)) - node = Link(self.head, value) + node = Node(self.head, value) self.head = node self.size += 1 @@ -121,7 +121,7 @@ def _put_value(self, value): class Queue(LinkedList): def __init__(self): - self.tail = Link(None) + self.tail = Node(None) self.head = self.tail self.size = 0 @@ -129,14 +129,14 @@ def push(self, value): # assert(isinstance(value, bigint.Int)) tail = self.tail tail.value = value - new = Link(None) + new = Node(None) tail.next = new self.tail = new self.size += 1 def dup(self): head = self.head - node = Link(head, head.value) + node = Node(head, head.value) self.head = node self.size += 1 @@ -156,7 +156,7 @@ def __init__(self): def push(self, value): # assert(isinstance(value, bigint.Int)) - node = Link(self.head, value) + node = Node(self.head, value) self.head = node self.size += 1 self.last_push = value From 86085feb1c5ef3c9f0cc506a77ab886532fc876f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 7 Apr 2024 20:54:43 +0900 Subject: [PATCH 2/4] =?UTF-8?q?aheui.aheuis=20=EC=83=9D=EA=B8=B0=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aheui/option.py | 17 ++++++++++------- tests/test_option.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 tests/test_option.py diff --git a/aheui/option.py b/aheui/option.py index aff6e5f..38a550c 100644 --- a/aheui/option.py +++ b/aheui/option.py @@ -64,10 +64,11 @@ def kwarg_or_environ_int(kwargs, environ, arg_key, env_key, default): return value -def process_options(argv, environ): - def open_r(filename): - return os.open(filename, os.O_RDONLY, 0o777) +def open_input(filename): + return os.open(filename, os.O_RDONLY, 0o777) + +def process_options(argv, environ): kwargs, args = parser.parse_args(argv) if not args: raise SystemExit() @@ -82,7 +83,7 @@ def open_r(filename): fp = 0 contents = compile.read(fp) else: - fp = open_r(filename) + fp = open_input(filename) contents = compile.read(fp) os.close(fp) else: @@ -115,10 +116,12 @@ def open_r(filename): if need_aheuic: aheuic_output = filename - if aheuic_output.endswith('.aheui'): - aheuic_output += 'c' + if filename.endswith('.aheui'): + aheuic_output = filename + 'c' + elif filename.endswith('.aheuis'): + aheuic_output = filename[:-1] + 'c' else: - aheuic_output += '.aheuic' + aheuic_output = filename + '.aheuic' else: aheuic_output = None diff --git a/tests/test_option.py b/tests/test_option.py new file mode 100644 index 0000000..6995e51 --- /dev/null +++ b/tests/test_option.py @@ -0,0 +1,35 @@ +import pytest +from aheui.option import process_options + + +def test_option_filename(mocker): + mocker.patch('os.open', return_value=0) + mocker.patch('os.close', return_value=None) + mocker.patch('aheui.compile.read', return_value=b'') + + assert ('', 'text', b'', '1', 'run', None, False, '-', 3, -1) == process_options(['aheui-c', '-'], {}) + assert ('', 'text', b'', '1', 'run', 'x.aheuic', False, '-', 3, -1) == process_options(['aheui-c', 'x'], {}) + assert ('', 'text', b'', '1', 'run', 'x.aheuic', False, '-', 3, -1) == process_options(['aheui-c', 'x.aheui'], {}) + assert ('', 'asm', b'', '1', 'run', 'x.aheuic', False, '-', 3, -1) == process_options(['aheui-c', 'x.aheuis'], {}) + assert ('', 'bytecode', b'', '1', 'run', None, False, '-', 3, -1) == process_options(['aheui-c', 'x.aheuic'], {}) + + assert ('', 'text', b'', '1', 'run', None, False, '-', 3, -1) == process_options(['aheui-c', '-', '--output=-'], {}) + assert ('', 'text', b'', '1', 'run', None, False, 'out', 3, -1) == process_options(['aheui-c', '-', '--output=out'], {}) + + +def test_option_cmd(mocker): + mocker.patch('os.open', return_value=0) + mocker.patch('os.close', return_value=None) + mocker.patch('aheui.compile.read', return_value=b'') + + heui = '희'.encode('utf-8') + assert (heui, 'text', heui, '1', 'run', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희'], {}) + assert (heui, 'text', heui, '1', 'run', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희', '--output=-'], {}) + assert (heui, 'text', heui, '1', 'run', None, False, 'out', 3, -1) == process_options(['aheui-c', '-c', '희', '--output=out'], {}) + # with pytest.raises(SystemExit): + # process_options(['aheui-c', '-c'], {}) + with pytest.raises(SystemExit): + process_options(['aheui-c', '-c', '희', 'x'], {}) + assert (heui, 'text', heui, '1', 'asm', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희', '--target=asm'], {}) + assert (heui, 'text', heui, '1', 'asm', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희', '--target=asm', '--output=-'], {}) + assert (heui, 'text', heui, '1', 'asm', None, False, 'out', 3, -1) == process_options(['aheui-c', '-c', '희', '--target=asm', '--output=out'], {}) From b8a637a1319aba6db9646e921ff32bd5832136b0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 7 Apr 2024 22:45:38 +0900 Subject: [PATCH 3/4] option tests --- aheui/_argparse.py | 25 +++++++++++++------- aheui/aheui.py | 8 +++++-- aheui/option.py | 56 ++++++++++++++++++++++++++++++++++---------- setup.py | 2 +- tests/test_option.py | 7 +++--- 5 files changed, 70 insertions(+), 28 deletions(-) diff --git a/aheui/_argparse.py b/aheui/_argparse.py index df196f9..b1056b1 100644 --- a/aheui/_argparse.py +++ b/aheui/_argparse.py @@ -25,12 +25,14 @@ class ArgumentNotInChoicesError(ParserError): __description__ = 'argument is not in choices: ' -class InformationException(ParserError): - __description__ = '' +class InformationException(Exception): + def __init__(self, desc=''): + self.desc = desc + class HelpException(InformationException): - __description__ = '' + pass class ArgumentParser(object): @@ -88,7 +90,10 @@ def _parse_args(self, args): done = True elif name.startswith('-'): if name == arg: - arg = args[idx + 1] + try: + arg = args[idx + 1] + except IndexError: + raise TooFewArgumentError(name) parsed[dest] = arg idx += 2 else: @@ -113,7 +118,7 @@ def parse_args(self, args): try: return self._parse_args(args) except HelpException: - os.write(2, 'usage: %s [option] ... file\n\n' % self.kwargs.get('prog', args[0])) + os.write(2, 'usage: %s [option] ... file\n\n' % get_prog(args[0])) for names, opt in self.arguments: name = names[0] if names[0] == names[1] else ('%s,%s' % names[0:2]) os.write(2, '%s%s: %s' % (name, ' ' * (12 - len(name)), opt['description'])) @@ -125,9 +130,11 @@ def parse_args(self, args): os.write(2, '\n') os.write(2, opt['full_description']) os.write(2, '\n') + raise except InformationException as e: os.write(2, '%s\n' % e.desc) - except ParserError as e: - prog = self.kwargs.get('prog', args[0]) - os.write(2, '%s: error: %s\n' % (prog, e.message())) - return {}, [] + raise + + +def get_prog(arg0): + return arg0.rsplit('/', 1)[-1] diff --git a/aheui/aheui.py b/aheui/aheui.py index 0cf145f..b72ccf3 100644 --- a/aheui/aheui.py +++ b/aheui/aheui.py @@ -6,9 +6,10 @@ import os from aheui import const as c +from aheui._argparse import InformationException, get_prog from aheui._compat import jit, unichr, ord, _unicode, bigint, PYR from aheui import compile -from aheui.option import process_options +from aheui.option import process_options, OptionError from aheui.warning import WarningPool @@ -483,7 +484,10 @@ def prepare_compiler(contents, opt_level=2, source='code', aheuic_output=None, a def entry_point(argv): try: cmd, source, contents, str_opt_level, target, aheuic_output, comment_aheuis, output, warning_limit, trace_limit = process_options(argv, os.environ) - except SystemExit: + except InformationException: + return 0 + except OptionError as e: + os.write(errfp, b"%s: error: %s\n" % (get_prog(argv[0]), e.message())) return 1 warnings.limit = warning_limit diff --git a/aheui/option.py b/aheui/option.py index 38a550c..d22f160 100644 --- a/aheui/option.py +++ b/aheui/option.py @@ -3,7 +3,7 @@ from __future__ import absolute_import import os -from aheui._argparse import ArgumentParser +from aheui._argparse import ArgumentParser, ParserError from aheui._compat import bigint, PY3 from aheui.version import VERSION from aheui import compile @@ -37,6 +37,39 @@ parser.add_argument('--help', '-h', narg='-1', default='no', description='Show this help text') +class OptionError(Exception): + pass + + +class ParsingError(OptionError): + def __init__(self, msg): + self.args = (msg,) + + def message(self): + return self.args[0] + + +class IntOptionParsingError(OptionError): + def __init__(self, key, value): + self.args = (key, value) + def message(self): + return 'The value of %s="%s" is not a valid integer' % self.args + + +class SourceError(Exception): + pass + + +class NoInputError(Exception): + def message(self): + return "no input files" + + +class CommandConflictInputFileError(Exception): + def message(self): + return "--cmd,-c and input file cannot be used together" + + def kwarg_or_environ(kwargs, environ, arg_key, env_key): if arg_key in kwargs and kwargs[arg_key] != '': return (1, kwargs[arg_key]) @@ -54,13 +87,11 @@ def kwarg_or_environ_int(kwargs, environ, arg_key, env_key, default): value = int(arg) except ValueError: if source == 1: - msg = b'The value of --%s="%s" is not a valid integer\n' % (arg_key, arg) + raise IntOptionParsingError('--' + arg_key, arg) elif source == 2: - msg = b'The value %s="%s" is not a valid integer\n' % (env_key, arg) + raise IntOptionParsingError(env_key, arg) else: assert False - os.write(2, msg) - raise return value @@ -69,15 +100,15 @@ def open_input(filename): def process_options(argv, environ): - kwargs, args = parser.parse_args(argv) - if not args: - raise SystemExit() + try: + kwargs, args = parser.parse_args(argv) + except ParserError as e: + raise ParsingError(e.message()) cmd = kwargs['cmd'] if cmd == '': if len(args) != 2: - os.write(2, b'aheui: error: no input files\n') - raise SystemExit() + raise NoInputError() filename = args[1] if filename == '-': fp = 0 @@ -88,8 +119,7 @@ def process_options(argv, environ): os.close(fp) else: if len(args) != 1: - os.write(2, b'aheui: error: --cmd,-c but input file found\n') - raise SystemExit() + raise CommandConflictInputFileError if PY3: cmd = cmd.encode('utf-8') contents = cmd @@ -145,7 +175,7 @@ def process_options(argv, environ): elif target == 'run': output = '-' else: - os.write(2, b'aheui: error: --target,-t must be one of "bytecode", "asm", "asm+comment", "run"\n') # noqa: E501 + assert False # must be handled by argparse raise SystemExit() warning_limit = kwarg_or_environ_int(kwargs, environ, 'warning-limit', 'RPAHEUI_WARNING_LIMIT', 3) diff --git a/setup.py b/setup.py index 7c22fc7..29de84c 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ def get_readme(): tests_require = [ - 'ruff', 'tox', 'pytest>=3.0.1', + 'ruff', 'tox', 'pytest>=3.0.1', 'pytest-mock' ] setup( diff --git a/tests/test_option.py b/tests/test_option.py index 6995e51..a0bfcd5 100644 --- a/tests/test_option.py +++ b/tests/test_option.py @@ -1,4 +1,5 @@ import pytest +from aheui import option from aheui.option import process_options @@ -26,9 +27,9 @@ def test_option_cmd(mocker): assert (heui, 'text', heui, '1', 'run', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희'], {}) assert (heui, 'text', heui, '1', 'run', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희', '--output=-'], {}) assert (heui, 'text', heui, '1', 'run', None, False, 'out', 3, -1) == process_options(['aheui-c', '-c', '희', '--output=out'], {}) - # with pytest.raises(SystemExit): - # process_options(['aheui-c', '-c'], {}) - with pytest.raises(SystemExit): + with pytest.raises(option.ParsingError): + process_options(['aheui-c', '-c'], {}) + with pytest.raises(option.CommandConflictInputFileError): process_options(['aheui-c', '-c', '희', 'x'], {}) assert (heui, 'text', heui, '1', 'asm', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희', '--target=asm'], {}) assert (heui, 'text', heui, '1', 'asm', None, False, '-', 3, -1) == process_options(['aheui-c', '-c', '희', '--target=asm', '--output=-'], {}) From 9cb38800d3088832745caa163dcb507cbd07a115 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 7 Apr 2024 23:14:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?run=EC=9D=B4=20=EC=95=84=EB=8B=90=EB=95=8C?= =?UTF-8?q?=20--output=EC=9D=B4=20=EC=A3=BC=EC=96=B4=EC=A7=80=EB=A9=B4=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aheui/aheui.py | 13 ++++++------- aheui/option.py | 10 +++++++--- aheui/warning.py | 38 ++++++++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/aheui/aheui.py b/aheui/aheui.py index b72ccf3..f04962c 100644 --- a/aheui/aheui.py +++ b/aheui/aheui.py @@ -10,7 +10,7 @@ from aheui._compat import jit, unichr, ord, _unicode, bigint, PYR from aheui import compile from aheui.option import process_options, OptionError -from aheui.warning import WarningPool +from aheui.warning import NoRpythonWarning, WriteUtf8RangeWarning, warnings def get_location(pc, stackok, is_queue, program): @@ -283,7 +283,7 @@ def write_number(value_str): os.write(outfp, value_str) -def write_utf8(warnings, value): +def write_utf8(value): REPLACE_CHAR = unichr(0xfffd).encode('utf-8') if bigint.is_unicodepoint(value): @@ -296,8 +296,8 @@ def write_utf8(warnings, value): os.write(outfp, bytes) -def warn_utf8_range(warnings, value): - warnings.warn(b'write-utf8-range', value) +def warn_utf8_range(value): + warnings.warn(WriteUtf8RangeWarning, value) os.write(outfp, unichr(0xfffd).encode('utf-8')) class Program(object): @@ -328,7 +328,6 @@ def get_label(self, pc): outfp = 1 errfp = 2 -warnings = WarningPool() def mainloop(program, debug): @@ -422,7 +421,7 @@ def mainloop(program, debug): write_number(bigint.str(r)) elif op == c.OP_POPCHAR: r = selected.pop() - write_utf8(warnings, r) + write_utf8(r) elif op == c.OP_PUSHNUM: num = read_number() selected.push(num) @@ -499,7 +498,7 @@ def entry_point(argv): outfp = 1 if output == '-' else open_w(output) if target == 'run': if not PYR: - warnings.warn(b'no-rpython') + warnings.warn(NoRpythonWarning) program = Program(compiler.lines, compiler.label_map) exitcode = mainloop(program, compiler.debug) elif target in ['asm', 'asm+comment']: diff --git a/aheui/option.py b/aheui/option.py index d22f160..3eae681 100644 --- a/aheui/option.py +++ b/aheui/option.py @@ -6,6 +6,7 @@ from aheui._argparse import ArgumentParser, ParserError from aheui._compat import bigint, PY3 from aheui.version import VERSION +from aheui.warning import CommandLineArgumentWarning, warnings from aheui import compile @@ -56,16 +57,16 @@ def message(self): return 'The value of %s="%s" is not a valid integer' % self.args -class SourceError(Exception): +class SourceError(OptionError): pass -class NoInputError(Exception): +class NoInputError(SourceError): def message(self): return "no input files" -class CommandConflictInputFileError(Exception): +class CommandConflictInputFileError(SourceError): def message(self): return "--cmd,-c and input file cannot be used together" @@ -177,6 +178,9 @@ def process_options(argv, environ): else: assert False # must be handled by argparse raise SystemExit() + else: + if target == 'run': + warnings.warn(CommandLineArgumentWarning, b'--target=run always ignores --output') warning_limit = kwarg_or_environ_int(kwargs, environ, 'warning-limit', 'RPAHEUI_WARNING_LIMIT', 3) trace_limit = kwarg_or_environ_int(kwargs, environ, 'trace-limit', 'RPAHEUI_TRACE_LIMIT', -1) diff --git a/aheui/warning.py b/aheui/warning.py index e8f7265..0060f63 100644 --- a/aheui/warning.py +++ b/aheui/warning.py @@ -5,36 +5,46 @@ class Warning(object): - def __init__(self, name, message): - self.name = name - self.message = message - def format(self, *args): - return self.message % args + return self.MESSAGE % args + + +class NoRpythonWarning(Warning): + MESSAGE = b"[Warning:VirtualMachine] Running without rlib/jit." + + +class CommandLineArgumentWarning(Warning): + MESSAGE = b"[Warning:CommandLine] Invalid command line argument is ignored: %s." + + +class WriteUtf8RangeWarning(Warning): + MESSAGE = b'[Warning:UndefinedBehavior:write-utf8-range] value %x is out of unicode codepoint range.' WARNING_LIST = [ - Warning(b'no-rpython', b"[Warning:VirtualMachine] Running without rlib/jit.\n"), - Warning(b'write-utf8-range', b'[Warning:UndefinedBehavior:write-utf8-range] value %x is out of unicode codepoint range.'), + NoRpythonWarning(), + CommandLineArgumentWarning(), + WriteUtf8RangeWarning(), ] class WarningPool(object): def __init__(self): self.limit = -1 - self.warnings = {} self.counters = {} for w in WARNING_LIST: - self.warnings[w.name] = w - self.counters[w.name] = 0 + self.counters[type(w).__name__] = 0 @jit.dont_look_inside - def warn(self, name, *args): - warning = self.warnings[name] + def warn(self, warning, *args): + name = warning.__name__ if self.limit != -1 and self.limit <= self.counters[name]: return self.counters[name] = self.counters[name] + 1 - os.write(2, warning.format(*args)) + os.write(2, warning().format(*args)) os.write(2, b'\n') if self.limit != -1 and self.limit <= self.counters[name]: - os.write(2, b"[Warning:Meta] The warning '%s' has reached the limit %d and will be suppressed\n" % (warning.name, self.limit)) + os.write(2, b"[Warning:Meta] The warning '%s' has reached the limit %d and will be suppressed\n" % (name, self.limit)) + + +warnings = WarningPool()