diff --git a/.vscode/settings.json b/.vscode/settings.json index 5c80254..78321c5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,6 @@ "[python]": { "editor.defaultFormatter": "ms-python.autopep8" }, - "python.formatting.provider": "none" + "python.formatting.provider": "none", + "cSpell.words": ["expt"] } diff --git a/Python3/lisp.py b/Python3/lisp.py index 6ad3593..256ac08 100644 --- a/Python3/lisp.py +++ b/Python3/lisp.py @@ -3,7 +3,7 @@ import math import operator as op program = "(begin (define r 10) (* pi (* r r)))" -# Types +################ Types Symbol = str List = list @@ -12,19 +12,16 @@ Atom = (Symbol, Number) Exp = (Atom, list) Env = dict -# Parsing: parse, tokenize, and read_from_tokens - +################ Parsing: parse, tokenize, and read_from_tokens def parse(program: str) -> Exp: "Read a Scheme expression from a string." return read_from_tokens(tokenize(program)) - def tokenize(chars: str) -> list: "Convert a string of characters into list of tokens." return chars.replace('(', ' ( ').replace(')', ' ) ').split() - def read_from_tokens(tokens: list) -> Exp: "Read an expression from a sequence of tokens." if len(tokens) == 0: @@ -41,7 +38,6 @@ def read_from_tokens(tokens: list) -> Exp: else: return atom(token) - def atom(token: str) -> Atom: "Numbers become numbers; every other token is a symbol." try: @@ -82,8 +78,32 @@ def standard_env() -> Env: 'print': print, 'procedure?': callable, 'round': round, - 'symbol?': lambda x : isinstance(x, Symbol) + 'symbol?': lambda x : isinstance(x, Symbol), + }) return env -global_env = standard_env() \ No newline at end of file +global_env = standard_env() + +def eval(x: Exp, env=global_env) -> Exp: + 'Evaluate an expression in an environment.' + if isinstance(x, Symbol): + return env[x] + elif isinstance(x, Number): + return x + elif x[0] == 'if': + (_,test,conseq,alt) = x + exp = ( conseq if eval(test,env) else alt) + return eval(exp, env) + elif x[0] == 'define': + (_,symbol, exp) = x + env[symbol] = eval(exp,env) + else: + proc = eval(x[0], env) + args = [eval(arg,env) for arg in x[1:]] + return proc(*args) + +print(eval(parse(program))) + + + \ No newline at end of file diff --git a/TypeScript/lisp.ts b/TypeScript/lisp.ts index 4597ffb..a45d3e7 100644 --- a/TypeScript/lisp.ts +++ b/TypeScript/lisp.ts @@ -1,47 +1,52 @@ // Lispts: Scheme Interpreter in TypeScript -const program = "(begin (define r 10) (* pi (* r r)))" +const program = '(begin (define r 10) (* pi (* r r)))' -// types +//--------- types ------------ type LSymbolType = string // A Lisp Symbol(alias TSymbol) is implemented as TypeScript string type LNumber = number // A Lisp Symbol(alias TSymbol) is implemented as TypeScript number type Atom = LSymbolType | number // A Lisp Atom is a Symbol or Number impl type List = Array // A Lisp List is implemented as a TypeScript array type Exp = Atom | List // A Lisp expression is an Atom or List -type Env = Map; +type Env = Map +//---------- utils ------------- class LSymbol { - readonly value: string; + readonly value: string constructor(value: string) { - this.value = value; + this.value = value } toString(): string { - return this.value; + return this.value } } -// utils -const deepEqual = (a: any, b: any): boolean =>{ - if(a=== b) return true +const deepEqual = (a: any, b: any): boolean => { + if (a === b) return true - if(typeof a !== 'object'|| typeof b !== 'object' || a === null || b === null ) - return false + if ( + typeof a !== 'object' || + typeof b !== 'object' || + a === null || + b === null + ) + return false const keyA = Object.keys(a) const keyB = Object.keys(b) - if(keyA.length !== keyB.length) return false + if (keyA.length !== keyB.length) return false - - for(const key of keyA){ - if(!keyB.includes(key) || !deepEqual(a[key], b[key])) return false + for (const key of keyA) { + if (!keyB.includes(key) || !deepEqual(a[key], b[key])) return false } - + return true } +//---------- Parsing: parse, tokenize, and read_from_tokens ---------------- /** * Read a Scheme expression from a string. */ @@ -61,9 +66,9 @@ const tokenize = (char: string): string[] => * Numbers become numbers; every other token is a symbol(LSymbol). */ const atom = (token: string): Atom => - isNaN(Number(token)) ? token as LSymbolType : Number(token) + isNaN(Number(token)) ? (token as LSymbolType) : Number(token) - /** +/** * Read an expression from a sequence of tokens */ const readFromTokens = (tokens: string[]): Exp => { @@ -85,7 +90,7 @@ const readFromTokens = (tokens: string[]): Exp => { } } -// Environments +// ---------------- Environments ------------------ /** * An environment with some Scheme standard procedures */ @@ -104,26 +109,60 @@ const standardEnv = (): Env => { env.set('append', (a: any[], b: any[]) => a.concat(b)) env.set('apply', (proc: Function, args: any[]) => proc(...args)) env.set('begin', (...x: any) => x[x.length - 1]) - env.set('car', (...x: any) => x[0]) - env.set('cdr', (...x: any) => x.slice(1)) - env.set('cons', (x: any, y: any) => [x, ...y]) + env.set('car', (...x: any[]) => x[0]) + env.set('cdr', (...x: any[]) => x.slice(1)) + env.set('cos', Math.cos) + env.set('cons', (x: any, y: any[]) => [x, ...y]) env.set('eq?', (a: any, b: any) => Object.is(a, b)) env.set('equal?', (a: any, b: any) => deepEqual(a, b)) env.set('expt', Math.pow) env.set('length', (x: any[]) => x.length) env.set('list', (...x: any[]) => Array.from(x)) env.set('list?', (x: any) => Array.isArray(x)) - env.set('map', Array.prototype.map) + env.set('map', (func: (value: any, index: number, array: any[]) => unknown, arr: any[]) => arr.map(func)) env.set('max', Math.max) env.set('min', Math.min) env.set('not', (x: boolean) => !x) env.set('null?', (x: any[]) => x.length === 0) env.set('number?', (x: any) => typeof x === 'number') env.set('print', console.log) + env.set('pi', Math.PI) env.set('procedure?', (x: any) => typeof x === 'function') env.set('round', Math.round) - env.set('symbol?', (x: any) =>x instanceof LSymbol ) + env.set('sin', Math.sin) + env.set('symbol?', (x: any) => x instanceof LSymbol) return env } -const globalEnv = standardEnv() \ No newline at end of file +const globalEnv = standardEnv() + +/** + * Evaluate an expression in an environment. + */ +const evalScheme = (x: Exp, env = globalEnv): Exp => { + if (typeof x === 'string') { + // variable reference + return env.get(x) ?? new Error('Undefined symbol:' + x) + } else if (typeof x === 'number') { + return x + } else if (Array.isArray(x) && x[0] === 'if') { + const [_, test, consq, alt] = x + const exp = evalScheme(test, env) ? consq : alt + return evalScheme(exp, env) + } else if (Array.isArray(x) && x[0] === 'define') { + const [_, symbol, exp] = x + const value = evalScheme(exp, env) + env.set(symbol, value) + return value + } else { + const [op, ...args] = (x as List).map((e: Exp) => evalScheme(e, env)) + + if (typeof op === 'function') { + return (op as Function)(...args) + } else { + throw new Error(`${op} is not function`) + } + } +} + +console.log(evalScheme(parse(program)))