Add specialized opcodes, mutable strings, --fast flag
- 20 specialized opcodes: ADD, SUB, MUL, NEG, ADD1, SUB1, NUM_EQ, LT, GT, LE, GE, CAR, CDR, CONS, NULL?, PAIR?, NOT, ZERO?, VEC_REF, VEC_SET. Compiler pattern-matches (+ x 1) → ADD1, (- x 1) → SUB1. - MutableString class: string-set!, string-fill!, string-copy! now work. make-string and string-copy return mutable strings. - --fast / -f flag: auto-compile mode from command line. - 529 tests green.
This commit is contained in:
parent
e091accd56
commit
f89d3bce6f
2 changed files with 166 additions and 6 deletions
52
tests.py
52
tests.py
|
|
@ -2583,6 +2583,58 @@ class TestBytecodeCompiler(unittest.TestCase):
|
|||
assert run('((lambda (x) (+ x 1)) 5)', self.g) == 6
|
||||
run('(auto-compile! #f)', self.g)
|
||||
|
||||
def test_specialized_opcodes_arithmetic(self):
|
||||
"""Specialized opcodes for +, -, *, =, <, etc."""
|
||||
self._compile_and_run('(define (f x y) (+ x y))', '(f 3 4)')
|
||||
assert run('(f 3 4)', self.g) == 7
|
||||
assert run('(f 1.5 2.5)', self.g) == 4.0
|
||||
|
||||
def test_specialized_opcodes_add1_sub1(self):
|
||||
"""(+ x 1) → ADD1, (- x 1) → SUB1."""
|
||||
self._compile_and_run(
|
||||
'(define (inc x) (+ x 1))', '(inc 41)')
|
||||
assert run('(inc 41)', self.g) == 42
|
||||
self._compile_and_run(
|
||||
'(define (dec x) (- x 1))', '(dec 43)')
|
||||
assert run('(dec 43)', self.g) == 42
|
||||
|
||||
def test_specialized_opcodes_car_cdr_cons(self):
|
||||
"""Specialized car, cdr, cons."""
|
||||
self._compile_and_run(
|
||||
'(define (hd lst) (car lst))', "(hd '(1 2 3))")
|
||||
assert run("(hd '(1 2 3))", self.g) == 1
|
||||
self._compile_and_run(
|
||||
'(define (tl lst) (cdr lst))', "(tl '(1 2 3))")
|
||||
assert show(run("(tl '(1 2 3))", self.g)) == '(2 3)'
|
||||
self._compile_and_run(
|
||||
'(define (pair a b) (cons a b))', '(pair 1 2)')
|
||||
assert show(run('(pair 1 2)', self.g)) == '(1 . 2)'
|
||||
|
||||
def test_specialized_opcodes_predicates(self):
|
||||
"""null?, pair?, not, zero?."""
|
||||
self._compile_and_run(
|
||||
"(define (f x) (null? x))", "(f '())")
|
||||
assert run("(f '())", self.g) == True
|
||||
assert run("(f '(1))", self.g) == False
|
||||
|
||||
def test_specialized_tail_position(self):
|
||||
"""Specialized opcodes work in tail position."""
|
||||
r = self._compile_and_run(
|
||||
'(define (f x) (+ x 1))', '(f 99)')
|
||||
assert r == 100
|
||||
|
||||
def test_string_set(self):
|
||||
"""string-set! for mutable strings."""
|
||||
run('(define s (string-copy "hello"))', self.g)
|
||||
run('(string-set! s 0 #\\H)', self.g)
|
||||
assert run('s', self.g) == "Hello"
|
||||
|
||||
def test_string_fill(self):
|
||||
"""string-fill! fills a mutable string."""
|
||||
run('(define s (make-string 3 #\\x))', self.g)
|
||||
run('(string-fill! s #\\o)', self.g)
|
||||
assert run('s', self.g) == "ooo"
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Main
|
||||
|
|
|
|||
120
uncommonlisp.py
120
uncommonlisp.py
|
|
@ -236,6 +236,38 @@ class StringOutputPort:
|
|||
def close(self): pass
|
||||
def getvalue(self): return ''.join(self._buf)
|
||||
|
||||
class MutableString:
|
||||
"""Mutable string for R7RS string-set!, string-copy!, string-fill!."""
|
||||
__slots__ = ('_c',)
|
||||
def __init__(self, s):
|
||||
self._c = list(s) if isinstance(s, str) else list(s._c) if isinstance(s, MutableString) else list(s)
|
||||
def __len__(self): return len(self._c)
|
||||
def __getitem__(self, k):
|
||||
if isinstance(k, slice): return ''.join(self._c[k])
|
||||
return self._c[k]
|
||||
def __setitem__(self, k, v): self._c[k] = v
|
||||
def __str__(self): return ''.join(self._c)
|
||||
def __repr__(self): return '"' + str(self) + '"'
|
||||
def __eq__(self, o):
|
||||
if isinstance(o, MutableString): return self._c == o._c
|
||||
if isinstance(o, str): return str(self) == o
|
||||
return NotImplemented
|
||||
def __hash__(self): return hash(str(self))
|
||||
def __contains__(self, x): return x in str(self)
|
||||
def __add__(self, o): return str(self) + (str(o) if isinstance(o, MutableString) else o)
|
||||
def __radd__(self, o): return o + str(self)
|
||||
# String-like methods for compatibility
|
||||
def upper(self): return str(self).upper()
|
||||
def lower(self): return str(self).lower()
|
||||
def strip(self): return str(self).strip()
|
||||
def rstrip(self): return str(self).rstrip()
|
||||
def split(self, *a): return str(self).split(*a)
|
||||
def find(self, *a): return str(self).find(*a)
|
||||
def replace(self, *a): return str(self).replace(*a)
|
||||
def startswith(self, *a): return str(self).startswith(*a)
|
||||
def endswith(self, *a): return str(self).endswith(*a)
|
||||
def join(self, it): return str(self).join(str(x) if isinstance(x, MutableString) else x for x in it)
|
||||
|
||||
###############################################################################
|
||||
# Printer
|
||||
###############################################################################
|
||||
|
|
@ -251,6 +283,10 @@ def show(x, display=False):
|
|||
if isinstance(x, Pair): return repr(x)
|
||||
if isinstance(x, list): # vector
|
||||
return '#(' + ' '.join(show(e) for e in x) + ')'
|
||||
if isinstance(x, MutableString):
|
||||
if display: return str(x)
|
||||
return ('"' + str(x).replace('\\', '\\\\').replace('"', '\\"')
|
||||
.replace('\n', '\\n').replace('\t', '\\t') + '"')
|
||||
if isinstance(x, str):
|
||||
if isinstance(x, Symbol): return str(x)
|
||||
if display: return x
|
||||
|
|
@ -1032,6 +1068,24 @@ OP_MAKE_CLOSURE = 30
|
|||
OP_PUSH_ENV = 40; OP_POP_ENV = 41; OP_BIND = 42
|
||||
OP_EVAL = 50 # fallback to tree-walker
|
||||
OP_CALL_CC = 51 # call/cc
|
||||
# Specialized opcodes (avoid LOOKUP+CALL for hot builtins)
|
||||
OP_ADD = 60; OP_SUB = 61; OP_MUL = 62; OP_NEG = 63
|
||||
OP_NUM_EQ = 64; OP_LT = 65; OP_GT = 66; OP_LE = 67; OP_GE = 68
|
||||
OP_ADD1 = 69; OP_SUB1 = 70
|
||||
OP_CAR = 71; OP_CDR = 72; OP_CONS = 73
|
||||
OP_NULL_P = 74; OP_PAIR_P = 75; OP_NOT = 76; OP_ZERO_P = 77
|
||||
OP_VEC_REF = 78; OP_VEC_SET = 79
|
||||
|
||||
# Specialization table: {symbol: {arity: opcode}}
|
||||
_BC_SPECIALIZE = {
|
||||
S('+'): {2: OP_ADD}, S('-'): {2: OP_SUB, 1: OP_NEG}, S('*'): {2: OP_MUL},
|
||||
S('='): {2: OP_NUM_EQ}, S('<'): {2: OP_LT}, S('>'): {2: OP_GT},
|
||||
S('<='): {2: OP_LE}, S('>='): {2: OP_GE},
|
||||
S('car'): {1: OP_CAR}, S('cdr'): {1: OP_CDR}, S('cons'): {2: OP_CONS},
|
||||
S('null?'): {1: OP_NULL_P}, S('pair?'): {1: OP_PAIR_P},
|
||||
S('not'): {1: OP_NOT}, S('zero?'): {1: OP_ZERO_P},
|
||||
S('vector-ref'): {2: OP_VEC_REF}, S('vector-set!'): {3: OP_VEC_SET},
|
||||
}
|
||||
|
||||
# Constant folding tables
|
||||
def _bc_is_const(expr):
|
||||
|
|
@ -1347,7 +1401,24 @@ def _bc(expr, code, env, tail=False):
|
|||
result = _BC_FOLDABLE[head]([a for a in call_args])
|
||||
code.emit(OP_CONST, result); return
|
||||
except Exception:
|
||||
pass # fold failed, emit normal call
|
||||
pass
|
||||
|
||||
# --- Specialized opcodes for hot builtins ---
|
||||
if isinstance(head, Symbol):
|
||||
n = len(call_args)
|
||||
spec = _BC_SPECIALIZE.get(head)
|
||||
if spec and n in spec:
|
||||
# Special case: (+ x 1) → ADD1, (- x 1) → SUB1
|
||||
if head is S('+') and n == 2:
|
||||
if _bc_is_const(call_args[1]) and call_args[1] == 1:
|
||||
_bc(call_args[0], code, env); code.emit(OP_ADD1); return
|
||||
if _bc_is_const(call_args[0]) and call_args[0] == 1:
|
||||
_bc(call_args[1], code, env); code.emit(OP_ADD1); return
|
||||
if head is S('-') and n == 2:
|
||||
if _bc_is_const(call_args[1]) and call_args[1] == 1:
|
||||
_bc(call_args[0], code, env); code.emit(OP_SUB1); return
|
||||
for arg in call_args: _bc(arg, code, env)
|
||||
code.emit(spec[n]); return
|
||||
|
||||
# --- Function call ---
|
||||
_bc(head, code, env)
|
||||
|
|
@ -1516,6 +1587,28 @@ def _vm_loop(instrs, ip, stack, env, frames, vm_id):
|
|||
elif callable(proc):
|
||||
_ap(proc([cont], env))
|
||||
else: raise LispErr(f'call/cc: not callable: {show(proc)}')
|
||||
# ── Specialized opcodes ──────────────────────────────────────────
|
||||
elif op == OP_ADD: b = _po(); stack[-1] = stack[-1] + b
|
||||
elif op == OP_SUB: b = _po(); stack[-1] = stack[-1] - b
|
||||
elif op == OP_MUL: b = _po(); stack[-1] = stack[-1] * b
|
||||
elif op == OP_NEG: stack[-1] = -stack[-1]
|
||||
elif op == OP_ADD1: stack[-1] = stack[-1] + 1
|
||||
elif op == OP_SUB1: stack[-1] = stack[-1] - 1
|
||||
elif op == OP_NUM_EQ: b = _po(); stack[-1] = stack[-1] == b
|
||||
elif op == OP_LT: b = _po(); stack[-1] = stack[-1] < b
|
||||
elif op == OP_GT: b = _po(); stack[-1] = stack[-1] > b
|
||||
elif op == OP_LE: b = _po(); stack[-1] = stack[-1] <= b
|
||||
elif op == OP_GE: b = _po(); stack[-1] = stack[-1] >= b
|
||||
elif op == OP_CAR: stack[-1] = stack[-1].car
|
||||
elif op == OP_CDR: stack[-1] = stack[-1].cdr
|
||||
elif op == OP_CONS: d = _po(); stack[-1] = Pair(stack[-1], d)
|
||||
elif op == OP_NULL_P: stack[-1] = stack[-1] is NIL
|
||||
elif op == OP_PAIR_P: stack[-1] = _isinstance(stack[-1], Pair)
|
||||
elif op == OP_NOT: stack[-1] = stack[-1] is False
|
||||
elif op == OP_ZERO_P: stack[-1] = stack[-1] == 0
|
||||
elif op == OP_VEC_REF: i = _po(); stack[-1] = stack[-1][i]
|
||||
elif op == OP_VEC_SET:
|
||||
v = _po(); i = _po(); stack[-1][i] = v; stack[-1] = VOID
|
||||
return stack[-1] if stack else VOID
|
||||
|
||||
|
||||
|
|
@ -1547,6 +1640,12 @@ def _disassemble(proc):
|
|||
OP_MAKE_CLOSURE: 'MAKE_CLOSURE',
|
||||
OP_PUSH_ENV: 'PUSH_ENV', OP_POP_ENV: 'POP_ENV', OP_BIND: 'BIND',
|
||||
OP_EVAL: 'EVAL', OP_CALL_CC: 'CALL_CC',
|
||||
OP_ADD: 'ADD', OP_SUB: 'SUB', OP_MUL: 'MUL', OP_NEG: 'NEG',
|
||||
OP_ADD1: 'ADD1', OP_SUB1: 'SUB1',
|
||||
OP_NUM_EQ: 'NUM_EQ', OP_LT: 'LT', OP_GT: 'GT', OP_LE: 'LE', OP_GE: 'GE',
|
||||
OP_CAR: 'CAR', OP_CDR: 'CDR', OP_CONS: 'CONS',
|
||||
OP_NULL_P: 'NULL?', OP_PAIR_P: 'PAIR?', OP_NOT: 'NOT', OP_ZERO_P: 'ZERO?',
|
||||
OP_VEC_REF: 'VEC_REF', OP_VEC_SET: 'VEC_SET',
|
||||
}
|
||||
lines = [f'--- {proc.name or "λ"} '
|
||||
f'({" ".join(str(p) for p in proc.params)}'
|
||||
|
|
@ -1585,6 +1684,7 @@ def _num(x):
|
|||
return x
|
||||
|
||||
def _str_val(x):
|
||||
if isinstance(x, MutableString): return x
|
||||
if not isinstance(x, str) or isinstance(x, Symbol):
|
||||
raise LispErr(f'not a string: {show(x)}')
|
||||
return x
|
||||
|
|
@ -1814,7 +1914,7 @@ def make_global_env():
|
|||
d(S('null?'), lambda a, _: a[0] is NIL)
|
||||
d(S('list?'), lambda a, _: _is_proper_list(a[0]))
|
||||
d(S('symbol?'), lambda a, _: isinstance(a[0], Symbol))
|
||||
d(S('string?'), lambda a, _: isinstance(a[0], str) and not isinstance(a[0], Symbol))
|
||||
d(S('string?'), lambda a, _: isinstance(a[0], MutableString) or (isinstance(a[0], str) and not isinstance(a[0], Symbol)))
|
||||
d(S('char?'), lambda a, _: isinstance(a[0], str) and not isinstance(a[0], Symbol) and len(a[0]) == 1)
|
||||
d(S('vector?'), lambda a, _: isinstance(a[0], list))
|
||||
d(S('boolean?'), lambda a, _: isinstance(a[0], bool))
|
||||
|
|
@ -2020,13 +2120,16 @@ def make_global_env():
|
|||
d(S('apply'), lambda a, e: _call(a[0], (_L(a[-1]) if len(a)==2 else [leval(x,e) for x in a[1:-1]] + _L(a[-1])), e))
|
||||
|
||||
# ── Strings ───────────────────────────────────────────────────────────────
|
||||
d(S('make-string'), lambda a, _: (a[1] if len(a) > 1 else ' ') * int(_num(a[0])))
|
||||
d(S('make-string'), lambda a, _: MutableString((a[1] if len(a) > 1 else ' ') * int(_num(a[0]))))
|
||||
d(S('string'), lambda a, _: ''.join(a))
|
||||
d(S('string-length'), lambda a, _: len(_str_val(a[0])))
|
||||
d(S('string-ref'), lambda a, _: _str_val(a[0])[int(_num(a[1]))])
|
||||
d(S('substring'), lambda a, _: _str_val(a[0])[int(_num(a[1])): int(_num(a[2])) if len(a) > 2 else None])
|
||||
d(S('string-append'), lambda a, _: ''.join(_str_val(x) for x in a))
|
||||
d(S('string-copy'), lambda a, _: _str_val(a[0]))
|
||||
d(S('substring'), lambda a, _: str(_str_val(a[0]))[int(_num(a[1])): int(_num(a[2])) if len(a) > 2 else None])
|
||||
d(S('string-append'), lambda a, _: ''.join(str(_str_val(x)) for x in a))
|
||||
d(S('string-copy'), lambda a, _: MutableString(_str_val(a[0])))
|
||||
d(S('string-set!'), lambda a, _: (_str_val(a[0]).__setitem__(int(_num(a[1])), a[2]) or VOID) if isinstance(a[0], MutableString) else _raise(LispErr('string-set!: immutable string')))
|
||||
d(S('string-fill!'), lambda a, _: [_str_val(a[0]).__setitem__(i, a[1]) for i in range(len(a[0]))] and VOID if isinstance(a[0], MutableString) else _raise(LispErr('string-fill!: immutable string')))
|
||||
d(S('string-copy!'), lambda a, _: [a[0].__setitem__(int(_num(a[1]))+i, c) for i, c in enumerate(str(_str_val(a[2]))[int(_num(a[3])) if len(a)>3 else 0:int(_num(a[4])) if len(a)>4 else None])] and VOID if isinstance(a[0], MutableString) else _raise(LispErr('string-copy!: immutable dest')))
|
||||
d(S('string->list'), lambda a, _: _P(list(_str_val(a[0]))))
|
||||
d(S('list->string'), lambda a, _: ''.join(_L(a[0])))
|
||||
d(S('string->symbol'), lambda a, _: S(_str_val(a[0])))
|
||||
|
|
@ -2506,6 +2609,11 @@ def main():
|
|||
|
||||
args = sys.argv[1:]
|
||||
|
||||
# --fast / -f: enable auto-compile
|
||||
if '--fast' in args or '-f' in args:
|
||||
_auto_compile[0] = True
|
||||
args = [a for a in args if a not in ('--fast', '-f')]
|
||||
|
||||
# -e 'expr' mode
|
||||
if args and args[0] == '-e':
|
||||
if len(args) < 2:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue