Skip to content

Commit

Permalink
A little bit of PEP8 and other cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
olafleur committed May 15, 2014
1 parent 4bbff2d commit 7d2b95e
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 38 deletions.
3 changes: 3 additions & 0 deletions diylisp/asserts.py
Expand Up @@ -3,6 +3,7 @@
from types import LispError
from parser import unparse


def assert_exp_length(ast, length):
if len(ast) > length:
msg = "Malformed %s, too many arguments: %s" % (ast[0], unparse(ast))
Expand All @@ -11,6 +12,7 @@ def assert_exp_length(ast, length):
msg = "Malformed %s, too few arguments: %s" % (ast[0], unparse(ast))
raise LispError(msg)


def assert_valid_definition(d):
if len(d) != 2:
msg = "Wrong number of arguments for variable definition: %s" % d
Expand All @@ -19,6 +21,7 @@ def assert_valid_definition(d):
msg = "Attempted to define non-symbol as variable: %s" % d
raise LispError(msg)


def assert_boolean(p, exp=None):
if not is_boolean(p):
msg = "Boolean required, got '%s'. " % unparse(p)
Expand Down
8 changes: 7 additions & 1 deletion diylisp/ast.py
Expand Up @@ -3,25 +3,31 @@
from types import Closure

"""
This module contains a few simple helper functions for
This module contains a few simple helper functions for
checking the type of ASTs.
"""


def is_symbol(x):
return isinstance(x, str)


def is_list(x):
return isinstance(x, list)


def is_boolean(x):
return isinstance(x, bool)


def is_integer(x):
return isinstance(x, int)


def is_closure(x):
return isinstance(x, Closure)


def is_atom(x):
return is_symbol(x) \
or is_integer(x) \
Expand Down
1 change: 1 addition & 0 deletions diylisp/evaluator.py
Expand Up @@ -14,6 +14,7 @@
in a day, after all.)
"""


def evaluate(ast, env):
"""Evaluate an Abstract Syntax Tree in the specified environment."""
raise NotImplementedError("DIY")
2 changes: 2 additions & 0 deletions diylisp/interpreter.py
Expand Up @@ -6,6 +6,7 @@
from parser import parse, unparse, parse_multiple
from types import Environment


def interpret(source, env=None):
"""
Interpret a lisp program statement
Expand All @@ -18,6 +19,7 @@ def interpret(source, env=None):

return unparse(evaluate(parse(source), env))


def interpret_file(filename, env=None):
"""
Interpret a lisp file
Expand Down
7 changes: 7 additions & 0 deletions diylisp/parser.py
Expand Up @@ -10,6 +10,7 @@
understand.
"""


def parse(source):
"""Parse string representation of one *single* expression
into the corresponding Abstract Syntax Tree."""
Expand All @@ -22,10 +23,12 @@ def parse(source):
## counting, after all.
##


def remove_comments(source):
"""Remove from a string anything in between a ; and a linebreak"""
return re.sub(r";.*\n", "\n", source)


def find_matching_paren(source, start=0):
"""Given a string and the index of an opening parenthesis, determines
the index of the matching closing paren."""
Expand All @@ -43,6 +46,7 @@ def find_matching_paren(source, start=0):
open_brackets -= 1
return pos


def split_exps(source):
"""Splits a source string into subexpressions
that can be parsed individually.
Expand All @@ -60,6 +64,7 @@ def split_exps(source):
exps.append(exp)
return exps


def first_expression(source):
"""Split string into (exp, rest) where exp is the
first expression in the string and rest is the
Expand All @@ -83,6 +88,7 @@ def first_expression(source):
## the REPL to work. Don't worry about them when implementing the language.
##


def parse_multiple(source):
"""Creates a list of ASTs from program source constituting multiple expressions.
Expand All @@ -96,6 +102,7 @@ def parse_multiple(source):
source = remove_comments(source)
return [parse(exp) for exp in split_exps(source)]


def unparse(ast):
"""Turns an AST back into lisp program source"""

Expand Down
12 changes: 8 additions & 4 deletions diylisp/repl.py
Expand Up @@ -12,6 +12,7 @@
# where it is supported (i.e. UNIX-y systems)
import readline


def repl():
"""Start the interactive Read-Eval-Print-Loop"""
print
Expand Down Expand Up @@ -44,8 +45,9 @@ def repl():
print faded(" " + str(e.__class__.__name__) + ":"),
print str(e)


def read_expression():
"Read from stdin until we have at least one s-expression"
"""Read from stdin until we have at least one s-expression"""

exp = ""
open_parens = 0
Expand All @@ -58,17 +60,18 @@ def read_expression():

return exp.strip()


def read_line(prompt):
"Return touple of user input line and number of unclosed parens"
"""Return touple of user input line and number of unclosed parens"""

line = raw_input(colored(prompt, "grey", "bold"))
line = remove_comments(line + "\n")
return (line, line.count("(") - line.count(")"))
return line, line.count("(") - line.count(")")


def colored(text, color, attr=None):
attributes = {
'bold': 1,
'bold': 1,
'dark': 2
}
colors = {
Expand All @@ -93,5 +96,6 @@ def colored(text, color, attr=None):

return color + attr + text + reset


def faded(text):
return colored(text, "grey", attr='bold')
7 changes: 4 additions & 3 deletions diylisp/types.py
Expand Up @@ -7,20 +7,21 @@
The LispError class you can have for free :)
"""

