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' {car, cdr, cons, nil, nilp, pairp, vectorToList, list} = require 'cons-lists/lists'
{length} = require 'cons-lists/reduce'
{inspect} = require "util" {inspect} = require "util"
NEWLINES = ["\n", "\r", "\x0B", "\x0C"] NEWLINES = ["\n", "\r", "\x0B", "\x0C"]
WHITESPACE = [" ", "\t"].concat(NEWLINES) WHITESPACE = [" ", "\t"].concat(NEWLINES)
EOF = new (class Eof)() EOF = new (class Eof)()
EOO = new (class Eoo)() 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 class Source
constructor: (@inStream) -> constructor: (@inStream) ->
@index = 0 @index = 0
@ -32,13 +38,24 @@ class Source
skipWS = (inStream) -> skipWS = (inStream) ->
while inStream.peek() in WHITESPACE then inStream.next() 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)} # (type, value, line, column) -> (node {type, value, line, column)}
makeObj = (type, value, line, column) -> makeObj = (value, type, line, column) ->
list(type, value, line, column) lineInfo (invisibleProperty value, '__type', type), line, column
# msg -> (IO -> Node => Error) # msg -> (IO -> Node => Error)
handleError = (message) -> handleError = (message) ->
(line, column) -> makeObj('error', message, line, column) (line, column) -> lineInfo (cons "error", message), line, column
# IO -> Node => Comment # IO -> Node => Comment
readComment = (inStream) -> readComment = (inStream) ->
@ -47,26 +64,26 @@ readComment = (inStream) ->
inStream.next()).join("") inStream.next()).join("")
if not inStream.done() if not inStream.done()
inStream.next() inStream.next()
makeObj 'comment', r, line, column makeObj (new String r), 'comment', line, column
# IO -> (Node => Literal => String) | Error # IO -> (Node => Literal => String) | Error
readString = (inStream) -> readString = (inStream) ->
[line, column] = inStream.position() [line, column] = inStream.position()
inStream.next() inStream.next()
string = until inStream.peek() == '"' or inStream.done() string = until streq(inStream.peek(), '"') or inStream.done()
if inStream.peek() == '\\' if (streq inStream.peek(), '\\')
inStream.next() inStream.next()
inStream.next() inStream.next()
if inStream.done() if inStream.done()
return handleError("end of file seen before end of string.")(line, column) return handleError("end of file seen before end of string.")(line, column)
inStream.next() inStream.next()
makeObj 'string', (string.join ''), line, column makeObj (new String (string.join '')), 'string', line, column
# (String) -> (Node => Literal => Number) | Nothing # (String) -> (Node => Literal => Number) | Nothing
readMaybeNumber = (symbol) -> readMaybeNumber = (symbol) ->
if symbol[0] == '+' if streq(symbol[0], '+')
return readMaybeNumber symbol.substr(1) return readMaybeNumber symbol.substr(1)
if symbol[0] == '-' if streq(symbol[0], '-')
ret = readMaybeNumber symbol.substr(1) ret = readMaybeNumber symbol.substr(1)
return if ret? then -1 * ret else undefined return if ret? then -1 * ret else undefined
if symbol.search(/^0x[0-9a-fA-F]+$/) > -1 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) symbol = (until (inStream.done() or inStream.peek() in tableKeys or inStream.peek() in WHITESPACE)
inStream.next()).join '' inStream.next()).join ''
number = readMaybeNumber symbol number = readMaybeNumber symbol
if nilp number
return nil
if number? if number?
return makeObj 'number', number, line, column return makeObj (new Number number), 'number', line, column
makeObj 'symbol', symbol, line, column makeObj (new String symbol), 'symbol', line, column
# (Delim, TypeName) -> IO -> (IO, node) | Error # (Delim, TypeName) -> IO -> (IO, node) | Error
makeReadPair = (delim, type) -> makeReadContainer = (delim, constructor) ->
# IO -> (IO, Node) | Error # IO -> (IO, Node) | Error
(inStream) -> (inStream) ->
inStream.next() inStream.next()
skipWS inStream skipWS inStream
[line, column] = inStream.position() [line, column] = inStream.position()
if inStream.peek() == delim if streq(inStream.peek(), delim)
inStream.next() inStream.next()
return makeObj(type, nil, line, column) return constructor(nil, line, column)
# IO -> (IO, Node) | Error # IO -> (IO, Node) | Error
dotted = false dotted = false
readEachPair = (inStream) -> readInContainer = (inStream) ->
[line, column] = inStream.position() [line, column] = inStream.position()
obj = read inStream, true, null, true obj = read inStream, true, null, true
if inStream.peek() == delim if streq(inStream.peek(), delim)
if dotted then return obj 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 inStream.done() then return handleError("Unexpected end of input")(line, column)
if dotted then return handleError("More than one symbol after dot") if dotted then return handleError("More than one symbol after dot")
return obj if (car obj) == 'error' return obj if (errorp obj)
if (car obj) == 'symbol' and (car cdr obj) == '.' if streq(obj, ".")
dotted = true dotted = true
return readEachPair inStream return readInContainer inStream
cons obj, readEachPair inStream cons obj, readInContainer inStream
ret = makeObj type, readEachPair(inStream), line, column ret = readInContainer(inStream)
inStream.next() inStream.next()
ret ret
# Type -> (IO -> (IO, Node)) # Type -> (IO -> (IO, Node))
#
# Handles the quoted symbol things.
#
prefixReader = (type) -> prefixReader = (type) ->
# IO -> (IO, Node) # IO -> (IO, Node)
(inStream) -> (inStream) ->
@ -130,7 +152,7 @@ prefixReader = (type) ->
[line1, column1] = inStream.position() [line1, column1] = inStream.position()
obj = read inStream, true, null, true obj = read inStream, true, null, true
return obj if (car obj) == 'error' 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 # 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 # object or a vector) something handled by a read macro. Maybe in a
@ -138,12 +160,14 @@ prefixReader = (type) ->
readMacros = readMacros =
'"': readString '"': readString
'(': makeReadPair ')', 'list' '(': makeReadContainer ')', (o, l, c) -> lineInfo o, l, c
')': handleError "Closing paren encountered" ')': handleError "Closing paren encountered"
'[': makeReadPair ']', 'vector' '[': makeReadContainer ']', (o, l, c) -> cons("vector", lineinfo o, l, c)
']': handleError "Closing bracket encountered" ']': handleError "Closing bracket encountered"
'{': makeReadPair('}', 'record', (res) -> '{': makeReadContainer '}', (o, l, c) ->
res.length % 2 == 0 and true or mkerr "record key without value") 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." '}': handleError "Closing curly without corresponding opening."
"`": prefixReader 'back-quote' "`": prefixReader 'back-quote'
"'": prefixReader 'quote' "'": prefixReader 'quote'
@ -153,9 +177,9 @@ readMacros =
# Given a stream, reads from the stream until a single complete lisp # Given a stream, reads from the stream until a single complete lisp
# object has been found and returns the object # object has been found and returns the object
# IO -> Form # 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 inStream = if inStream instanceof Source then inStream else new Source inStream
inReadMacros = if InReadMacros? then inReadMacros else readMacros inReadMacros = if InReadMacros? then inReadMacros else readMacros
inReadMacroKeys = (i for i of inReadMacros) inReadMacroKeys = (i for i of inReadMacros)