Skip to content
This repository was archived by the owner on Jun 7, 2020. It is now read-only.

Commit 6e14bc4

Browse files
authored
Merge pull request #52 from thejoeejoee/feature/escapes-solution
Final string escapes solution
2 parents 83fc02d + 89058e0 commit 6e14bc4

File tree

7 files changed

+67
-48
lines changed

7 files changed

+67
-48
lines changed

ifj2017/interpreter/exceptions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ def __str__(self):
5050
return 'Unknown data type error{}'.format(self.on_line)
5151

5252

53+
class StringError(BaseInterpreterError):
54+
def __init__(self, source, index):
55+
self._source = source
56+
self._index = index
57+
58+
def __str__(self):
59+
return 'Invalid index {} on string "{}"{}'.format(self._index, self._source, self.on_line)
60+
61+
5362
class EmptyDataStackError(BaseInterpreterError):
5463
def __str__(self):
5564
return 'Empty data stack error{}'.format(self.on_line)

ifj2017/interpreter/instruction.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,7 @@ def operands(self):
151151
state.get_value(op1),
152152
))),
153153
'STRLEN': State.str_len,
154-
'GETCHAR': lambda state, target, string, index: state.set_value(
155-
target,
156-
state.get_value(string)[state.get_value(index)]
157-
),
154+
'GETCHAR': State.get_char,
158155
'SETCHAR': State.set_char,
159156

160157
'INT2FLOAT': lambda state, op0, op1: state.set_value(op1, float(state.get_value(op1))),

ifj2017/interpreter/operand.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ def float(value):
2222
return float_.fromhex(value)
2323

2424

25+
ESCAPE_RE = re.compile(r'\\([0-9]{3})')
26+
27+
28+
def unquote_escape_sequences(value):
29+
def __(m):
30+
# magic for decimal \ddd to octal \ooo
31+
return chr(int(m.group(1)))
32+
33+
return ESCAPE_RE.sub(__, value)
34+
35+
2536
class TypeOperand(IntEnum):
2637
VARIABLE = 1
2738
CONSTANT = 2
@@ -75,20 +86,22 @@ def __init__(self, value):
7586

7687
raise InvalidCodeException(InvalidCodeException.INVALID_OPERAND)
7788

78-
def _resolve_constant(self, constant_match):
79-
# type: (Match) -> None
80-
type_, value = constant_match.groups()
89+
def _resolve_constant(self, constant_match: Match[str]) -> None:
90+
type_, value = constant_match.groups() # type: str, str
91+
type_ = type_.lower().strip()
8192
try:
82-
self.value = self.CONSTANT_MAPPING.get(type_.lower())(value)
83-
if type_.lower() == self.CONSTANT_MAPPING_REVERSE.get(bool):
93+
self.value = self.CONSTANT_MAPPING.get(type_)(value)
94+
if type_ == self.CONSTANT_MAPPING_REVERSE.get(bool):
8495
self.value = self.BOOL_LITERAL_MAPPING.get(value.lower())
96+
elif type_ == self.CONSTANT_MAPPING_REVERSE.get(str):
97+
self.value = unquote_escape_sequences(value=self.value)
8598
except ValueError:
8699
pass
87100
if self.value is None:
88101
raise InvalidCodeException(type_=InvalidCodeException.INVALID_OPERAND)
89102
self.type = TypeOperand.CONSTANT
90103

91-
def _resolve_variable(self, variable_match):
104+
def _resolve_variable(self, variable_match: Match[str]) -> None:
92105
# type: (Match) -> None
93106
frame, name = variable_match.groups()
94107
if not (frame and name):
@@ -97,8 +110,8 @@ def _resolve_variable(self, variable_match):
97110
self.name = name
98111
self.type = TypeOperand.VARIABLE
99112

100-
def _resolve_type(self, type_match):
101-
# type: (Match) -> None
113+
def _resolve_type(self, type_match: Match[str]) -> None:
114+
# type: (Match[str]) -> None
102115
self.data_type = type_match.group(1).lower()
103116
if self.data_type not in self.CONSTANT_MAPPING:
104117
raise InvalidCodeException(type_=InvalidCodeException.INVALID_OPERAND)

ifj2017/interpreter/state.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# coding=utf-8
2-
import codecs
32
import logging
43
import re
54
from io import StringIO
5+
from typing import Optional, Union
66

7-
from ifj2017.interpreter.exceptions import UnknownDataTypeError
7+
from ifj2017.interpreter.exceptions import UnknownDataTypeError, StringError
88
from .exceptions import EmptyDataStackError, UndefinedVariableError, UndeclaredVariableError, \
99
FrameError, UnknownLabelError, InvalidReturnError, InvalidOperandTypeError
1010
from .operand import Operand, TypeOperand
@@ -61,8 +61,7 @@ def pop_frame(self):
6161
self.temp_frame = self.frame_stack[-1]
6262
self.frame_stack = self.frame_stack[:-1]
6363

64-
def get_value(self, value):
65-
# type: (Operand) -> object
64+
def get_value(self, value: Optional[Operand]) -> Union[None, int, str, float]:
6665
if value is None:
6766
# variable declaration
6867
return None
@@ -153,8 +152,18 @@ def set_char(self, where, index, from_):
153152
changed[self.get_value(index)] = self.get_value(from_)[0]
154153
self.set_value(where, changed)
155154

155+
def get_char(self, target, string, index):
156+
source = self.get_value(string)
157+
try:
158+
self.set_value(
159+
target,
160+
source[self.get_value(index)]
161+
)
162+
except IndexError:
163+
raise StringError(source, self.get_value(index))
164+
156165
def str_len(self, target, string):
157-
return self.set_value(target, len(self.ESCAPE_RE.sub('_', self.get_value(string))))
166+
return self.set_value(target, len(('_', self.get_value(string))))
158167

159168
def read(self, to, type_):
160169
# type: (Operand, Operand) -> None
@@ -211,22 +220,7 @@ def write(self, op):
211220
elif isinstance(value, float):
212221
rendered = '{: g}'.format(value)
213222

214-
def __(m):
215-
# magic for decimal \ddd to octal \ooo
216-
return '\\{}'.format(
217-
oct(
218-
int(
219-
m.group(1)
220-
)
221-
).lstrip('0o').zfill(3)
222-
)
223-
224-
self.stdout.write(
225-
codecs.decode(
226-
self.ESCAPE_RE.sub(repl=__, string=rendered),
227-
'unicode_escape'
228-
)
229-
)
223+
self.stdout.write(rendered)
230224

231225
def string_to_int_stack(self):
232226
index = self.pop_stack()

ifj2017/test/loader.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,21 +185,23 @@ def _parse_wildcards(tests_wildcards):
185185
return wildcards
186186

187187
def _load_test_file(self, section_dir, test_name, type_):
188-
return ((
189-
self.load_file(
190-
path.join(
191-
section_dir,
192-
'.'.join((test_name, type_))
193-
),
194-
allow_fail=True
195-
) or '').replace('\r\n', '\n').replace('\r', '\n') # normalize newlines to \n
196-
) or ''
188+
# splitlines is not possible due endlines at and of file
189+
return (
190+
(
191+
self.load_file(
192+
path.join(
193+
section_dir,
194+
'.'.join((test_name, type_))
195+
),
196+
allow_fail=True
197+
) or '').replace('\r\n', '\n').replace('\r', '\n') # normalize newlines to \n
198+
) or ''
197199

198200
@staticmethod
199201
def load_file(file, allow_fail=False):
200202
assert allow_fail or (path.isfile(file) and os.access(file, os.R_OK))
201203
try:
202-
with open(file, 'rb') as f:
204+
with open(file, 'rbU') as f:
203205
return f.read().decode('utf-8')
204206
except IOError:
205207
if not allow_fail:

ifj2017/test/runner.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,18 +265,22 @@ def _interpret(self, code, test_info):
265265

266266
process = Popen([self._interpreter_binary, '-v', code_temp], stdout=PIPE, stdin=PIPE, stderr=PIPE)
267267
try:
268-
out, err = process.communicate(input=bytes(test_info.stdin, encoding='utf-8'),
269-
timeout=test_info.timeout)
268+
out, err = process.communicate(
269+
input=bytes(test_info.stdin, encoding='utf-8'),
270+
timeout=test_info.timeout
271+
)
270272
except (TimeoutError, TimeoutExpired):
271273
process.kill()
272274
raise
273275
finally:
274276
os.remove(code_temp)
275277
# err has non-escaped characters
276-
return out.decode('ascii'), err.decode('unicode_escape'), process.returncode
278+
return out.decode('raw_unicode_escape'), err.decode('raw_unicode_escape'), process.returncode
277279

278280
def _interpret_price(self, code, test_info):
279-
interpreter = Interpreter(code=code, state_kwargs=dict(stdin=StringIO(test_info.stdin)))
281+
interpreter = Interpreter(code=code, state_kwargs=dict(
282+
stdin=StringIO(test_info.stdin),
283+
))
280284
return interpreter.run()
281285

282286
def _save_report(self, test_info, report):

ifj2017/tests/01_basic/tests.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
{
8585
"info": "Print string with char 238",
8686
"code": "scope \n print !\"\\238\"; \n end scope",
87-
"stdout": "Todo"
87+
"stdout": "\u00ee"
8888
},
8989
{
9090
"name": "29",

0 commit comments

Comments
 (0)