class LispError(Exception):

class LispError(Exception):
"""General lisp error class."""
pass


class Closure:

def __init__(self, env, params, body):
raise NotImplementedError("DIY")

def __str__(self):
return "<closure/%d>" % len(self.params)

class Environment:

class Environment:
def __init__(self, variables=None):
self.variables = variables if variables else {}

Expand Down
38 changes: 26 additions & 12 deletions tests/test_1_parsing.py
Expand Up @@ -5,6 +5,7 @@
from diylisp.parser import parse, unparse
from diylisp.types import LispError


def test_parse_single_symbol():
"""Parsing a single symbol.
Expand All @@ -13,6 +14,7 @@ def test_parse_single_symbol():

assert_equals('foo', parse('foo'))


def test_parse_boolean():
"""Parsing single booleans.
Expand All @@ -22,6 +24,7 @@ def test_parse_boolean():
assert_equals(True, parse('#t'))
assert_equals(False, parse('#f'))


def test_parse_integer():
"""Parsing single integer.
Expand All @@ -33,6 +36,7 @@ def test_parse_integer():
assert_equals(42, parse('42'))
assert_equals(1337, parse('1337'))


def test_parse_list_of_symbols():
"""Parsing list of only symbols.
Expand All @@ -46,6 +50,7 @@ def test_parse_list_of_symbols():
assert_equals(['foo', 'bar', 'baz'], parse('(foo bar baz)'))
assert_equals([], parse('()'))


def test_parse_list_of_mixed_types():
"""Parsing a list containing different types.
Expand All @@ -54,21 +59,24 @@ def test_parse_list_of_mixed_types():

assert_equals(['foo', True, 123], parse('(foo #t 123)'))


def test_parse_on_nested_list():
"""Parsing should also handle nested lists properly."""

program = '(foo (bar ((#t)) x) (baz y))'
ast = ['foo',
['bar', [[True]], 'x'],
['baz', 'y']]
ast = ['foo',
['bar', [[True]], 'x'],
['baz', 'y']]
assert_equals(ast, parse(program))


def test_parse_exception_missing_paren():
"""The proper exception should be raised if the expresions is incomplete."""

with assert_raises_regexp(LispError, 'Incomplete expression'):
parse('(foo (bar x y)')


def test_parse_exception_extra_paren():
"""Another exception is raised if the expression is too large.
Expand All @@ -78,6 +86,7 @@ def test_parse_exception_extra_paren():
with assert_raises_regexp(LispError, 'Expected EOF'):
parse('(foo (bar x y)))')


def test_parse_with_extra_whitespace():
"""Excess whitespace should be removed."""

Expand All @@ -89,6 +98,7 @@ def test_parse_with_extra_whitespace():
expected_ast = ['program', 'with', 'much', 'whitespace']
assert_equals(expected_ast, parse(program))


def test_parse_comments():
"""All comments should be stripped away as part of the parsing."""

Expand All @@ -100,12 +110,13 @@ def test_parse_comments():
42 ; inline comment!
(something else)))
"""
expected_ast = ['define', 'variable',
['if', True,
42,
['something', 'else']]]
expected_ast = ['define', 'variable',
['if', True,
42,
['something', 'else']]]
assert_equals(expected_ast, parse(program))


def test_parse_larger_example():
"""Test a larger example to check that everything works as expected"""

Expand All @@ -118,15 +129,16 @@ def test_parse_larger_example():
; the existence of negative numbers
(* n (fact (- n 1))))))
"""
ast = ['define', 'fact',
['lambda', ['n'],
['if', ['<=', 'n', 1],
1,
['*', 'n', ['fact', ['-', 'n', 1]]]]]]
ast = ['define', 'fact',
['lambda', ['n'],
['if', ['<=', 'n', 1],
1,
['*', 'n', ['fact', ['-', 'n', 1]]]]]]
assert_equals(ast, parse(program))

## The following tests checks that quote expansion works properly


def test_expand_single_quoted_symbol():
"""Quoting is a shorthand syntax for calling the `quote` form.
Expand All @@ -138,9 +150,11 @@ def test_expand_single_quoted_symbol():
"""
assert_equals(["foo", ["quote", "nil"]], parse("(foo 'nil)"))


def test_nested_quotes():
assert_equals(["quote", ["quote", ["quote", ["quote", "foo"]]]], parse("''''foo"))


def test_expand_crazy_quote_combo():
"""One final test to see that quote expansion works."""

Expand Down

0 comments on commit 7d2b95e

Please sign in to comment.