This commit is contained in:
Elf M. Sternberg 2015-07-23 16:17:20 -07:00
parent 39f6a09d51
commit e6b4a73559
1 changed files with 54 additions and 30 deletions

View File

@ -1,13 +1,19 @@
{car, cdr, cons, nil, nilp, pairp, vectorToList, list} = require 'cons-lists/lists'
{length} = require 'cons-lists/reduce'
{inspect} = require "util"
NEWLINES = ["\n", "\r", "\x0B", "\x0C"]
WHITESPACE = [" ", "\t"].concat(NEWLINES)
EOF = new (class Eof)()
EOO = new (class Eoo)()
stringp = (o) -> o?.__type == 'string'
errorp = (o) -> (pairp o) and (car o) == 'error'
streq = (s, t) -> String(s) == String(t)
class Source
constructor: (@inStream) ->
@index = 0
@ -32,13 +38,24 @@ class Source
skipWS = (inStream) ->
while inStream.peek() in WHITESPACE then inStream.next()
invisibleProperty = (obj, name, value) ->
Object.defineProperty obj, name,
value: value
configurable: false
enumerable: false
writable: false
obj
lineInfo = (obj, line, column) ->
invisibleProperty obj, '__position', {line: line, column: column}
# (type, value, line, column) -> (node {type, value, line, column)}
makeObj = (type, value, line, column) ->
list(type, value, line, column)
makeObj = (value, type, line, column) ->
lineInfo (invisibleProperty value, '__type', type), line, column
# msg -> (IO -> Node => Error)
handleError = (message) ->
(line, column) -> makeObj('error', message, line, column)
(line, column) -> lineInfo (cons "error", message), line, column
# IO -> Node => Comment
readComment = (inStream) ->
@ -47,26 +64,26 @@ readComment = (inStream) ->
inStream.next()).join("")
if not inStream.done()
inStream.next()
makeObj 'comment', r, line, column
makeObj (new String r), 'comment', 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() == '\\'
string = until streq(inStream.peek(), '"') or inStream.done()
if (streq 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
makeObj (new String (string.join '')), 'string', line, column
# (String) -> (Node => Literal => Number) | Nothing
readMaybeNumber = (symbol) ->
if symbol[0] == '+'
if streq(symbol[0], '+')
return readMaybeNumber symbol.substr(1)
if symbol[0] == '-'
if streq(symbol[0], '-')
ret = readMaybeNumber symbol.substr(1)
return if ret? then -1 * ret else undefined
if symbol.search(/^0x[0-9a-fA-F]+$/) > -1
@ -85,43 +102,48 @@ readSymbol = (inStream, tableKeys) ->
symbol = (until (inStream.done() or inStream.peek() in tableKeys or inStream.peek() in WHITESPACE)
inStream.next()).join ''
number = readMaybeNumber symbol
if nilp number
return nil
if number?
return makeObj 'number', number, line, column
makeObj 'symbol', symbol, line, column
return makeObj (new Number number), 'number', line, column
makeObj (new String symbol), 'symbol', line, column
# (Delim, TypeName) -> IO -> (IO, node) | Error
makeReadPair = (delim, type) ->
makeReadContainer = (delim, constructor) ->
# IO -> (IO, Node) | Error
(inStream) ->
inStream.next()
skipWS inStream
[line, column] = inStream.position()
if inStream.peek() == delim
if streq(inStream.peek(), delim)
inStream.next()
return makeObj(type, nil, line, column)
return constructor(nil, line, column)
# IO -> (IO, Node) | Error
dotted = false
readEachPair = (inStream) ->
readInContainer = (inStream) ->
[line, column] = inStream.position()
obj = read inStream, true, null, true
if inStream.peek() == delim
if streq(inStream.peek(), delim)
if dotted then return obj
return cons obj, nil
return lineInfo (cons obj, nil), line, column
if inStream.done() then return handleError("Unexpected end of input")(line, column)
if dotted then return handleError("More than one symbol after dot")
return obj if (car obj) == 'error'
if (car obj) == 'symbol' and (car cdr obj) == '.'
return obj if (errorp obj)
if streq(obj, ".")
dotted = true
return readEachPair inStream
cons obj, readEachPair inStream
return readInContainer inStream
cons obj, readInContainer inStream
ret = makeObj type, readEachPair(inStream), line, column
ret = readInContainer(inStream)
inStream.next()
ret
# Type -> (IO -> (IO, Node))
#
# Handles the quoted symbol things.
#
prefixReader = (type) ->
# IO -> (IO, Node)
(inStream) ->
@ -130,7 +152,7 @@ prefixReader = (type) ->
[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
cons (makeObj (new String type), 'symbol', line1, column1), obj
# 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
@ -138,12 +160,14 @@ prefixReader = (type) ->
readMacros =
'"': readString
'(': makeReadPair ')', 'list'
'(': makeReadContainer ')', (o, l, c) -> lineInfo o, l, c
')': handleError "Closing paren encountered"
'[': makeReadPair ']', 'vector'
'[': makeReadContainer ']', (o, l, c) -> cons("vector", lineinfo o, l, c)
']': handleError "Closing bracket encountered"
'{': makeReadPair('}', 'record', (res) ->
res.length % 2 == 0 and true or mkerr "record key without value")
'{': makeReadContainer '}', (o, l, c) ->
if length(o) % 2 != 0
return handleError "Records require an even number of items."
cons('record', lineinfo o, l, c)
'}': handleError "Closing curly without corresponding opening."
"`": prefixReader 'back-quote'
"'": prefixReader 'quote'
@ -153,9 +177,9 @@ readMacros =
# 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) ->
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)