sexp also accepts cons
This commit is contained in:
parent
8fa8f13741
commit
49b953f318
2 changed files with 81 additions and 102 deletions
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"]])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue