Backing up.
This commit is contained in:
parent
a7855a737c
commit
ac2b89fa5a
2
Makefile
2
Makefile
|
@ -1,7 +1,7 @@
|
|||
lib/lisp_parser.js: lib/lisp_parser.peg
|
||||
node_modules/.bin/pegjs $< $@
|
||||
|
||||
test: lib/lisp_parser.js
|
||||
tests: lib/lisp_parser.js
|
||||
node_modules/.bin/coffee bin/lisp test.scm
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
{car, cdr, cons, nil, nilp, listp} = require './lists'
|
||||
|
||||
NEWLINES = ["\n", "\r", "\x0B", "\x0C"]
|
||||
WHITESPACE = [" ", "\t"].concat(NEWLINES)
|
||||
|
||||
EOF = new (class)
|
||||
EOO = new (class)
|
||||
|
||||
class Source
|
||||
constructor: (@inStream) ->
|
||||
@index = 0
|
||||
@max = @inStream.length - 1
|
||||
@line = 0
|
||||
@column = 0
|
||||
|
||||
peek: -> @inStream[@index]
|
||||
|
||||
position: -> [@line, @column]
|
||||
|
||||
next: ->
|
||||
c = @peek()
|
||||
return EOF if @done()
|
||||
@index++
|
||||
[@line, @column] = if @peek() in NEWLINES then [@line + 1, 0] else [@line, @column + 1]
|
||||
c
|
||||
|
||||
done: -> @index > @max
|
||||
|
||||
# IO -> IO
|
||||
skipWS = (inStream) ->
|
||||
while inStream.peek() in WHITESPACE then inStream.next()
|
||||
|
||||
# (type, value, line, column) -> (node {type, value, line, column)}
|
||||
makeObj = (type, value, line, column) ->
|
||||
cons type, (cons value, (cons line, (cons column)))
|
||||
|
||||
# msg -> (IO -> Node => Error)
|
||||
handleError = (message) ->
|
||||
(line, column) -> makeObj('error', message, line, column)
|
||||
|
||||
# IO -> Node => Comment
|
||||
readComment = (inStream) ->
|
||||
[line, column] = inStream.position()
|
||||
r = (while inStream.peek() != "\n" and not inStream.done()
|
||||
inStream.next()).join("")
|
||||
if not inStream.done()
|
||||
inStream.next()
|
||||
makeObj 'comment', r, line, column
|
||||
|
||||
# IO -> (Node => Literal => String) | Error
|
||||
readString = (inStream) ->
|
||||
[line, column] = inStream.position()
|
||||
inStream.next()
|
||||
string = until inStream.peek() == '"' or inStream.done()
|
||||
if inStream.peek() == '\\'
|
||||
inStream.next()
|
||||
inStream.next()
|
||||
if inStream.done()
|
||||
return handleError("end of file seen before end of string.")(line, column)
|
||||
inStream.next()
|
||||
makeObj 'string', (string.join ''), line, column
|
||||
|
||||
# (String) -> (Node => Literal => Number) | Nothing
|
||||
readMaybeNumber = (symbol) ->
|
||||
if symbol[0] == '+'
|
||||
return readMaybeNumber symbol.substr(1)
|
||||
if symbol[0] == '-'
|
||||
ret = readMaybeNumber symbol.substr(1)
|
||||
return if ret? then -1 * ret else undefined
|
||||
if symbol.search(/^0x[0-9a-fA-F]+$/) > -1
|
||||
return parseInt(symbol, 16)
|
||||
if symbol.search(/^0[0-9a-fA-F]+$/) > -1
|
||||
return parseInt(symbol, 8)
|
||||
if symbol.search(/^[0-9]+$/) > -1
|
||||
return parseInt(symbol, 10)
|
||||
if symbol.search(/^nil$/) > -1
|
||||
return nil
|
||||
undefined
|
||||
|
||||
# (IO, macros) -> (IO, Node => Number | Symbol) | Error
|
||||
readSymbol = (inStream, tableKeys) ->
|
||||
[line, column] = inStream.position()
|
||||
symbol = (until (inStream.done() or inStream.peek() in tableKeys or inStream.peek() in WHITESPACE)
|
||||
inStream.next()).join ''
|
||||
number = readMaybeNumber symbol
|
||||
if number?
|
||||
return makeObj 'number', number, line, column
|
||||
makeObj 'symbol', symbol, line, column
|
||||
|
||||
|
||||
# (Delim, TypeName) -> IO -> (IO, node) | Error
|
||||
makeReadPair = (delim, type) ->
|
||||
# IO -> (IO, Node) | Error
|
||||
(inStream) ->
|
||||
inStream.next()
|
||||
skipWS inStream
|
||||
[line, column] = inStream.position()
|
||||
if inStream.peek() == delim
|
||||
inStream.next()
|
||||
return makeObj(type, nil, line, column)
|
||||
|
||||
# IO -> (IO, Node) | Error
|
||||
readEachPair = (inStream) ->
|
||||
[line, column] = inStream.position()
|
||||
obj = read inStream, true, null, true
|
||||
if inStream.peek() == delim then return cons obj, nil
|
||||
if inStream.done() then return handleError("Unexpected end of input")(line, column)
|
||||
return obj if (car obj) == 'error'
|
||||
cons obj, readEachPair(inStream)
|
||||
|
||||
ret = makeObj type, readEachPair(inStream), line, column
|
||||
inStream.next()
|
||||
ret
|
||||
|
||||
# Type -> (IO -> (IO, Node))
|
||||
prefixReader = (type) ->
|
||||
# IO -> (IO, Node)
|
||||
(inStream) ->
|
||||
[line, column] = inStream.position()
|
||||
inStream.next()
|
||||
[line1, column1] = inStream.position()
|
||||
obj = read inStream, true, null, true
|
||||
return obj if (car obj) == 'error'
|
||||
makeObj "list", cons((makeObj("symbol", type, line1, column1)), cons(obj)), line, column
|
||||
|
||||
# I really wanted to make anything more complex than a list (like an
|
||||
# object or a vector) something handled by a read macro. Maybe in a
|
||||
# future revision I can vertically de-integrate these.
|
||||
|
||||
readMacros =
|
||||
'"': readString
|
||||
'(': makeReadPair ')', 'list'
|
||||
')': handleError "Closing paren encountered"
|
||||
'[': makeReadPair ']', 'vector'
|
||||
']': handleError "Closing bracket encountered"
|
||||
'{': makeReadPair('}', 'record', (res) ->
|
||||
res.length % 2 == 0 and true or mkerr "record key without value")
|
||||
'}': handleError "Closing curly without corresponding opening."
|
||||
"`": prefixReader 'back-quote'
|
||||
"'": prefixReader 'quote'
|
||||
",": prefixReader 'unquote'
|
||||
";": readComment
|
||||
|
||||
|
||||
# Given a stream, reads from the stream until a single complete lisp
|
||||
# object has been found and returns the object
|
||||
|
||||
# IO -> Form
|
||||
read = (inStream, eofErrorP = false, eofError = EOF, recursiveP = false, inReadMacros = null, keepComments = false) ->
|
||||
inStream = if inStream instanceof Source then inStream else new Source inStream
|
||||
inReadMacros = if InReadMacros? then inReadMacros else readMacros
|
||||
inReadMacroKeys = (i for i of inReadMacros)
|
||||
|
||||
c = inStream.peek()
|
||||
|
||||
# (IO, Char) -> (IO, Node) | Error
|
||||
matcher = (inStream, c) ->
|
||||
if inStream.done()
|
||||
return if recursiveP then handleError('EOF while processing nested object')(inStream) else nil
|
||||
if c in WHITESPACE
|
||||
inStream.next()
|
||||
return nil
|
||||
if c == ';'
|
||||
return readComment(inStream)
|
||||
ret = if c in inReadMacroKeys then inReadMacros[c](inStream) else readSymbol(inStream, inReadMacroKeys)
|
||||
skipWS inStream
|
||||
ret
|
||||
|
||||
while true
|
||||
form = matcher inStream, c
|
||||
skip = (not nilp form) and (car form == 'comment') and not keepComments
|
||||
break if (not skip and not nilp form) or inStream.done()
|
||||
c = inStream.peek()
|
||||
null
|
||||
form
|
||||
|
||||
# readForms assumes that the string provided contains zero or more
|
||||
# forms. As such, it always returns a list of zero or more forms.
|
||||
|
||||
# IO -> (Form* | Error)
|
||||
readForms = (inStream) ->
|
||||
inStream = if inStream instanceof Source then inStream else new Source inStream
|
||||
return nil if inStream.done()
|
||||
|
||||
# IO -> (FORM*, IO) | Error
|
||||
[line, column] = inStream.position()
|
||||
readEach = (inStream) ->
|
||||
obj = read inStream, true, null, false
|
||||
return nil if (nilp obj)
|
||||
return obj if (car obj) == 'error'
|
||||
cons obj, readEach inStream
|
||||
|
||||
obj = readEach inStream
|
||||
if (car obj) == 'error' then obj else makeObj "list", obj, line, column
|
||||
|
||||
exports.read = read
|
||||
exports.readForms = readForms
|
|
@ -0,0 +1,50 @@
|
|||
{car, cdr, cons, listp, nilp, nil, list, listToString} = require './lists'
|
||||
|
||||
reduce = (lst, iteratee, memo, context) ->
|
||||
count = 0
|
||||
ptr = lst
|
||||
while not nilp ptr
|
||||
[item, ptr] = [(car ptr), (cdr ptr)]
|
||||
memo = iteratee.call(context, memo, item, count, lst)
|
||||
count++
|
||||
iteratee.call(context, memo, nil, count, lst)
|
||||
|
||||
map = (lst, iteratee, context) ->
|
||||
return nil if nilp lst
|
||||
root = cons("")
|
||||
|
||||
reducer = (memo, item, count) ->
|
||||
next = cons(iteratee.call(context, item, count, lst))
|
||||
memo[1] = next
|
||||
next
|
||||
|
||||
reduce(lst, reducer, root, context)
|
||||
(cdr root)
|
||||
|
||||
rmap = (lst, iteratee, context) ->
|
||||
reducer = (memo, item, count) ->
|
||||
cons(iteratee.call(context, item, count, lst), memo)
|
||||
reduce(lst, reducer, nil, context)
|
||||
|
||||
|
||||
filter = (lst, iteratee, context) ->
|
||||
return nil if nilp lst
|
||||
root = cons("")
|
||||
|
||||
reducer = (memo, item, count) ->
|
||||
if iteratee.call(context, item, count, lst)
|
||||
next = cons(item)
|
||||
memo[1] = next
|
||||
next
|
||||
else
|
||||
memo
|
||||
|
||||
reduce(lst, reducer, root, context)
|
||||
(cdr root)
|
||||
|
||||
module.exports =
|
||||
reduce: reduce
|
||||
map: map
|
||||
rmap: rmap
|
||||
filter: filter
|
||||
|
|
@ -0,0 +1 @@
|
|||
true
|
Loading…
Reference in New Issue