mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-19 15:31:28 +00:00
sexp also accepts cons
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
import strutils, os, parseopt, parseUtils
|
||||
import options, commands, modules, sem, passes, passaux, msgs, nimconf,
|
||||
extccomp, condsyms, lists, net, rdstdin
|
||||
extccomp, condsyms, lists, net, rdstdin, sexp
|
||||
|
||||
const Usage = """
|
||||
Nimsuggest - Tool to give every editor IDE like capabilities for Nim
|
||||
@@ -23,6 +23,7 @@ Options:
|
||||
--address:HOST binds to that address, by default ""
|
||||
--stdin read commands from stdin and write results to
|
||||
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.
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ type
|
||||
sexpInt, ## an integer literal
|
||||
sexpFloat, ## a float literal
|
||||
sexpNil, ## the value ``nil``
|
||||
sexpDot, ## the dot to separate car/cdr
|
||||
sexpListStart, ## start of a list: the ``(`` token
|
||||
sexpListEnd, ## end of a list: the ``)`` token
|
||||
|
||||
@@ -30,6 +31,7 @@ type
|
||||
tkInt,
|
||||
tkFloat,
|
||||
tkNil,
|
||||
tkDot,
|
||||
tkParensLe,
|
||||
tkParensRi
|
||||
tkSpace
|
||||
@@ -41,16 +43,11 @@ type
|
||||
errQuoteExpected, ## ``"`` expected
|
||||
errEofExpected, ## EOF expected
|
||||
|
||||
ParserState = enum
|
||||
stateEof, stateStart, stateList, stateListExpectsSpace
|
||||
|
||||
SexpParser* = object of BaseLexer ## the parser object.
|
||||
a: string
|
||||
tok: TTokKind
|
||||
kind: SexpEventKind
|
||||
err: SexpError
|
||||
state: seq[ParserState]
|
||||
filename: string
|
||||
|
||||
const
|
||||
errorMessages: array [SexpError, string] = [
|
||||
@@ -68,18 +65,10 @@ const
|
||||
"int literal",
|
||||
"float literal",
|
||||
"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.} =
|
||||
## closes the parser `my` and its associated input stream.
|
||||
lexbase.close(my)
|
||||
@@ -112,21 +101,15 @@ proc getLine*(my: SexpParser): int {.inline.} =
|
||||
## get the current line the parser has arrived at.
|
||||
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 =
|
||||
## returns a helpful error message for the event ``sexpError``
|
||||
assert(my.kind == sexpError)
|
||||
result = "$1($2, $3) Error: $4" % [
|
||||
my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]]
|
||||
result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), errorMessages[my.err]]
|
||||
|
||||
proc errorMsgExpected*(my: SexpParser, e: string): string =
|
||||
## returns an error message "`e` expected" in the same format as the
|
||||
## other error messages
|
||||
result = "$1($2, $3) Error: $4" % [
|
||||
my.filename, $getLine(my), $getColumn(my), e & " expected"]
|
||||
result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"]
|
||||
|
||||
proc handleHexChar(c: char, x: var int): bool =
|
||||
result = true # Success
|
||||
@@ -237,7 +220,8 @@ proc parseSymbol(my: var SexpParser) =
|
||||
proc getTok(my: var SexpParser): TTokKind =
|
||||
setLen(my.a, 0)
|
||||
case my.buf[my.bufpos]
|
||||
of '-', '.', '0'..'9':
|
||||
of '-', '0'..'9': # numbers that start with a . are not parsed
|
||||
# correctly.
|
||||
parseNumber(my)
|
||||
if {'.', 'e', 'E'} in my.a:
|
||||
result = tkFloat
|
||||
@@ -262,65 +246,14 @@ proc getTok(my: var SexpParser): TTokKind =
|
||||
of ' ':
|
||||
result = tkSpace
|
||||
inc(my.bufpos)
|
||||
of '.':
|
||||
result = tkDot
|
||||
inc(my.bufpos)
|
||||
else:
|
||||
inc(my.bufpos)
|
||||
result = tkError
|
||||
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 ---------------------------------------
|
||||
|
||||
type
|
||||
@@ -329,8 +262,9 @@ type
|
||||
SInt,
|
||||
SFloat,
|
||||
SString,
|
||||
SSymbol
|
||||
SSymbol,
|
||||
SList,
|
||||
SCons
|
||||
|
||||
SexpNode* = ref SexpNodeObj ## SEXP node
|
||||
SexpNodeObj* {.acyclic.} = object
|
||||
@@ -345,9 +279,14 @@ type
|
||||
fnum*: float
|
||||
of SList:
|
||||
elems*: seq[SexpNode]
|
||||
of SCons:
|
||||
car: SexpNode
|
||||
cdr: SexpNode
|
||||
of SNil:
|
||||
discard
|
||||
|
||||
Cons = tuple[car: SexpNode, cdr: SexpNode]
|
||||
|
||||
SexpParsingError* = object of ValueError ## is raised for a SEXP error
|
||||
|
||||
proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} =
|
||||
@@ -381,6 +320,13 @@ proc newSNil*(): SexpNode =
|
||||
## Creates a new `SNil SexpNode`.
|
||||
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 =
|
||||
## Creates a new `SList SexpNode`
|
||||
new(result)
|
||||
@@ -433,6 +379,14 @@ proc getElems*(n: SexpNode, default: seq[SexpNode] = @[]): seq[SexpNode] =
|
||||
elif n.kind != SList: return default
|
||||
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 =
|
||||
## Generic constructor for SEXP data. Creates a new `SString SexpNode`.
|
||||
new(result)
|
||||
@@ -506,6 +460,8 @@ proc `==`* (a,b: SexpNode): bool =
|
||||
a.elems == b.elems
|
||||
of SSymbol:
|
||||
a.symbol == b.symbol
|
||||
of SCons:
|
||||
a.car == b.car and a.cdr == b.cdr
|
||||
|
||||
proc hash* (n:SexpNode): THash =
|
||||
## Compute the hash for a SEXP node
|
||||
@@ -522,6 +478,8 @@ proc hash* (n:SexpNode): THash =
|
||||
result = hash(0)
|
||||
of SSymbol:
|
||||
result = hash(n.symbol)
|
||||
of SCons:
|
||||
result = hash(n.car) !& hash(n.cdr)
|
||||
|
||||
proc len*(n: SexpNode): int =
|
||||
## 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(toHex(r, 4))
|
||||
result.add("\"")
|
||||
|
||||
proc copy*(p: SexpNode): SexpNode =
|
||||
## Performs a deep copy of `a`.
|
||||
case p.kind
|
||||
@@ -588,6 +547,8 @@ proc copy*(p: SexpNode): SexpNode =
|
||||
result = newSList()
|
||||
for i in items(p.elems):
|
||||
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,
|
||||
lstArr = false, currIndent = 0) =
|
||||
@@ -622,9 +583,18 @@ proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
|
||||
result.indent(currIndent)
|
||||
result.add(")")
|
||||
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 =
|
||||
## Converts `node` to its JSON Representation, with indentation and
|
||||
## Converts `node` to its Sexp Representation, with indentation and
|
||||
## on multiple lines.
|
||||
result = ""
|
||||
toPretty(result, node, indent)
|
||||
@@ -675,41 +645,49 @@ proc parseSexp(p: var SexpParser): SexpNode =
|
||||
of tkParensLe:
|
||||
result = newSList()
|
||||
discard getTok(p)
|
||||
while p.tok != tkParensRi:
|
||||
while p.tok notin {tkParensRi, tkDot}:
|
||||
result.add(parseSexp(p))
|
||||
if p.tok != tkSpace: break
|
||||
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)
|
||||
of tkSpace, tkError, tkParensRi, tkEof:
|
||||
of tkSpace, tkDot, tkError, tkParensRi, tkEof:
|
||||
raiseParseErr(p, "(")
|
||||
|
||||
proc parseSexp*(s: Stream, filename: string): SexpNode =
|
||||
## Parses from a stream `s` into a `SexpNode`. `filename` is only needed
|
||||
## for nice error messages.
|
||||
proc open*(my: var SexpParser, input: Stream) =
|
||||
## initializes the parser with an input stream.
|
||||
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
|
||||
p.open(s, filename)
|
||||
p.open(s)
|
||||
discard getTok(p) # read first token
|
||||
result = p.parseSexp()
|
||||
p.close()
|
||||
|
||||
proc parseSexp*(buffer: string): SexpNode =
|
||||
## Parses SEXP from `buffer`.
|
||||
result = parseSexp(newStringStream(buffer), "input")
|
||||
|
||||
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)
|
||||
## Parses Sexp from `buffer`.
|
||||
result = parseSexp(newStringStream(buffer))
|
||||
|
||||
when isMainModule:
|
||||
let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""")
|
||||
assert(testSexp[0].getNum == 1)
|
||||
assert(testSexp[1][0].getNum == 98)
|
||||
assert(testSexp[2].getElems == @[])
|
||||
assert(testSexp[4].getSymbol == "foobar")
|
||||
assert(testSexp[5].getStr == "foo")
|
||||
# let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""")
|
||||
# assert(testSexp[0].getNum == 1)
|
||||
# assert(testSexp[1][0].getNum == 98)
|
||||
# assert(testSexp[2].getElems == @[])
|
||||
# assert(testSexp[4].getSymbol == "foobar")
|
||||
# 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:
|
||||
var j = convertSexp([true, false, "foobar", [1, 2, "baz"]])
|
||||
|
||||
Reference in New Issue
Block a user