diff --git a/attachment/awk_lisp.tgz b/attachment/awk_lisp.tgz new file mode 100644 index 0000000..e3804d2 Binary files /dev/null and b/attachment/awk_lisp.tgz differ diff --git a/attachment/faq-doc-6.txt b/attachment/faq-doc-6.txt new file mode 100644 index 0000000..daa55c9 --- /dev/null +++ b/attachment/faq-doc-6.txt @@ -0,0 +1,77 @@ +Quelle: http://www.cs.cmu.edu/Groups/AI/html/faqs/lang/lisp/part1/faq-doc-6.html +Abruf: 04.08.2019 +----------------------------------------------------------------------------- +[1-5] What is the "minimal" set of primitives needed for a Lisp interpreter? + +Many Lisp functions can be defined in terms of other Lisp functions. +For example, CAAR can be defined in terms of CAR as + (defun caar (list) (car (car list))) +It is then natural to ask whether there is a "minimal" or smallest set +of primitives necessary to implement the language. + +There is no single "best" minimal set of primitives; it all depends on +the implementation. For example, even something as basic as numbers +need not be primitive, and can be represented as lists. One possible +set of primitives might include CAR, CDR, and CONS for manipulation of +S-expressions, READ and PRINT for the input/output of S-expressions +and APPLY and EVAL for the guts of an interpreter. But then you might +want to add LAMBDA for functions, EQ for equality, COND for +conditionals, SET for assignment, and DEFUN for definitions. QUOTE +might come in handy as well. If you add more specialized datatypes, +such as integers, floats, arrays, characters, and structures, you'll +need to add primitives to construct and access each. + +AWKLisp is a Lisp interpreter written in awk, available by anonymous +ftp from ftp.cs.cmu.edu:/user/ai/lang/lisp/impl/awk/. It has thirteen +built-in functions: CAR, CDR, CONS, EQ, ATOM, SET, EVAL, ERROR, QUOTE, +COND, AND, OR, LIST. + +A more practical notion of a "minimal" set of primitives might be to +look at the implementation of Scheme. While many Scheme functions can +be derived from others, the language is much smaller than Common Lisp. +See Dybvig's PhD thesis, + R. Kent Dybvig, "Three Implementation Models for Scheme", Department + of Computer Science Technical Report #87-011, University of North + Carolina at Chapel Hill, Chapel Hill, North Carolina, April 1987. +for a justification of a particularly practical minimal set of +primitives for Scheme. + +In a language like Common Lisp, however, there are a lot of low-level +primitive functions that cannot be written in terms of the others, +such as GET-UNIVERSAL-TIME, READ-CHAR, WRITE-CHAR, OPEN, and CLOSE, +for starters. Moreover, real Common Lisp implementations are often +built upon primitives that aren't part of the language, per se, and +certainly not intended to be user-accessible, such as SYS:%POINTER-REF. + +Beside the references listed in [1-4], some other relevant references +include: + + McCarthy, John, "Recursive Functions of Symbolic Expressions and + their Computation by Machine, Part I", CACM 3(4):185-195, April 1960. + [Defines five elementary functions on s-expressions.] + + McCarthy, John, "A Micro-Manual for Lisp -- not the whole Truth", + ACM SIGPLAN Notices, 13(8):215-216, August 1978. + [Defines the Lisp programming language in 10 rules and gives + a small interpreter (eval) written in this Lisp.] + + McCarthy, John, et al., "LISP 1.5 Programmer's Manual", 2nd edition, + MIT Press, 1965, ISBN 0-262-13011-4 (paperback). + [Gives five basic functions, CAR, CDR, CONS, EQ, and ATOM. + Using composition, conditional expressions (COND), and + recursion, LAMBDA, and QUOTE, these basic functions may be used + to construct the entire class of computable functions of + S-expressions. Gives the functions EVAL and APPLY in + M-expression syntax.] + + Abelson and Sussman's SICP, especially chapters 4 and 5 on the + implementation of meta-circular and explicit-control evaluators. + + Steele and Gabriel's "The Evolution of LISP". + +Go Back Up + +Go To Previous + +Go To Next + diff --git a/attachment/lis.py b/attachment/lis.py new file mode 100644 index 0000000..2d031ad --- /dev/null +++ b/attachment/lis.py @@ -0,0 +1,145 @@ +################ Lispy: Scheme Interpreter in Python + +## (c) Peter Norvig, 2010-16; See http://norvig.com/lispy.html + +from __future__ import division +import math +import operator as op + +################ Types + +Symbol = str # A Lisp Symbol is implemented as a Python str +List = list # A Lisp List is implemented as a Python list +Number = (int, float) # A Lisp Number is implemented as a Python int or float + +################ Parsing: parse, tokenize, and read_from_tokens + +def parse(program): + "Read a Scheme expression from a string." + return read_from_tokens(tokenize(program)) + +def tokenize(s): + "Convert a string into a list of tokens." + return s.replace('(',' ( ').replace(')',' ) ').split() + +def read_from_tokens(tokens): + "Read an expression from a sequence of tokens." + if len(tokens) == 0: + raise SyntaxError('unexpected EOF while reading') + token = tokens.pop(0) + if '(' == token: + L = [] + while tokens[0] != ')': + L.append(read_from_tokens(tokens)) + tokens.pop(0) # pop off ')' + return L + elif ')' == token: + raise SyntaxError('unexpected )') + else: + return atom(token) + +def atom(token): + "Numbers become numbers; every other token is a symbol." + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + return Symbol(token) + +################ Environments + +def standard_env(): + "An environment with some Scheme standard procedures." + env = Env() + env.update(vars(math)) # sin, cos, sqrt, pi, ... + env.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'abs': abs, + 'append': op.add, + 'apply': apply, + 'begin': lambda *x: x[-1], + 'car': lambda x: x[0], + 'cdr': lambda x: x[1:], + 'cons': lambda x,y: [x] + y, + 'eq?': op.is_, + 'equal?': op.eq, + 'length': len, + 'list': lambda *x: list(x), + 'list?': lambda x: isinstance(x,list), + 'map': map, + 'max': max, + 'min': min, + 'not': op.not_, + 'null?': lambda x: x == [], + 'number?': lambda x: isinstance(x, Number), + 'procedure?': callable, + 'round': round, + 'symbol?': lambda x: isinstance(x, Symbol), + }) + return env + +class Env(dict): + "An environment: a dict of {'var':val} pairs, with an outer Env." + def __init__(self, parms=(), args=(), outer=None): + self.update(zip(parms, args)) + self.outer = outer + def find(self, var): + "Find the innermost Env where var appears." + return self if (var in self) else self.outer.find(var) + +global_env = standard_env() + +################ Interaction: A REPL + +def repl(prompt='lis.py> '): + "A prompt-read-eval-print loop." + while True: + val = eval(parse(raw_input(prompt))) + if val is not None: + print(lispstr(val)) + +def lispstr(exp): + "Convert a Python object back into a Lisp-readable string." + if isinstance(exp, List): + return '(' + ' '.join(map(lispstr, exp)) + ')' + else: + return str(exp) + +################ Procedures + +class Procedure(object): + "A user-defined Scheme procedure." + def __init__(self, parms, body, env): + self.parms, self.body, self.env = parms, body, env + def __call__(self, *args): + return eval(self.body, Env(self.parms, args, self.env)) + +################ eval + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + if isinstance(x, Symbol): # variable reference + return env.find(x)[x] + elif not isinstance(x, List): # constant literal + return x + elif x[0] == 'quote': # (quote exp) + (_, exp) = x + return exp + elif x[0] == 'if': # (if test conseq alt) + (_, test, conseq, alt) = x + exp = (conseq if eval(test, env) else alt) + return eval(exp, env) + elif x[0] == 'define': # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + elif x[0] == 'set!': # (set! var exp) + (_, var, exp) = x + env.find(var)[var] = eval(exp, env) + elif x[0] == 'lambda': # (lambda (var...) body) + (_, parms, body) = x + return Procedure(parms, body, env) + else: # (proc arg...) + proc = eval(x[0], env) + args = [eval(exp, env) for exp in x[1:]] + return proc(*args) \ No newline at end of file diff --git a/attachment/lispy.py b/attachment/lispy.py new file mode 100644 index 0000000..08923c1 --- /dev/null +++ b/attachment/lispy.py @@ -0,0 +1,318 @@ +################ Scheme Interpreter in Python + +## (c) Peter Norvig, 2010; See http://norvig.com/lispy2.html + +################ Symbol, Procedure, classes + +from __future__ import division +import re, sys, StringIO + +class Symbol(str): pass + +def Sym(s, symbol_table={}): + "Find or create unique Symbol entry for str s in symbol table." + if s not in symbol_table: symbol_table[s] = Symbol(s) + return symbol_table[s] + +_quote, _if, _set, _define, _lambda, _begin, _definemacro, = map(Sym, +"quote if set! define lambda begin define-macro".split()) + +_quasiquote, _unquote, _unquotesplicing = map(Sym, +"quasiquote unquote unquote-splicing".split()) + +class Procedure(object): + "A user-defined Scheme procedure." + def __init__(self, parms, exp, env): + self.parms, self.exp, self.env = parms, exp, env + def __call__(self, *args): + return eval(self.exp, Env(self.parms, args, self.env)) + +################ parse, read, and user interaction + +def parse(inport): + "Parse a program: read and expand/error-check it." + # Backwards compatibility: given a str, convert it to an InPort + if isinstance(inport, str): inport = InPort(StringIO.StringIO(inport)) + return expand(read(inport), toplevel=True) + +eof_object = Symbol('#') # Note: uninterned; can't be read + +class InPort(object): + "An input port. Retains a line of chars." + tokenizer = r"""\s*(,@|[('`,)]|"(?:[\\].|[^\\"])*"|;.*|[^\s('"`,;)]*)(.*)""" + def __init__(self, file): + self.file = file; self.line = '' + def next_token(self): + "Return the next token, reading new text into line buffer if needed." + while True: + if self.line == '': self.line = self.file.readline() + if self.line == '': return eof_object + token, self.line = re.match(InPort.tokenizer, self.line).groups() + if token != '' and not token.startswith(';'): + return token + +def readchar(inport): + "Read the next character from an input port." + if inport.line != '': + ch, inport.line = inport.line[0], inport.line[1:] + return ch + else: + return inport.file.read(1) or eof_object + +def read(inport): + "Read a Scheme expression from an input port." + def read_ahead(token): + if '(' == token: + L = [] + while True: + token = inport.next_token() + if token == ')': return L + else: L.append(read_ahead(token)) + elif ')' == token: raise SyntaxError('unexpected )') + elif token in quotes: return [quotes[token], read(inport)] + elif token is eof_object: raise SyntaxError('unexpected EOF in list') + else: return atom(token) + # body of read: + token1 = inport.next_token() + return eof_object if token1 is eof_object else read_ahead(token1) + +quotes = {"'":_quote, "`":_quasiquote, ",":_unquote, ",@":_unquotesplicing} + +def atom(token): + 'Numbers become numbers; #t and #f are booleans; "..." string; otherwise Symbol.' + if token == '#t': return True + elif token == '#f': return False + elif token[0] == '"': return token[1:-1].decode('string_escape') + try: return int(token) + except ValueError: + try: return float(token) + except ValueError: + try: return complex(token.replace('i', 'j', 1)) + except ValueError: + return Sym(token) + +def to_string(x): + "Convert a Python object back into a Lisp-readable string." + if x is True: return "#t" + elif x is False: return "#f" + elif isa(x, Symbol): return x + elif isa(x, str): return '"%s"' % x.encode('string_escape').replace('"',r'\"') + elif isa(x, list): return '('+' '.join(map(to_string, x))+')' + elif isa(x, complex): return str(x).replace('j', 'i') + else: return str(x) + +def load(filename): + "Eval every expression from a file." + repl(None, InPort(open(filename)), None) + +def repl(prompt='lispy> ', inport=InPort(sys.stdin), out=sys.stdout): + "A prompt-read-eval-print loop." + sys.stderr.write("Lispy version 2.0\n") + while True: + try: + if prompt: sys.stderr.write(prompt) + x = parse(inport) + if x is eof_object: return + val = eval(x) + if val is not None and out: print >> out, to_string(val) + except Exception as e: + print '%s: %s' % (type(e).__name__, e) + +################ Environment class + +class Env(dict): + "An environment: a dict of {'var':val} pairs, with an outer Env." + def __init__(self, parms=(), args=(), outer=None): + # Bind parm list to corresponding args, or single parm to list of args + self.outer = outer + if isa(parms, Symbol): + self.update({parms:list(args)}) + else: + if len(args) != len(parms): + raise TypeError('expected %s, given %s, ' + % (to_string(parms), to_string(args))) + self.update(zip(parms,args)) + def find(self, var): + "Find the innermost Env where var appears." + if var in self: return self + elif self.outer is None: raise LookupError(var) + else: return self.outer.find(var) + +def is_pair(x): return x != [] and isa(x, list) +def cons(x, y): return [x]+y + +def callcc(proc): + "Call proc with current continuation; escape only" + ball = RuntimeWarning("Sorry, can't continue this continuation any longer.") + def throw(retval): ball.retval = retval; raise ball + try: + return proc(throw) + except RuntimeWarning as w: + if w is ball: return ball.retval + else: raise w + +def add_globals(self): + "Add some Scheme standard procedures." + import math, cmath, operator as op + self.update(vars(math)) + self.update(vars(cmath)) + self.update({ + '+':op.add, '-':op.sub, '*':op.mul, '/':op.div, 'not':op.not_, + '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, + 'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':cons, + 'car':lambda x:x[0], 'cdr':lambda x:x[1:], 'append':op.add, + 'list':lambda *x:list(x), 'list?': lambda x:isa(x,list), + 'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol), + 'boolean?':lambda x: isa(x, bool), 'pair?':is_pair, + 'port?': lambda x:isa(x,file), 'apply':lambda proc,l: proc(*l), + 'eval':lambda x: eval(expand(x)), 'load':lambda fn: load(fn), 'call/cc':callcc, + 'open-input-file':open,'close-input-port':lambda p: p.file.close(), + 'open-output-file':lambda f:open(f,'w'), 'close-output-port':lambda p: p.close(), + 'eof-object?':lambda x:x is eof_object, 'read-char':readchar, + 'read':read, 'write':lambda x,port=sys.stdout:port.write(to_string(x)), + 'display':lambda x,port=sys.stdout:port.write(x if isa(x,str) else to_string(x))}) + return self + +isa = isinstance + +global_env = add_globals(Env()) + +################ eval (tail recursive) + +def eval(x, env=global_env): + "Evaluate an expression in an environment." + while True: + if isa(x, Symbol): # variable reference + return env.find(x)[x] + elif not isa(x, list): # constant literal + return x + elif x[0] is _quote: # (quote exp) + (_, exp) = x + return exp + elif x[0] is _if: # (if test conseq alt) + (_, test, conseq, alt) = x + x = (conseq if eval(test, env) else alt) + elif x[0] is _set: # (set! var exp) + (_, var, exp) = x + env.find(var)[var] = eval(exp, env) + return None + elif x[0] is _define: # (define var exp) + (_, var, exp) = x + env[var] = eval(exp, env) + return None + elif x[0] is _lambda: # (lambda (var*) exp) + (_, vars, exp) = x + return Procedure(vars, exp, env) + elif x[0] is _begin: # (begin exp+) + for exp in x[1:-1]: + eval(exp, env) + x = x[-1] + else: # (proc exp*) + exps = [eval(exp, env) for exp in x] + proc = exps.pop(0) + if isa(proc, Procedure): + x = proc.exp + env = Env(proc.parms, exps, proc.env) + else: + return proc(*exps) + +################ expand + +def expand(x, toplevel=False): + "Walk tree of x, making optimizations/fixes, and signaling SyntaxError." + require(x, x!=[]) # () => Error + if not isa(x, list): # constant => unchanged + return x + elif x[0] is _quote: # (quote exp) + require(x, len(x)==2) + return x + elif x[0] is _if: + if len(x)==3: x = x + [None] # (if t c) => (if t c None) + require(x, len(x)==4) + return map(expand, x) + elif x[0] is _set: + require(x, len(x)==3); + var = x[1] # (set! non-var exp) => Error + require(x, isa(var, Symbol), "can set! only a symbol") + return [_set, var, expand(x[2])] + elif x[0] is _define or x[0] is _definemacro: + require(x, len(x)>=3) + _def, v, body = x[0], x[1], x[2:] + if isa(v, list) and v: # (define (f args) body) + f, args = v[0], v[1:] # => (define f (lambda (args) body)) + return expand([_def, f, [_lambda, args]+body]) + else: + require(x, len(x)==3) # (define non-var/list exp) => Error + require(x, isa(v, Symbol), "can define only a symbol") + exp = expand(x[2]) + if _def is _definemacro: + require(x, toplevel, "define-macro only allowed at top level") + proc = eval(exp) + require(x, callable(proc), "macro must be a procedure") + macro_table[v] = proc # (define-macro v proc) + return None # => None; add v:proc to macro_table + return [_define, v, exp] + elif x[0] is _begin: + if len(x)==1: return None # (begin) => None + else: return [expand(xi, toplevel) for xi in x] + elif x[0] is _lambda: # (lambda (x) e1 e2) + require(x, len(x)>=3) # => (lambda (x) (begin e1 e2)) + vars, body = x[1], x[2:] + require(x, (isa(vars, list) and all(isa(v, Symbol) for v in vars)) + or isa(vars, Symbol), "illegal lambda argument list") + exp = body[0] if len(body) == 1 else [_begin] + body + return [_lambda, vars, expand(exp)] + elif x[0] is _quasiquote: # `x => expand_quasiquote(x) + require(x, len(x)==2) + return expand_quasiquote(x[1]) + elif isa(x[0], Symbol) and x[0] in macro_table: + return expand(macro_table[x[0]](*x[1:]), toplevel) # (m arg...) + else: # => macroexpand if m isa macro + return map(expand, x) # (f arg...) => expand each + +def require(x, predicate, msg="wrong length"): + "Signal a syntax error if predicate is false." + if not predicate: raise SyntaxError(to_string(x)+': '+msg) + +_append, _cons, _let = map(Sym, "append cons let".split()) + +def expand_quasiquote(x): + """Expand `x => 'x; `,x => x; `(,@x y) => (append x y) """ + if not is_pair(x): + return [_quote, x] + require(x, x[0] is not _unquotesplicing, "can't splice here") + if x[0] is _unquote: + require(x, len(x)==2) + return x[1] + elif is_pair(x[0]) and x[0][0] is _unquotesplicing: + require(x[0], len(x[0])==2) + return [_append, x[0][1], expand_quasiquote(x[1:])] + else: + return [_cons, expand_quasiquote(x[0]), expand_quasiquote(x[1:])] + +def let(*args): + args = list(args) + x = cons(_let, args) + require(x, len(args)>1) + bindings, body = args[0], args[1:] + require(x, all(isa(b, list) and len(b)==2 and isa(b[0], Symbol) + for b in bindings), "illegal binding list") + vars, vals = zip(*bindings) + return [[_lambda, list(vars)]+map(expand, body)] + map(expand, vals) + +macro_table = {_let:let} ## More macros can go here + +eval(parse("""(begin + +(define-macro and (lambda args + (if (null? args) #t + (if (= (length args) 1) (car args) + `(if ,(car args) (and ,@(cdr args)) #f))))) + +;; More macros can also go here + +)""")) + +if __name__ == '__main__': + repl() + diff --git a/llm.l b/llm.l index a625482..9167a89 100644 --- a/llm.l +++ b/llm.l @@ -35,8 +35,8 @@ * Data types: * #fn Functions * t,nil boolean (nand ) -> boolean - * "str" Strings (streq ) -> boolean - * abc Symboles (symeq ) -> boolean + * "str" Strings + * abc Symboles * 1234 Number/Integer (inv ) -> -num * (add ) -> num * (lt ) -> boolean @@ -48,7 +48,7 @@ * (lm () ()) * create function with one or more PARAMs and * BODY. - * (if ) + * (if ) * Evaluate BOOL and execute CASETRUE if true, * otherwise evaulate CASEFALSE * (quote a) @@ -60,6 +60,29 @@ * systems) * BUGS: * no freeing of memory (yet) + * + * Literature: + * [1] https://news.ycombinator.com/item?id=8714988 + * Discussion about "minimal" set of primitives + * for a LISP interpreter. + * [2] https://stackoverflow.com/questions/3482389/ + * How many primitives does it take to build a + * LISP machine? Ten, seven or five? + * [3] http://www.cs.cmu.edu/Groups/AI/html/faqs/lang/ \ + * lisp/part1/faq-doc-6.html + * Discussion about the minimal set of primitives + * (see attachment) + * [4] https://www.cs.cmu.edu/afs/cs/project/ai-repository \ + * /ai/lang/lisp/impl/awk/0.html + * LISP Interpreter in AWK. (see attachments) + * [5] http://norvig.com/lispy.html + * Similar project, with Python instead of C + * [6] http://norvig.com/lispy2.html + * Improved version of [5] in Python. (attachment) + * [7] Structure and Interpretation of Computer Programs, + * by Abelson, Sussman, and Sussman + * [8] FernUniversität Hagen: Logisches und funktionales + * Programmieren, by Prof.Dr.Beierle. */ %{ #define _POSIX_C_SOURCE 200809L