Skip to content

Commit 0c4239b

Browse files
committed
Merge branch 'main' of github.com:loda-lang/loda-python
2 parents b8e25c0 + b2a180f commit 0c4239b

4 files changed

Lines changed: 320 additions & 3 deletions

File tree

loda/lang/operation.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,11 @@ class Type(Enum):
7575
LPB = 29 # loop begin
7676
LPE = 30 # loop end
7777
CLR = 31 # clear
78-
SEQ = 32 # sequence
79-
DBG = 33 # debug
78+
FIL = 32 # fill
79+
ROL = 33 # rotate left
80+
ROR = 34 # rotate right
81+
SEQ = 35 # sequence
82+
DBG = 36 # debug
8083

8184
type: Type
8285
"""Type of this operation."""

loda/runtime/interpreter.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from loda.lang import Operand, Operation, Program
44
from loda.oeis import ProgramCache
5-
from .operations import exec_arithmetic
5+
from .operations import exec_arithmetic, clr, fil, rol, ror
66

77

88
class Interpreter:
@@ -159,6 +159,34 @@ def __run(self, p: Program, mem: dict) -> int:
159159
self.__set(op.target, seq_result, mem_tmp, op)
160160
steps += seq_steps
161161

162+
elif op.type == Operation.Type.CLR:
163+
# clear memory range
164+
length = self.__get(op.source, mem_tmp)
165+
start = self.__get(op.target, mem_tmp, get_address=True)
166+
self.__check_max_memory(length, op)
167+
clr(mem_tmp, start, length)
168+
169+
elif op.type == Operation.Type.FIL:
170+
# fill memory range
171+
length = self.__get(op.source, mem_tmp)
172+
start = self.__get(op.target, mem_tmp, get_address=True)
173+
self.__check_max_memory(length, op)
174+
fil(mem_tmp, start, length)
175+
176+
elif op.type == Operation.Type.ROL:
177+
# rotate left memory range
178+
length = self.__get(op.source, mem_tmp)
179+
start = self.__get(op.target, mem_tmp, get_address=True)
180+
self.__check_max_memory(length, op)
181+
rol(mem_tmp, start, length)
182+
183+
elif op.type == Operation.Type.ROR:
184+
# rotate right memory range
185+
length = self.__get(op.source, mem_tmp)
186+
start = self.__get(op.target, mem_tmp, get_address=True)
187+
self.__check_max_memory(length, op)
188+
ror(mem_tmp, start, length)
189+
162190
else:
163191
# arithmetic operation
164192
target = self.__get(op.target, mem_tmp)
@@ -212,5 +240,11 @@ def __set(self, op: Operand, v, mem, last):
212240
"overflow in {}; last operation: {}".format(op, last))
213241
mem[index] = v
214242

243+
def __check_max_memory(self, length, last_op):
244+
"""Check if memory range length exceeds the maximum allowed."""
245+
if abs(length) > self.__max_memory and self.__max_memory >= 0:
246+
self.__raise(
247+
"maximum memory exceeded: {}; last operation: {}".format(abs(length), last_op))
248+
215249
def __raise(self, msg: str) -> None:
216250
raise ValueError(msg)

loda/runtime/operations.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,54 @@ def bxo(a, b):
266266
return None
267267
return a ^ b
268268

269+
def get_range(start, length):
270+
"""Get the memory range [first, second) based on start and length.
271+
272+
If length is positive, the range is [start, start+length).
273+
If length is negative, the range is [start+length+1, start+1).
274+
If length is zero, the range is [start, start) (empty range).
275+
"""
276+
if length > 0:
277+
return (start, start + length)
278+
elif length < 0:
279+
return (start + length + 1, start + 1)
280+
else:
281+
return (start, start)
282+
283+
def clr(mem, start, length):
284+
"""Clear memory range. Sets memory cells in the range to 0."""
285+
first, second = get_range(start, length)
286+
keys_to_delete = [k for k in mem.keys() if first <= k < second]
287+
for k in keys_to_delete:
288+
del mem[k]
289+
290+
def fil(mem, start, length):
291+
"""Fill memory range. Sets all cells in range to the value at start."""
292+
value = mem.get(start, 0)
293+
first, second = get_range(start, length)
294+
for i in range(first, second):
295+
mem[i] = value
296+
297+
def rol(mem, start, length):
298+
"""Rotate left memory range. Shifts all values left by one position, wrapping around."""
299+
if length == 0:
300+
return
301+
first, second = get_range(start, length)
302+
leftmost = mem.get(first, 0)
303+
for i in range(first, second - 1):
304+
mem[i] = mem.get(i + 1, 0)
305+
mem[second - 1] = leftmost
306+
307+
def ror(mem, start, length):
308+
"""Rotate right memory range. Shifts all values right by one position, wrapping around."""
309+
if length == 0:
310+
return
311+
first, second = get_range(start, length)
312+
rightmost = mem.get(second - 1, 0)
313+
for i in range(second - 1, first, -1):
314+
mem[i] = mem.get(i - 1, 0)
315+
mem[first] = rightmost
316+
269317
def exec_arithmetic(t: Operation.Type, a, b):
270318
"""Execute an arithmetic operation."""
271319
if t == Operation.Type.MOV:

tests/test_memory_operations.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Test memory operations: clr, fil, rol, ror"""
4+
5+
from unittest import TestCase
6+
from loda.lang import Operation, Operand, Program
7+
from loda.runtime import Interpreter
8+
9+
10+
class MemoryOperationsTests(TestCase):
11+
12+
def setUp(self):
13+
self.interpreter = Interpreter(max_memory=10000)
14+
15+
def test_clr_positive_length(self):
16+
"""Test clear operation with positive length"""
17+
program = Program("""
18+
mov $1,5
19+
mov $2,10
20+
mov $3,15
21+
clr $1,2
22+
""")
23+
mem = {0: 0}
24+
self.interpreter.run(program, mem)
25+
self.assertEqual(mem.get(1, 0), 0)
26+
self.assertEqual(mem.get(2, 0), 0)
27+
self.assertEqual(mem.get(3, 0), 15)
28+
29+
def test_clr_negative_length(self):
30+
"""Test clear operation with negative length"""
31+
program = Program("""
32+
mov $1,5
33+
mov $2,10
34+
mov $3,15
35+
clr $3,-2
36+
""")
37+
mem = {0: 0}
38+
self.interpreter.run(program, mem)
39+
self.assertEqual(mem.get(1, 0), 5)
40+
self.assertEqual(mem.get(2, 0), 0)
41+
self.assertEqual(mem.get(3, 0), 0)
42+
43+
def test_fil_positive_length(self):
44+
"""Test fill operation with positive length"""
45+
program = Program("""
46+
mov $1,7
47+
mov $2,10
48+
mov $3,15
49+
fil $1,3
50+
""")
51+
mem = {0: 0}
52+
self.interpreter.run(program, mem)
53+
self.assertEqual(mem.get(1, 0), 7)
54+
self.assertEqual(mem.get(2, 0), 7)
55+
self.assertEqual(mem.get(3, 0), 7)
56+
57+
def test_fil_negative_length(self):
58+
"""Test fill operation with negative length"""
59+
program = Program("""
60+
mov $1,5
61+
mov $2,10
62+
mov $3,20
63+
fil $3,-2
64+
""")
65+
mem = {0: 0}
66+
self.interpreter.run(program, mem)
67+
self.assertEqual(mem.get(1, 0), 5)
68+
self.assertEqual(mem.get(2, 0), 20)
69+
self.assertEqual(mem.get(3, 0), 20)
70+
71+
def test_rol_positive_length(self):
72+
"""Test rotate left operation with positive length"""
73+
program = Program("""
74+
mov $1,10
75+
mov $2,20
76+
mov $3,30
77+
rol $1,3
78+
""")
79+
mem = {0: 0}
80+
self.interpreter.run(program, mem)
81+
self.assertEqual(mem.get(1, 0), 20)
82+
self.assertEqual(mem.get(2, 0), 30)
83+
self.assertEqual(mem.get(3, 0), 10)
84+
85+
def test_rol_negative_length(self):
86+
"""Test rotate left operation with negative length"""
87+
program = Program("""
88+
mov $1,10
89+
mov $2,20
90+
mov $3,30
91+
rol $3,-2
92+
""")
93+
mem = {0: 0}
94+
self.interpreter.run(program, mem)
95+
self.assertEqual(mem.get(1, 0), 10)
96+
self.assertEqual(mem.get(2, 0), 30)
97+
self.assertEqual(mem.get(3, 0), 20)
98+
99+
def test_ror_positive_length(self):
100+
"""Test rotate right operation with positive length"""
101+
program = Program("""
102+
mov $1,10
103+
mov $2,20
104+
mov $3,30
105+
ror $1,3
106+
""")
107+
mem = {0: 0}
108+
self.interpreter.run(program, mem)
109+
self.assertEqual(mem.get(1, 0), 30)
110+
self.assertEqual(mem.get(2, 0), 10)
111+
self.assertEqual(mem.get(3, 0), 20)
112+
113+
def test_ror_negative_length(self):
114+
"""Test rotate right operation with negative length"""
115+
program = Program("""
116+
mov $1,10
117+
mov $2,20
118+
mov $3,30
119+
ror $3,-2
120+
""")
121+
mem = {0: 0}
122+
self.interpreter.run(program, mem)
123+
self.assertEqual(mem.get(1, 0), 10)
124+
self.assertEqual(mem.get(2, 0), 30)
125+
self.assertEqual(mem.get(3, 0), 20)
126+
127+
def test_memory_operation_with_indirect_addressing(self):
128+
"""Test memory operations with indirect addressing"""
129+
program = Program("""
130+
mov $1,2
131+
mov $2,100
132+
mov $3,200
133+
fil $$1,2
134+
""")
135+
mem = {0: 0}
136+
self.interpreter.run(program, mem)
137+
self.assertEqual(mem.get(2, 0), 100)
138+
self.assertEqual(mem.get(3, 0), 100)
139+
140+
def test_max_memory_limit_fil(self):
141+
"""Test that memory range limit is enforced for fil operation"""
142+
program = Program("""
143+
mov $1,100
144+
fil $1,20000
145+
""")
146+
mem = {0: 0}
147+
with self.assertRaises(ValueError) as context:
148+
self.interpreter.run(program, mem)
149+
self.assertIn("maximum memory exceeded", str(context.exception).lower())
150+
151+
def test_max_memory_limit_clr(self):
152+
"""Test that memory range limit is enforced for clr operation"""
153+
program = Program("""
154+
mov $1,100
155+
clr $1,20000
156+
""")
157+
mem = {0: 0}
158+
with self.assertRaises(ValueError) as context:
159+
self.interpreter.run(program, mem)
160+
self.assertIn("maximum memory exceeded", str(context.exception).lower())
161+
162+
def test_max_memory_limit_rol(self):
163+
"""Test that memory range limit is enforced for rol operation"""
164+
program = Program("""
165+
mov $1,100
166+
rol $1,20000
167+
""")
168+
mem = {0: 0}
169+
with self.assertRaises(ValueError) as context:
170+
self.interpreter.run(program, mem)
171+
self.assertIn("maximum memory exceeded", str(context.exception).lower())
172+
173+
def test_max_memory_limit_ror(self):
174+
"""Test that memory range limit is enforced for ror operation"""
175+
program = Program("""
176+
mov $1,100
177+
ror $1,20000
178+
""")
179+
mem = {0: 0}
180+
with self.assertRaises(ValueError) as context:
181+
self.interpreter.run(program, mem)
182+
self.assertIn("maximum memory exceeded", str(context.exception).lower())
183+
184+
def test_rol_zero_length(self):
185+
"""Test rotate left with zero length (should do nothing)"""
186+
program = Program("""
187+
mov $1,10
188+
mov $2,20
189+
rol $1,0
190+
""")
191+
mem = {0: 0}
192+
self.interpreter.run(program, mem)
193+
self.assertEqual(mem.get(1, 0), 10)
194+
self.assertEqual(mem.get(2, 0), 20)
195+
196+
def test_ror_zero_length(self):
197+
"""Test rotate right with zero length (should do nothing)"""
198+
program = Program("""
199+
mov $1,10
200+
mov $2,20
201+
ror $1,0
202+
""")
203+
mem = {0: 0}
204+
self.interpreter.run(program, mem)
205+
self.assertEqual(mem.get(1, 0), 10)
206+
self.assertEqual(mem.get(2, 0), 20)
207+
208+
def test_clr_with_sparse_memory(self):
209+
"""Test clear operation with sparse memory (non-consecutive addresses)"""
210+
program = Program("""
211+
mov $10,100
212+
mov $20,200
213+
mov $30,300
214+
clr $10,25
215+
""")
216+
mem = {0: 0}
217+
self.interpreter.run(program, mem)
218+
self.assertEqual(mem.get(10, 0), 0)
219+
self.assertEqual(mem.get(20, 0), 0)
220+
self.assertEqual(mem.get(30, 0), 0)
221+
222+
def test_fil_with_constant_source(self):
223+
"""Test fill operation with constant as length"""
224+
program = Program("""
225+
mov $5,42
226+
fil $5,3
227+
""")
228+
mem = {0: 0}
229+
self.interpreter.run(program, mem)
230+
self.assertEqual(mem.get(5, 0), 42)
231+
self.assertEqual(mem.get(6, 0), 42)
232+
self.assertEqual(mem.get(7, 0), 42)

0 commit comments

Comments
 (0)