RosettaCodeData/Task/S-Expressions/CoffeeScript/s-expressions-1.coffee

131 lines
2.7 KiB
CoffeeScript

# This code works with Lisp-like s-expressions.
#
# We lex tokens then do recursive descent on the tokens
# to build our data structures.
sexp = (data) ->
# Convert a JS data structure to a string s-expression. A sexier version
# would remove quotes around strings that don't need them.
s = ''
if Array.isArray data
children = (sexp elem for elem in data).join ' '
'(' + children + ')'
else
return JSON.stringify data
parse_sexp = (sexp) ->
tokens = lex_sexp sexp
i = 0
_parse_list = ->
i += 1
arr = []
while i < tokens.length and tokens[i].type != ')'
arr.push _parse()
if i < tokens.length
i += 1
else
throw Error "missing end paren"
arr
_guess_type = (word) ->
# This is crude, doesn't handle all forms of floats.
if word.match /^\d+\.\d+$/
parseFloat(word)
else if word.match /^\d+/
parseInt(word)
else
word
_parse_word = ->
token = tokens[i]
i += 1
if token.type == 'string'
token.word
else
_guess_type token.word
_parse = ->
return undefined unless i < tokens.length
token = tokens[i]
if token.type == '('
_parse_list()
else
_parse_word()
exp = _parse()
throw Error "premature termination" if i < tokens.length
exp
lex_sexp = (sexp) ->
is_whitespace = (c) -> c in [' ', '\t', '\n']
i = 0
tokens = []
test = (f) ->
return false unless i < sexp.length
f(sexp[i])
eat_char = (c) ->
tokens.push
type: c
i += 1
eat_whitespace = ->
i += 1
while test is_whitespace
i += 1
eat_word = ->
token = c
i += 1
word_char = (c) ->
c != ')' and !is_whitespace c
while test word_char
token += sexp[i]
i += 1
tokens.push
type: "word"
word: token
eat_quoted_word = ->
start = i
i += 1
token = ''
while test ((c) -> c != '"')
if sexp[i] == '\\'
i += 1
throw Error("escaping error") unless i < sexp.length
token += sexp[i]
i += 1
if test ((c) -> c == '"')
tokens.push
type: "string"
word: token
i += 1
else
throw Error("end quote missing #{sexp.substring(start, i)}")
while i < sexp.length
c = sexp[i]
if c == '(' or c == ')'
eat_char c
else if is_whitespace c
eat_whitespace()
else if c == '"'
eat_quoted_word()
else
eat_word()
tokens
do ->
input = """
((data "quoted data with escaped \\"" 123 4.5 "14")
(data (!@# (4.5) "(more" "data)")))
"""
console.log "input:\n#{input}\n"
output = parse_sexp(input)
pp = (data) -> JSON.stringify data, null, ' '
console.log "output:\n#{pp output}\n"
console.log "round trip:\n#{sexp output}\n"