Skip to content

Commit

Permalink
More refactoring and tests for #4
Browse files Browse the repository at this point in the history
  • Loading branch information
eigenhombre committed Jul 22, 2022
1 parent 1b6b7e7 commit 026ef6e
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 188 deletions.
1 change: 1 addition & 0 deletions .projectile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- ./pytest_cache
- /dist
- /build
- /venv
2 changes: 1 addition & 1 deletion Dockerfile.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ RUN apt-get -qq -y upgrade
RUN apt-get install -qq -y make

WORKDIR /scheme
COPY . /scheme
RUN pip install --upgrade pip
RUN pip install pytest
COPY . /scheme
RUN pip install -e .
RUN pytest -s
RUN smallscheme fact.scm
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pip-docker-test:
build-docker-test:
docker build -t smallscheme -f Dockerfile.build .

alltests: test build-docker-test
alltests: test lint build-docker-test

release:
./bumpver
Expand Down
161 changes: 161 additions & 0 deletions smallscheme/builtin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import operator
import random
import sys
from smallscheme.dtypes import *
from functools import reduce

def argstype(args):
# FIXME: Unit test for argument types
arglist = [x for (x, _) in args]
argset = set(arglist)
if argset == {'int', 'float'} or argset == {'float'}:
return 'float'
elif argset == {'int'}:
return 'int'
else:
raise Exception("Bad numeric arg list: '%s'"
% arglist)

def plus(args):
return (argstype(args),
sum(x for (_, x) in args))

def times(args):
return (argstype(args),
reduce(operator.mul,
(x for (_, x) in args),
1))

def minus(args):
if len(args) == 1:
return argstype(args), -args[0][1]
else:
return (argstype(args),
args[0][1] - sum(x for (_, x) in args[1:]))

def divide(args):
return (argstype(args),
args[0][1] // reduce(operator.mul,
(x for (_, x) in args[1:]),
1))

def equals(args):
return bool_(operator.eq(*args))

def compare(args, oper):
((ty1, v1), (ty2, v2)) = args[:2]
if ((ty1 != 'int' and ty1 != 'float') or
(ty2 != 'int' and ty2 != 'float')):
raise Exception("Type error, can't compare '%s' to '%s'!" %
(ty1, ty2))
return bool_(oper(v1, v2))

def lessthan(args):
return compare(args, operator.lt)

def greaterthan(args):
return compare(args, operator.gt)

def notnot(args):
if args[0] == bool_(False):
return bool_(True)
else:
return bool_(False)

def car(x):
typ, l = x[0]
if typ != 'list':
raise Exception("Can't take car of '%s'!"
% x)
return l[0]

def cdr(x):
typ, l = x[0]
if typ != 'list':
raise Exception("Can't take car of '%s'!"
% x)
return list_(l[1:])

def cons(args):
# 2-ary cons for now:
(a, (type_l, l)) = args
if type_l != 'list':
raise Exception("Invalid cons args, '%s'!" % str(args))
return list_([a] + l)

def remainder(args):
((t1, v1), (t2, v2)) = args
if t1 != 'int':
raise Exception("Invalid arg type, '%s'!" % t1)
if t2 != 'int':
raise Exception("Invalid arg type, '%s'!" % t2)
return int_(v1 % v2)

def randint(arg):
t, v = arg[0]
if t != 'int':
raise Exception("Invalid arg type, '%s'!" % t)
return int_(random.randint(0, v - 1))

def display(arg):
print(printable_value(arg[0]), end='')
return noop

def newline(_):
print()
return noop

# System time in msec for use in benchmarking:
if(sys.version_info.major >= 3 and
sys.version_info.minor >= 7):
import time

def runtime(_):
return int(time.time_ns() / 1E6)
else:
import datetime

def runtime(_):
return int(
(datetime.datetime.utcnow() -
datetime.datetime(1970, 1, 1)).total_seconds() * 1000)

def is_assert(arg):
t, v = arg[0]
if v is False:
raise AssertionError(f"is / assert failed: {arg}")
return noop

def begin_aka_test(args):
"""
For now, the `test` expression is simply a `begin` (like `progn`
in Common Lisp or `do` in Clojure): evaluate all the arguments and
return the last one. This may change as the test framework gets
more features.
"""
if args:
return args[-1]

def set_bang(args):
pass

dispatch_table = {'+': plus,
'*': times,
'-': minus,
'/': divide,
'=': equals,
'<': lessthan,
'>': greaterthan,
'not': notnot,
'car': car,
'cdr': cdr,
'remainder': remainder,
'random': randint,
'cons': cons,
'display': display,
'newline': newline,
'runtime': runtime,
'is': is_assert,
'test': begin_aka_test,
'begin': begin_aka_test,
'set!': set_bang}
25 changes: 25 additions & 0 deletions smallscheme/dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,28 @@ def float_(x):

def typ(x):
return x[0]

noop = 'nop', None

def printable_value(ast):
k, v = ast
if k == 'int' or k == 'float':
return str(v)
if k == 'bool':
return {True: "#t",
False: "#f"}.get(v)
if k == 'intproc':
return "Internal procedure '%s'" % v
if k == 'atom':
return v
if k == 'list':
return '(' + ' '.join([printable_value(x)
for x in v]) + ')'
if k == 'nop':
return ''
if k == 'fn':
(fn_name, _, _) = v
if fn_name == 'lambda':
return "Anonymous-function"
return "Function-'%s'" % str(fn_name)
raise Exception('Unprintable ast "%s"' % str(ast))
Loading

0 comments on commit 026ef6e

Please sign in to comment.