sexp.nim 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf, Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## **Note:** Import ``nimsuggest/sexp`` to use this module
  10. import
  11. hashes, strutils, lexbase, streams, unicode, macros
  12. import std/private/decode_helpers
  13. when defined(nimPreviewSlimSystem):
  14. import std/[assertions, formatfloat]
  15. type
  16. SexpEventKind* = enum ## enumeration of all events that may occur when parsing
  17. sexpError, ## an error occurred during parsing
  18. sexpEof, ## end of file reached
  19. sexpString, ## a string literal
  20. sexpSymbol, ## a symbol
  21. sexpInt, ## an integer literal
  22. sexpFloat, ## a float literal
  23. sexpNil, ## the value ``nil``
  24. sexpDot, ## the dot to separate car/cdr
  25. sexpListStart, ## start of a list: the ``(`` token
  26. sexpListEnd, ## end of a list: the ``)`` token
  27. TTokKind = enum # must be synchronized with SexpEventKind!
  28. tkError,
  29. tkEof,
  30. tkString,
  31. tkSymbol,
  32. tkInt,
  33. tkFloat,
  34. tkNil,
  35. tkDot,
  36. tkParensLe,
  37. tkParensRi
  38. tkSpace
  39. SexpError* = enum ## enumeration that lists all errors that can occur
  40. errNone, ## no error
  41. errInvalidToken, ## invalid token
  42. errParensRiExpected, ## ``)`` expected
  43. errQuoteExpected, ## ``"`` expected
  44. errEofExpected, ## EOF expected
  45. SexpParser* = object of BaseLexer ## the parser object.
  46. a: string
  47. tok: TTokKind
  48. kind: SexpEventKind
  49. err: SexpError
  50. const
  51. errorMessages: array[SexpError, string] = [
  52. "no error",
  53. "invalid token",
  54. "')' expected",
  55. "'\"' or \"'\" expected",
  56. "EOF expected",
  57. ]
  58. tokToStr: array[TTokKind, string] = [
  59. "invalid token",
  60. "EOF",
  61. "string literal",
  62. "symbol",
  63. "int literal",
  64. "float literal",
  65. "nil",
  66. ".",
  67. "(", ")", "space"
  68. ]
  69. proc close*(my: var SexpParser) {.inline.} =
  70. ## closes the parser `my` and its associated input stream.
  71. lexbase.close(my)
  72. proc str*(my: SexpParser): string {.inline.} =
  73. ## returns the character data for the events: ``sexpInt``, ``sexpFloat``,
  74. ## ``sexpString``
  75. assert(my.kind in {sexpInt, sexpFloat, sexpString})
  76. result = my.a
  77. proc getInt*(my: SexpParser): BiggestInt {.inline.} =
  78. ## returns the number for the event: ``sexpInt``
  79. assert(my.kind == sexpInt)
  80. result = parseBiggestInt(my.a)
  81. proc getFloat*(my: SexpParser): float {.inline.} =
  82. ## returns the number for the event: ``sexpFloat``
  83. assert(my.kind == sexpFloat)
  84. result = parseFloat(my.a)
  85. proc kind*(my: SexpParser): SexpEventKind {.inline.} =
  86. ## returns the current event type for the SEXP parser
  87. result = my.kind
  88. proc getColumn*(my: SexpParser): int {.inline.} =
  89. ## get the current column the parser has arrived at.
  90. result = getColNumber(my, my.bufpos)
  91. proc getLine*(my: SexpParser): int {.inline.} =
  92. ## get the current line the parser has arrived at.
  93. result = my.lineNumber
  94. proc errorMsg*(my: SexpParser): string =
  95. ## returns a helpful error message for the event ``sexpError``
  96. assert(my.kind == sexpError)
  97. result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), errorMessages[my.err]]
  98. proc errorMsgExpected*(my: SexpParser, e: string): string =
  99. ## returns an error message "`e` expected" in the same format as the
  100. ## other error messages
  101. result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"]
  102. proc parseString(my: var SexpParser): TTokKind =
  103. result = tkString
  104. var pos = my.bufpos + 1
  105. while true:
  106. case my.buf[pos]
  107. of '\0':
  108. my.err = errQuoteExpected
  109. result = tkError
  110. break
  111. of '"':
  112. inc(pos)
  113. break
  114. of '\\':
  115. case my.buf[pos+1]
  116. of '\\', '"', '\'', '/':
  117. add(my.a, my.buf[pos+1])
  118. inc(pos, 2)
  119. of 'b':
  120. add(my.a, '\b')
  121. inc(pos, 2)
  122. of 'f':
  123. add(my.a, '\f')
  124. inc(pos, 2)
  125. of 'n':
  126. add(my.a, '\L')
  127. inc(pos, 2)
  128. of 'r':
  129. add(my.a, '\C')
  130. inc(pos, 2)
  131. of 't':
  132. add(my.a, '\t')
  133. inc(pos, 2)
  134. of 'u':
  135. inc(pos, 2)
  136. var r: int
  137. if handleHexChar(my.buf[pos], r): inc(pos)
  138. if handleHexChar(my.buf[pos], r): inc(pos)
  139. if handleHexChar(my.buf[pos], r): inc(pos)
  140. if handleHexChar(my.buf[pos], r): inc(pos)
  141. add(my.a, toUTF8(Rune(r)))
  142. else:
  143. # don't bother with the error
  144. add(my.a, my.buf[pos])
  145. inc(pos)
  146. of '\c':
  147. pos = lexbase.handleCR(my, pos)
  148. add(my.a, '\c')
  149. of '\L':
  150. pos = lexbase.handleLF(my, pos)
  151. add(my.a, '\L')
  152. else:
  153. add(my.a, my.buf[pos])
  154. inc(pos)
  155. my.bufpos = pos # store back
  156. proc parseNumber(my: var SexpParser) =
  157. var pos = my.bufpos
  158. if my.buf[pos] == '-':
  159. add(my.a, '-')
  160. inc(pos)
  161. if my.buf[pos] == '.':
  162. add(my.a, "0.")
  163. inc(pos)
  164. else:
  165. while my.buf[pos] in Digits:
  166. add(my.a, my.buf[pos])
  167. inc(pos)
  168. if my.buf[pos] == '.':
  169. add(my.a, '.')
  170. inc(pos)
  171. # digits after the dot:
  172. while my.buf[pos] in Digits:
  173. add(my.a, my.buf[pos])
  174. inc(pos)
  175. if my.buf[pos] in {'E', 'e'}:
  176. add(my.a, my.buf[pos])
  177. inc(pos)
  178. if my.buf[pos] in {'+', '-'}:
  179. add(my.a, my.buf[pos])
  180. inc(pos)
  181. while my.buf[pos] in Digits:
  182. add(my.a, my.buf[pos])
  183. inc(pos)
  184. my.bufpos = pos
  185. proc parseSymbol(my: var SexpParser) =
  186. var pos = my.bufpos
  187. if my.buf[pos] in IdentStartChars:
  188. while my.buf[pos] in IdentChars:
  189. add(my.a, my.buf[pos])
  190. inc(pos)
  191. my.bufpos = pos
  192. proc getTok(my: var SexpParser): TTokKind =
  193. setLen(my.a, 0)
  194. case my.buf[my.bufpos]
  195. of '-', '0'..'9': # numbers that start with a . are not parsed
  196. # correctly.
  197. parseNumber(my)
  198. if {'.', 'e', 'E'} in my.a:
  199. result = tkFloat
  200. else:
  201. result = tkInt
  202. of '"': #" # gotta fix nim-mode
  203. result = parseString(my)
  204. of '(':
  205. inc(my.bufpos)
  206. result = tkParensLe
  207. of ')':
  208. inc(my.bufpos)
  209. result = tkParensRi
  210. of '\0':
  211. result = tkEof
  212. of 'a'..'z', 'A'..'Z', '_':
  213. parseSymbol(my)
  214. if my.a == "nil":
  215. result = tkNil
  216. else:
  217. result = tkSymbol
  218. of ' ':
  219. result = tkSpace
  220. inc(my.bufpos)
  221. of '.':
  222. result = tkDot
  223. inc(my.bufpos)
  224. else:
  225. inc(my.bufpos)
  226. result = tkError
  227. my.tok = result
  228. # ------------- higher level interface ---------------------------------------
  229. type
  230. SexpNodeKind* = enum ## possible SEXP node types
  231. SNil,
  232. SInt,
  233. SFloat,
  234. SString,
  235. SSymbol,
  236. SList,
  237. SCons
  238. SexpNode* = ref SexpNodeObj ## SEXP node
  239. SexpNodeObj* {.acyclic.} = object
  240. case kind*: SexpNodeKind
  241. of SString:
  242. str*: string
  243. of SSymbol:
  244. symbol*: string
  245. of SInt:
  246. num*: BiggestInt
  247. of SFloat:
  248. fnum*: float
  249. of SList:
  250. elems*: seq[SexpNode]
  251. of SCons:
  252. car: SexpNode
  253. cdr: SexpNode
  254. of SNil:
  255. discard
  256. Cons = tuple[car: SexpNode, cdr: SexpNode]
  257. SexpParsingError* = object of ValueError ## is raised for a SEXP error
  258. proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} =
  259. ## raises an `ESexpParsingError` exception.
  260. raise newException(SexpParsingError, errorMsgExpected(p, msg))
  261. proc newSString*(s: string): SexpNode =
  262. ## Creates a new `SString SexpNode`.
  263. result = SexpNode(kind: SString, str: s)
  264. proc newSInt*(n: BiggestInt): SexpNode =
  265. ## Creates a new `SInt SexpNode`.
  266. result = SexpNode(kind: SInt, num: n)
  267. proc newSFloat*(n: float): SexpNode =
  268. ## Creates a new `SFloat SexpNode`.
  269. result = SexpNode(kind: SFloat, fnum: n)
  270. proc newSNil*(): SexpNode =
  271. ## Creates a new `SNil SexpNode`.
  272. result = SexpNode(kind: SNil)
  273. proc newSCons*(car, cdr: SexpNode): SexpNode =
  274. ## Creates a new `SCons SexpNode`
  275. result = SexpNode(kind: SCons, car: car, cdr: cdr)
  276. proc newSList*(): SexpNode =
  277. ## Creates a new `SList SexpNode`
  278. result = SexpNode(kind: SList, elems: @[])
  279. proc newSSymbol*(s: string): SexpNode =
  280. result = SexpNode(kind: SSymbol, symbol: s)
  281. proc getStr*(n: SexpNode, default: string = ""): string =
  282. ## Retrieves the string value of a `SString SexpNode`.
  283. ##
  284. ## Returns ``default`` if ``n`` is not a ``SString``.
  285. if n.kind != SString: return default
  286. else: return n.str
  287. proc getNum*(n: SexpNode, default: BiggestInt = 0): BiggestInt =
  288. ## Retrieves the int value of a `SInt SexpNode`.
  289. ##
  290. ## Returns ``default`` if ``n`` is not a ``SInt``.
  291. if n.kind != SInt: return default
  292. else: return n.num
  293. proc getFNum*(n: SexpNode, default: float = 0.0): float =
  294. ## Retrieves the float value of a `SFloat SexpNode`.
  295. ##
  296. ## Returns ``default`` if ``n`` is not a ``SFloat``.
  297. if n.kind != SFloat: return default
  298. else: return n.fnum
  299. proc getSymbol*(n: SexpNode, default: string = ""): string =
  300. ## Retrieves the int value of a `SList SexpNode`.
  301. ##
  302. ## Returns ``default`` if ``n`` is not a ``SList``.
  303. if n.kind != SSymbol: return default
  304. else: return n.symbol
  305. proc getElems*(n: SexpNode, default: seq[SexpNode] = @[]): seq[SexpNode] =
  306. ## Retrieves the int value of a `SList SexpNode`.
  307. ##
  308. ## Returns ``default`` if ``n`` is not a ``SList``.
  309. if n.kind == SNil: return @[]
  310. elif n.kind != SList: return default
  311. else: return n.elems
  312. proc getCons*(n: SexpNode, defaults: Cons = (newSNil(), newSNil())): Cons =
  313. ## Retrieves the cons value of a `SList SexpNode`.
  314. ##
  315. ## Returns ``default`` if ``n`` is not a ``SList``.
  316. if n.kind == SCons: return (n.car, n.cdr)
  317. elif n.kind == SList: return (n.elems[0], n.elems[1])
  318. else: return defaults
  319. proc sexp*(s: string): SexpNode =
  320. ## Generic constructor for SEXP data. Creates a new `SString SexpNode`.
  321. result = SexpNode(kind: SString, str: s)
  322. proc sexp*(n: BiggestInt): SexpNode =
  323. ## Generic constructor for SEXP data. Creates a new `SInt SexpNode`.
  324. result = SexpNode(kind: SInt, num: n)
  325. proc sexp*(n: float): SexpNode =
  326. ## Generic constructor for SEXP data. Creates a new `SFloat SexpNode`.
  327. result = SexpNode(kind: SFloat, fnum: n)
  328. proc sexp*(b: bool): SexpNode =
  329. ## Generic constructor for SEXP data. Creates a new `SSymbol
  330. ## SexpNode` with value t or `SNil SexpNode`.
  331. if b:
  332. result = SexpNode(kind: SSymbol, symbol: "t")
  333. else:
  334. result = SexpNode(kind: SNil)
  335. proc sexp*(elements: openArray[SexpNode]): SexpNode =
  336. ## Generic constructor for SEXP data. Creates a new `SList SexpNode`
  337. result = SexpNode(kind: SList)
  338. newSeq(result.elems, elements.len)
  339. for i, p in pairs(elements): result.elems[i] = p
  340. proc sexp*(s: SexpNode): SexpNode =
  341. result = s
  342. proc toSexp(x: NimNode): NimNode {.compileTime.} =
  343. case x.kind
  344. of nnkBracket:
  345. result = newNimNode(nnkBracket)
  346. for i in 0 ..< x.len:
  347. result.add(toSexp(x[i]))
  348. else:
  349. result = x
  350. result = prefix(result, "sexp")
  351. macro convertSexp*(x: untyped): untyped =
  352. ## Convert an expression to a SexpNode directly, without having to specify
  353. ## `%` for every element.
  354. result = toSexp(x)
  355. func `==`* (a, b: SexpNode): bool =
  356. ## Check two nodes for equality
  357. if a.isNil:
  358. if b.isNil: return true
  359. return false
  360. elif b.isNil or a.kind != b.kind:
  361. return false
  362. else:
  363. return case a.kind
  364. of SString:
  365. a.str == b.str
  366. of SInt:
  367. a.num == b.num
  368. of SFloat:
  369. a.fnum == b.fnum
  370. of SNil:
  371. true
  372. of SList:
  373. a.elems == b.elems
  374. of SSymbol:
  375. a.symbol == b.symbol
  376. of SCons:
  377. a.car == b.car and a.cdr == b.cdr
  378. proc hash* (n:SexpNode): Hash =
  379. ## Compute the hash for a SEXP node
  380. case n.kind
  381. of SList:
  382. result = hash(n.elems)
  383. of SInt:
  384. result = hash(n.num)
  385. of SFloat:
  386. result = hash(n.fnum)
  387. of SString:
  388. result = hash(n.str)
  389. of SNil:
  390. result = hash(0)
  391. of SSymbol:
  392. result = hash(n.symbol)
  393. of SCons:
  394. result = hash(n.car) !& hash(n.cdr)
  395. proc len*(n: SexpNode): int =
  396. ## If `n` is a `SList`, it returns the number of elements.
  397. ## If `n` is a `JObject`, it returns the number of pairs.
  398. ## Else it returns 0.
  399. case n.kind
  400. of SList: result = n.elems.len
  401. else: discard
  402. proc `[]`*(node: SexpNode, index: int): SexpNode =
  403. ## Gets the node at `index` in a List. Result is undefined if `index`
  404. ## is out of bounds
  405. assert(not isNil(node))
  406. assert(node.kind == SList)
  407. return node.elems[index]
  408. proc add*(father, child: SexpNode) =
  409. ## Adds `child` to a SList node `father`.
  410. assert father.kind == SList
  411. father.elems.add(child)
  412. # ------------- pretty printing ----------------------------------------------
  413. proc indent(s: var string, i: int) =
  414. s.add(spaces(i))
  415. proc newIndent(curr, indent: int, ml: bool): int =
  416. if ml: return curr + indent
  417. else: return indent
  418. proc nl(s: var string, ml: bool) =
  419. if ml: s.add("\n")
  420. proc escapeJson*(s: string): string =
  421. ## Converts a string `s` to its JSON representation.
  422. result = newStringOfCap(s.len + s.len shr 3)
  423. result.add("\"")
  424. for x in runes(s):
  425. var r = int(x)
  426. if r >= 32 and r <= 127:
  427. var c = chr(r)
  428. case c
  429. of '"': result.add("\\\"") #" # gotta fix nim-mode
  430. of '\\': result.add("\\\\")
  431. else: result.add(c)
  432. else:
  433. result.add("\\u")
  434. result.add(toHex(r, 4))
  435. result.add("\"")
  436. proc copy*(p: SexpNode): SexpNode =
  437. ## Performs a deep copy of `a`.
  438. case p.kind
  439. of SString:
  440. result = newSString(p.str)
  441. of SInt:
  442. result = newSInt(p.num)
  443. of SFloat:
  444. result = newSFloat(p.fnum)
  445. of SNil:
  446. result = newSNil()
  447. of SSymbol:
  448. result = newSSymbol(p.symbol)
  449. of SList:
  450. result = newSList()
  451. for i in items(p.elems):
  452. result.elems.add(copy(i))
  453. of SCons:
  454. result = newSCons(copy(p.car), copy(p.cdr))
  455. proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true,
  456. lstArr = false, currIndent = 0) =
  457. case node.kind
  458. of SString:
  459. if lstArr: result.indent(currIndent)
  460. result.add(escapeJson(node.str))
  461. of SInt:
  462. if lstArr: result.indent(currIndent)
  463. result.addInt(node.num)
  464. of SFloat:
  465. if lstArr: result.indent(currIndent)
  466. result.addFloat(node.fnum)
  467. of SNil:
  468. if lstArr: result.indent(currIndent)
  469. result.add("nil")
  470. of SSymbol:
  471. if lstArr: result.indent(currIndent)
  472. result.add(node.symbol)
  473. of SList:
  474. if lstArr: result.indent(currIndent)
  475. if len(node.elems) != 0:
  476. result.add("(")
  477. result.nl(ml)
  478. for i in 0..len(node.elems)-1:
  479. if i > 0:
  480. result.add(" ")
  481. result.nl(ml) # New Line
  482. toPretty(result, node.elems[i], indent, ml,
  483. true, newIndent(currIndent, indent, ml))
  484. result.nl(ml)
  485. result.indent(currIndent)
  486. result.add(")")
  487. else: result.add("nil")
  488. of SCons:
  489. if lstArr: result.indent(currIndent)
  490. result.add("(")
  491. toPretty(result, node.car, indent, ml,
  492. true, newIndent(currIndent, indent, ml))
  493. result.add(" . ")
  494. toPretty(result, node.cdr, indent, ml,
  495. true, newIndent(currIndent, indent, ml))
  496. result.add(")")
  497. proc pretty*(node: SexpNode, indent = 2): string =
  498. ## Converts `node` to its Sexp Representation, with indentation and
  499. ## on multiple lines.
  500. result = ""
  501. toPretty(result, node, indent)
  502. proc `$`*(node: SexpNode): string =
  503. ## Converts `node` to its SEXP Representation on one line.
  504. result = ""
  505. toPretty(result, node, 0, false)
  506. iterator items*(node: SexpNode): SexpNode =
  507. ## Iterator for the items of `node`. `node` has to be a SList.
  508. assert node.kind == SList
  509. for i in items(node.elems):
  510. yield i
  511. iterator mitems*(node: var SexpNode): var SexpNode =
  512. ## Iterator for the items of `node`. `node` has to be a SList. Items can be
  513. ## modified.
  514. assert node.kind == SList
  515. for i in mitems(node.elems):
  516. yield i
  517. proc eat(p: var SexpParser, tok: TTokKind) =
  518. if p.tok == tok: discard getTok(p)
  519. else: raiseParseErr(p, tokToStr[tok])
  520. proc parseSexp(p: var SexpParser): SexpNode =
  521. ## Parses SEXP from a SEXP Parser `p`.
  522. case p.tok
  523. of tkString:
  524. # we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
  525. result = SexpNode(kind: SString, str: move p.a)
  526. discard getTok(p)
  527. of tkInt:
  528. result = newSInt(parseBiggestInt(p.a))
  529. discard getTok(p)
  530. of tkFloat:
  531. result = newSFloat(parseFloat(p.a))
  532. discard getTok(p)
  533. of tkNil:
  534. result = newSNil()
  535. discard getTok(p)
  536. of tkSymbol:
  537. result = SexpNode(kind: SSymbol, symbol: move p.a)
  538. discard getTok(p)
  539. of tkParensLe:
  540. result = newSList()
  541. discard getTok(p)
  542. while p.tok notin {tkParensRi, tkDot}:
  543. result.add(parseSexp(p))
  544. if p.tok != tkSpace: break
  545. discard getTok(p)
  546. if p.tok == tkDot:
  547. eat(p, tkDot)
  548. eat(p, tkSpace)
  549. result.add(parseSexp(p))
  550. result = newSCons(result[0], result[1])
  551. eat(p, tkParensRi)
  552. of tkSpace, tkDot, tkError, tkParensRi, tkEof:
  553. raiseParseErr(p, "(")
  554. proc open*(my: var SexpParser, input: Stream) =
  555. ## initializes the parser with an input stream.
  556. lexbase.open(my, input)
  557. my.kind = sexpError
  558. my.a = ""
  559. proc parseSexp*(s: Stream): SexpNode =
  560. ## Parses from a buffer `s` into a `SexpNode`.
  561. var p: SexpParser
  562. p.open(s)
  563. discard getTok(p) # read first token
  564. result = p.parseSexp()
  565. p.close()
  566. proc parseSexp*(buffer: string): SexpNode =
  567. ## Parses Sexp from `buffer`.
  568. result = parseSexp(newStringStream(buffer))
  569. when isMainModule:
  570. let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""")
  571. assert(testSexp[0].getNum == 1)
  572. assert(testSexp[1][0].getNum == 98)
  573. assert(testSexp[2].getElems == @[])
  574. assert(testSexp[4].getSymbol == "foobar")
  575. assert(testSexp[5].getStr == "foo")
  576. let alist = parseSexp("""((1 . 2) (2 . "foo"))""")
  577. assert(alist[0].getCons.car.getNum == 1)
  578. assert(alist[0].getCons.cdr.getNum == 2)
  579. assert(alist[1].getCons.cdr.getStr == "foo")
  580. # Generator:
  581. var j = convertSexp([true, false, "foobar", [1, 2, "baz"]])
  582. assert($j == """(t nil "foobar" (1 2 "baz"))""")