A working version of Scheme in 20 Minutes.
This commit is contained in:
commit
5a3e5272a2
|
@ -0,0 +1,7 @@
|
||||||
|
*#
|
||||||
|
.#*
|
||||||
|
*~
|
||||||
|
*.orig
|
||||||
|
npm-debug.log
|
||||||
|
node_modules/*
|
||||||
|
tmp/
|
|
@ -0,0 +1,7 @@
|
||||||
|
lib/lisp_parser.js: lib/lisp_parser.peg
|
||||||
|
node_modules/.bin/pegjs $< $@
|
||||||
|
|
||||||
|
test: lib/lisp_parser.js
|
||||||
|
node_modules/.bin/coffee bin/lisp test.scm
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
# James Coglan's "A Simple Scheme In 20 Minutes"
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
A version of James Coglan's "A Language In 20 Minutes" (see:
|
||||||
|
https://www.youtube.com/watch?v=CqhL-BDT8lg for the whole 40 minute
|
||||||
|
presentation). It took me about three hours to make this work. This
|
||||||
|
version is tighter, using Coffeescript and PegJS instead of Javascript
|
||||||
|
and whatever parser/lexer he described.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
When I went to university, my degree was deliberately steered away from
|
||||||
|
the esoterica of compilers and interpreters. My degree and financial
|
||||||
|
backing was predicated on my getting "practical" programming skills, so
|
||||||
|
I took classes in COBOL, Fortran, ADA, SQL, Accounting, and similar
|
||||||
|
subjects intended to make me a "financial products" programmer.
|
||||||
|
|
||||||
|
The Internet came along and gave me a much more interesting career, but
|
||||||
|
now it's time to rectify the shortcoming of my education and study
|
||||||
|
programming languages themselves. This is a "My First Programming
|
||||||
|
Lanugage."
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
It only has three special forms: 'define', 'lambda', and 'if'. It
|
||||||
|
understands addition, subtraction, multiplication, division, and
|
||||||
|
equality for the purposes of the 'if' special form, although it's
|
||||||
|
clearly treating arithmetic as 'special forms' for the purpose of doing
|
||||||
|
the math. It's good enough to handle lexical scoping and recursion, and
|
||||||
|
it handles basic integer arithmetic. There is a bug is the lexer such
|
||||||
|
that symbols that start with a numeral won't be read right, but I'm too
|
||||||
|
lazy to fix it.
|
||||||
|
|
||||||
|
It has no macros. Sorry about that.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Coffeescript, pegjs.
|
||||||
|
|
||||||
|
## LICENSE AND COPYRIGHT NOTICE: NO WARRANTY GRANTED OR IMPLIED
|
||||||
|
|
||||||
|
Copyright (c) 2015 Elf M. Sternberg
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
lisp = require '../lib/lisp'
|
||||||
|
fs = require 'fs'
|
||||||
|
{inspect} = require 'util'
|
||||||
|
|
||||||
|
console.log inspect((lisp.run process.argv[2]), true, null, false)
|
|
@ -0,0 +1,14 @@
|
||||||
|
lispeval = (element, scope) ->
|
||||||
|
switch element.type
|
||||||
|
when 'boolean' then element.value == '#t'
|
||||||
|
when 'number' then parseInt(element.value, 10)
|
||||||
|
when 'symbol'
|
||||||
|
scope.lookup(element.value)
|
||||||
|
when 'list'
|
||||||
|
proc = lispeval(element.value[0], scope)
|
||||||
|
args = element.value.slice(1)
|
||||||
|
proc.apply(args, scope)
|
||||||
|
|
||||||
|
else throw new Error ("Unrecognized type in parse")
|
||||||
|
|
||||||
|
module.exports = lispeval
|
|
@ -0,0 +1,21 @@
|
||||||
|
lispeval = require './eval'
|
||||||
|
|
||||||
|
class Proc
|
||||||
|
constructor: (@scope, @params, @body) ->
|
||||||
|
apply: (cells, scope) ->
|
||||||
|
args = (cells.map (i) -> lispeval(i, scope))
|
||||||
|
if @body instanceof Function
|
||||||
|
@body.apply(this, args)
|
||||||
|
else
|
||||||
|
inner = @scope.fork()
|
||||||
|
@params.forEach((name, i) -> inner.set(name, args[i]))
|
||||||
|
@body.map((e) -> lispeval(e, inner)).pop()
|
||||||
|
|
||||||
|
class Syntax extends Proc
|
||||||
|
apply: (cells, scope) ->
|
||||||
|
return @body(cells, scope)
|
||||||
|
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
Proc: Proc
|
||||||
|
Syntax: Syntax
|
|
@ -0,0 +1,12 @@
|
||||||
|
fs = require 'fs'
|
||||||
|
{parse} = require './lisp_parser'
|
||||||
|
lisp = require './parser'
|
||||||
|
Scope = require './scope'
|
||||||
|
{inspect} = require 'util'
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
run: (pathname) ->
|
||||||
|
text = fs.readFileSync(pathname, 'utf8')
|
||||||
|
ast = parse(text)
|
||||||
|
return lisp(ast, new Scope.Toplevel())
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
lisp
|
||||||
|
= cell*
|
||||||
|
|
||||||
|
cell
|
||||||
|
= _* datum:datum _*
|
||||||
|
{ return datum }
|
||||||
|
|
||||||
|
datum
|
||||||
|
= list / boolean / number / symbol
|
||||||
|
|
||||||
|
list
|
||||||
|
= "(" items:cell* ")"
|
||||||
|
{ return { type: "list", value: items } }
|
||||||
|
|
||||||
|
boolean
|
||||||
|
= b:("#t" / "#f")
|
||||||
|
{ return { type: 'boolean', value: b } }
|
||||||
|
|
||||||
|
delim
|
||||||
|
= paren / _
|
||||||
|
|
||||||
|
number
|
||||||
|
= b:( [0-9]+ )
|
||||||
|
{ return { type: 'number', value: b.join("") } }
|
||||||
|
|
||||||
|
symbol
|
||||||
|
= b:(!delim c:. { return c })+
|
||||||
|
{ return { type: 'symbol', value: b.join("") } }
|
||||||
|
|
||||||
|
paren
|
||||||
|
= "(" / ")"
|
||||||
|
|
||||||
|
_
|
||||||
|
= w:[ \t\n\r]+
|
|
@ -0,0 +1,8 @@
|
||||||
|
lispeval = require './eval'
|
||||||
|
|
||||||
|
lisp = (ast, scope) ->
|
||||||
|
ast.map((e) -> lispeval(e, scope)).pop()
|
||||||
|
|
||||||
|
module.exports = lisp
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
parser = require './parser'
|
||||||
|
lispeval = require './eval'
|
||||||
|
|
||||||
|
{Proc, Syntax} = require './fn'
|
||||||
|
|
||||||
|
class Scope
|
||||||
|
constructor: (@parent) ->
|
||||||
|
@_symbols = {}
|
||||||
|
|
||||||
|
lookup: (name) ->
|
||||||
|
if @_symbols[name]?
|
||||||
|
return @_symbols[name]
|
||||||
|
|
||||||
|
if @parent
|
||||||
|
return @parent.lookup(name)
|
||||||
|
|
||||||
|
throw new Error "Unknown variable '#{name}'"
|
||||||
|
|
||||||
|
define: (name, body) ->
|
||||||
|
@set name, (new Proc(this, [], body))
|
||||||
|
|
||||||
|
syntax: (name, body) ->
|
||||||
|
@set name, (new Syntax(this, [], body))
|
||||||
|
|
||||||
|
fork: -> new Scope(@)
|
||||||
|
|
||||||
|
set: (name, value) ->
|
||||||
|
@_symbols[name] = value
|
||||||
|
|
||||||
|
class Toplevel extends Scope
|
||||||
|
constructor: (@parent = null) ->
|
||||||
|
super
|
||||||
|
@define '+', (a, b) -> a + b
|
||||||
|
@define '-', (a, b) -> a - b
|
||||||
|
@define '*', (a, b) -> a * b
|
||||||
|
@define '==', (a, b) -> a == b
|
||||||
|
|
||||||
|
@syntax 'define', (list, scope) ->
|
||||||
|
scope.set(list[0].value, lispeval(list[1], scope))
|
||||||
|
|
||||||
|
@syntax 'lambda', (list, scope) ->
|
||||||
|
params = list[0].value.map (n) -> return n.value
|
||||||
|
new Proc(scope, params, list.slice(1))
|
||||||
|
|
||||||
|
@syntax 'if', (list, scope) ->
|
||||||
|
lispeval(list[if lispeval(list[0], scope) then 1 else 2], scope)
|
||||||
|
|
||||||
|
Scope.Toplevel = Toplevel
|
||||||
|
|
||||||
|
module.exports = Scope
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "LangIn20",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"description": "A Coffeescript & PegJS rendition of James Coglan's 'Javascript in 20 Minutes'",
|
||||||
|
"main": "bin/lisp",
|
||||||
|
"dependencies": {
|
||||||
|
"coffee-script": "^1.9.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"pegjs": "^0.8.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"scheme",
|
||||||
|
"practice",
|
||||||
|
"interpreter",
|
||||||
|
"javascript",
|
||||||
|
"coffeescript",
|
||||||
|
"pegjs"
|
||||||
|
],
|
||||||
|
"author": "Elf M. Sternberg",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
Loading…
Reference in New Issue