sexp also accepts cons

This commit is contained in:
Simon Hafner 2015-04-14 02:47:51 -05:00
commit 49b953f318
2 changed files with 81 additions and 102 deletions

View file

@ -11,7 +11,7 @@
import strutils, os, parseopt, parseUtils import strutils, os, parseopt, parseUtils
import options, commands, modules, sem, passes, passaux, msgs, nimconf, import options, commands, modules, sem, passes, passaux, msgs, nimconf,
extccomp, condsyms, lists, net, rdstdin extccomp, condsyms, lists, net, rdstdin, sexp
const Usage = """ const Usage = """
Nimsuggest - Tool to give every editor IDE like capabilities for Nim Nimsuggest - Tool to give every editor IDE like capabilities for Nim
@ -23,6 +23,7 @@ Options:
--address:HOST binds to that address, by default "" --address:HOST binds to that address, by default ""
--stdin read commands from stdin and write results to --stdin read commands from stdin and write results to
stdout instead of using sockets stdout instead of using sockets
--sexp talk in sexp, mainly for emacs epc.
The server then listens to the connection and takes line-based commands. The server then listens to the connection and takes line-based commands.

View file

@ -19,6 +19,7 @@ type
sexpInt, ## an integer literal sexpInt, ## an integer literal
sexpFloat, ## a float literal sexpFloat, ## a float literal
sexpNil, ## the value ``nil`` sexpNil, ## the value ``nil``
sexpDot, ## the dot to separate car/cdr
sexpListStart, ## start of a list: the ``(`` token sexpListStart, ## start of a list: the ``(`` token
sexpListEnd, ## end of a list: the ``)`` token sexpListEnd, ## end of a list: the ``)`` token
@ -30,6 +31,7 @@ type
tkInt, tkInt,
tkFloat, tkFloat,
tkNil, tkNil,
tkDot,
tkParensLe, tkParensLe,
tkParensRi tkParensRi
tkSpace tkSpace
@ -41,16 +43,11 @@ type
errQuoteExpected, ## ``"`` expected errQuoteExpected, ## ``"`` expected
errEofExpected, ## EOF expected errEofExpected, ## EOF expected
ParserState = enum
stateEof, stateStart, stateList, stateListExpectsSpace
SexpParser* = object of BaseLexer ## the parser object. SexpParser* = object of BaseLexer ## the parser object.
a: string a: string
tok: TTokKind tok: TTokKind
kind: SexpEventKind kind: SexpEventKind
err: SexpError err: SexpError
state: seq[ParserState]
filename: string
const const
errorMessages: array [SexpError, string] = [ errorMessages: array [SexpError, string] = [
@ -68,18 +65,10 @@ const
"int literal", "int literal",
"float literal", "float literal",
"nil", "nil",
"(", ")", " " ".",
"(", ")", "space"
] ]
proc open*(my: var SexpParser, input: Stream, filename: string) =
## initializes the parser with an input stream. `Filename` is only used
## for nice error messages.
lexbase.open(my, input)
my.filename = filename
my.state = @[stateStart]
my.kind = sexpError
my.a = ""
proc close*(my: var SexpParser) {.inline.} = proc close*(my: var SexpParser) {.inline.} =
## closes the parser `my` and its associated input stream. ## closes the parser `my` and its associated input stream.
lexbase.close(my) lexbase.close(my)
@ -112,21 +101,15 @@ proc getLine*(my: SexpParser): int {.inline.} =
## get the current line the parser has arrived at. ## get the current line the parser has arrived at.
result = my.lineNumber result = my.lineNumber
proc getFilename*(my: SexpParser): string {.inline.} =
## get the filename of the file that the parser processes.
result = my.filename
proc errorMsg*(my: SexpParser): string = proc errorMsg*(my: SexpParser): string =
## returns a helpful error message for the event ``sexpError`` ## returns a helpful error message for the event ``sexpError``
assert(my.kind == sexpError) assert(my.kind == sexpError)
result = "$1($2, $3) Error: $4" % [ result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), errorMessages[my.err]]
my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]]
proc errorMsgExpected*(my: SexpParser, e: string): string = proc errorMsgExpected*(my: SexpParser, e: string): string =
## returns an error message "`e` expected" in the same format as the ## returns an error message "`e` expected" in the same format as the
## other error messages ## other error messages
result = "$1($2, $3) Error: $4" % [ result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"]
my.filename, $getLine(my), $getColumn(my), e & " expected"]
proc handleHexChar(c: char, x: var int): bool = proc handleHexChar(c: char, x: var int): bool =
result = true # Success result = true # Success
@ -237,7 +220,8 @@ proc parseSymbol(my: var SexpParser) =
proc getTok(my: var SexpParser): TTokKind = proc getTok(my: var SexpParser): TTokKind =
setLen(my.a, 0) setLen(my.a, 0)
case my.buf[my.bufpos] case my.buf[my.bufpos]
of '-', '.', '0'..'9': of '-', '0'..'9': # numbers that start with a . are not parsed
# correctly.
parseNumber(my) parseNumber(my)
if {'.', 'e', 'E'} in my.a: if {'.', 'e', 'E'} in my.a:
result = tkFloat result = tkFloat
@ -262,65 +246,14 @@ proc getTok(my: var SexpParser): TTokKind =
of ' ': of ' ':
result = tkSpace result = tkSpace
inc(my.bufpos) inc(my.bufpos)
of '.':
result = tkDot
inc(my.bufpos)
else: else:
inc(my.bufpos) inc(my.bufpos)
result = tkError result = tkError
my.tok = result my.tok = result
proc next*(my: var SexpParser) =
## retrieves the first/next event. This controls the parser.
var tk = getTok(my)
var i = my.state.len-1
# the following code is a state machine. If we had proper coroutines,
# the code could be much simpler.
case my.state[i]
of stateEof:
if tk == tkEof:
my.kind = sexpEof
else:
my.kind = sexpError
my.err = errEofExpected
of stateStart:
# tokens allowed?
case tk
of tkString, tkInt, tkFloat, tkSymbol, tkNil:
my.state[i] = stateEof # expect EOF next!
my.kind = SexpEventKind(ord(tk))
of tkParensLe:
my.state.add(stateList)
my.kind = sexpListStart
of tkEof:
my.kind = sexpEof
else:
my.kind = sexpError
my.err = errEofExpected
of stateList:
case tk
of tkString, tkInt, tkFloat, tkSymbol, tkNil:
my.kind = SexpEventKind(ord(tk))
my.state.add(stateListExpectsSpace)
of tkParensLe:
my.state.add(stateList)
my.kind = sexpListStart
of tkParensRi:
my.kind = sexpListEnd
discard my.state.pop()
else:
my.kind = sexpError
my.err = errParensRiExpected
of stateListExpectsSpace:
case tk
of tkSpace:
discard my.state.pop()
next(my)
of tkParensLe:
my.kind = sexpListEnd
discard my.state.pop()
discard my.state.pop()
else:
my.kind = sexpError
my.err = errParensRiExpected
# ------------- higher level interface --------------------------------------- # ------------- higher level interface ---------------------------------------
type type
@ -329,8 +262,9 @@ type
SInt, SInt,
SFloat, SFloat,
SString, SString,
SSymbol SSymbol,
SList, SList,
SCons
SexpNode* = ref SexpNodeObj ## SEXP node SexpNode* = ref SexpNodeObj ## SEXP node
SexpNodeObj* {.acyclic.} = object SexpNodeObj* {.acyclic.} = object
@ -345,9 +279,14 @@ type
fnum*: float fnum*: float
of SList: of SList:
elems*: seq[SexpNode] elems*: seq[SexpNode]
of SCons:
car: SexpNode
cdr: SexpNode
of SNil: of SNil:
discard discard
Cons = tuple[car: SexpNode, cdr: SexpNode]
SexpParsingError* = object of ValueError ## is raised for a SEXP error SexpParsingError* = object of ValueError ## is raised for a SEXP error
proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} = proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} =
@ -381,6 +320,13 @@ proc newSNil*(): SexpNode =
## Creates a new `SNil SexpNode`. ## Creates a new `SNil SexpNode`.
new(result) new(result)
proc newSCons*(car, cdr: SexpNode): SexpNode =
## Creates a new `SCons SexpNode`
new(result)
result.kind = SCons
result.car = car
result.cdr = cdr
proc newSList*(): SexpNode = proc newSList*(): SexpNode =
## Creates a new `SList SexpNode` ## Creates a new `SList SexpNode`
new(result) new(result)
@ -433,6 +379,14 @@ proc getElems*(n: SexpNode, default: seq[SexpNode] = @[]): seq[SexpNode] =
elif n.kind != SList: return default elif n.kind != SList: return default
else: return n.elems else: return n.elems
proc getCons*(n: SexpNode, defaults: Cons = (newSNil(), newSNil())): Cons =
## Retrieves the cons value of a `SList SexpNode`.
##
## Returns ``default`` if ``n`` is not a ``SList``.
if n.kind == SCons: return (n.car, n.cdr)
elif n.kind == SList: return (n.elems[0], n.elems[1])
else: return defaults
proc `sexp`*(s: string): SexpNode = proc `sexp`*(s: string): SexpNode =
## Generic constructor for SEXP data. Creates a new `SString SexpNode`. ## Generic constructor for SEXP data. Creates a new `SString SexpNode`.
new(result) new(result)
@ -506,6 +460,8 @@ proc `==`* (a,b: SexpNode): bool =
a.elems == b.elems a.elems == b.elems
of SSymbol: of SSymbol:
a.symbol == b.symbol a.symbol == b.symbol
of SCons:
a.car == b.car and a.cdr == b.cdr
proc hash* (n:SexpNode): THash = proc hash* (n:SexpNode): THash =
## Compute the hash for a SEXP node ## Compute the hash for a SEXP node
@ -522,6 +478,8 @@ proc hash* (n:SexpNode): THash =
result = hash(0) result = hash(0)
of SSymbol: of SSymbol:
result = hash(n.symbol) result = hash(n.symbol)
of SCons:
result = hash(n.car) !& hash(n.cdr)
proc len*(n: SexpNode): int = proc len*(n: SexpNode): int =
## If `n` is a `SList`, it returns the number of elements. ## If `n` is a `SList`, it returns the number of elements.
@ -571,6 +529,7 @@ proc escapeJson*(s: string): string =
result.add("\\u") result.add("\\u")
result.add(toHex(r, 4)) result.add(toHex(r, 4))
result.add("\"") result.add("\"")
proc copy*(p: SexpNode): SexpNode = proc copy*(p: SexpNode): SexpNode =
## Performs a deep copy of `a`. ## Performs a deep copy of `a`.
case p.kind case p.kind
@ -588,6 +547,8 @@ proc copy*(p: SexpNode): SexpNode =
result = newSList() result = newSList()
for i in items(p.elems): for i in items(p.elems):
result.elems.add(copy(i)) result.elems.add(copy(i))
of SCons:
result = newSCons(copy(p.car), copy(p.cdr))
proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true, proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
lstArr = false, currIndent = 0) = lstArr = false, currIndent = 0) =
@ -622,9 +583,18 @@ proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
result.indent(currIndent) result.indent(currIndent)
result.add(")") result.add(")")
else: result.add("nil") else: result.add("nil")
of SCons:
if lstArr: result.indent(currIndent)
result.add("(")
toPretty(result, node.car, indent, ml,
true, newIndent(currIndent, indent, ml))
result.add(" . ")
toPretty(result, node.cdr, indent, ml,
true, newIndent(currIndent, indent, ml))
result.add(")")
proc pretty*(node: SexpNode, indent = 2): string = proc pretty*(node: SexpNode, indent = 2): string =
## Converts `node` to its JSON Representation, with indentation and ## Converts `node` to its Sexp Representation, with indentation and
## on multiple lines. ## on multiple lines.
result = "" result = ""
toPretty(result, node, indent) toPretty(result, node, indent)
@ -675,41 +645,49 @@ proc parseSexp(p: var SexpParser): SexpNode =
of tkParensLe: of tkParensLe:
result = newSList() result = newSList()
discard getTok(p) discard getTok(p)
while p.tok != tkParensRi: while p.tok notin {tkParensRi, tkDot}:
result.add(parseSexp(p)) result.add(parseSexp(p))
if p.tok != tkSpace: break if p.tok != tkSpace: break
discard getTok(p) discard getTok(p)
if p.tok == tkDot:
eat(p, tkDot)
eat(p, tkSpace)
result.add(parseSexp(p))
result = newSCons(result[0], result[1])
eat(p, tkParensRi) eat(p, tkParensRi)
of tkSpace, tkError, tkParensRi, tkEof: of tkSpace, tkDot, tkError, tkParensRi, tkEof:
raiseParseErr(p, "(") raiseParseErr(p, "(")
proc parseSexp*(s: Stream, filename: string): SexpNode = proc open*(my: var SexpParser, input: Stream) =
## Parses from a stream `s` into a `SexpNode`. `filename` is only needed ## initializes the parser with an input stream.
## for nice error messages. lexbase.open(my, input)
my.kind = sexpError
my.a = ""
proc parseSexp*(s: Stream): SexpNode =
## Parses from a buffer `s` into a `SexpNode`.
var p: SexpParser var p: SexpParser
p.open(s, filename) p.open(s)
discard getTok(p) # read first token discard getTok(p) # read first token
result = p.parseSexp() result = p.parseSexp()
p.close() p.close()
proc parseSexp*(buffer: string): SexpNode = proc parseSexp*(buffer: string): SexpNode =
## Parses SEXP from `buffer`. ## Parses Sexp from `buffer`.
result = parseSexp(newStringStream(buffer), "input") result = parseSexp(newStringStream(buffer))
proc parseFile*(filename: string): SexpNode =
## Parses `file` into a `SexpNode`.
var stream = newFileStream(filename, fmRead)
if stream == nil:
raise newException(IOError, "cannot read from file: " & filename)
result = parseSexp(stream, filename)
when isMainModule: when isMainModule:
let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""") # let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""")
assert(testSexp[0].getNum == 1) # assert(testSexp[0].getNum == 1)
assert(testSexp[1][0].getNum == 98) # assert(testSexp[1][0].getNum == 98)
assert(testSexp[2].getElems == @[]) # assert(testSexp[2].getElems == @[])
assert(testSexp[4].getSymbol == "foobar") # assert(testSexp[4].getSymbol == "foobar")
assert(testSexp[5].getStr == "foo") # assert(testSexp[5].getStr == "foo")
let alist = parseSexp("""((1 . 2) (2 . "foo"))""")
assert(alist[0].getCons.car.getNum == 1)
assert(alist[0].getCons.cdr.getNum == 2)
assert(alist[1].getCons.cdr.getStr == "foo")
# Generator: # Generator:
var j = convertSexp([true, false, "foobar", [1, 2, "baz"]]) var j = convertSexp([true, false, "foobar", [1, 2, "baz"]])