aboutsummaryrefslogtreecommitdiff
path: root/nim/reader.nim
blob: 10fce10ffb8a71b6b89a89c040af4906c25ed446 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import nre, optional_t, strutils, types

let
  tokenRE = re"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"""
  intRE   = re"-?[0-9]+$"

type
  Blank* = object of Exception

  Reader = object
    tokens: seq[string]
    position: int

proc next(r: var Reader): string =
  if r.position >= r.tokens.len:
    result = nil
  else:
    result = r.tokens[r.position]
    inc r.position

proc peek(r: Reader): string =
  if r.position >= r.tokens.len: nil
  else: r.tokens[r.position]

proc tokenize(str: string): seq[string] =
  result = @[]
  for match in str.findIter(tokenRE):
    if match.captures[0][0] != ';':
      result.add match.captures[0]

proc read_form(r: var Reader): MalType

proc read_seq(r: var Reader, fr, to: string): seq[MalType] =
  result = @[]
  var t = r.next
  if t != fr: raise newException(ValueError, "expected '" & fr & "'")

  t = r.peek
  while t != to:
    if t == nil: raise newException(ValueError, "expected '" & to & "', got EOF")
    result.add r.read_form
    t = r.peek
  discard r.next

proc read_list(r: var Reader): MalType =
  result = list r.read_seq("(", ")")

proc read_vector(r: var Reader): MalType =
  result = vector r.read_seq("[", "]")

proc read_hash_map(r: var Reader): MalType =
  result = hash_map r.read_seq("{", "}")

proc read_atom(r: var Reader): MalType =
  let t = r.next
  if t.match(intRE): number t.parseInt
  elif t[0] == '"':  str t[1 .. <t.high].replace("\\\"", "\"")
  elif t[0] == ':':  keyword t[1 .. t.high]
  elif t == "nil":   nilObj
  elif t == "true":  trueObj
  elif t == "false": falseObj
  else:              symbol t

proc read_form(r: var Reader): MalType =
  if r.peek[0] == ';':
    discard r.next
    return nilObj
  case r.peek
  of "'":
    discard r.next
    result = list(symbol "quote", r.read_form)
  of "`":
    discard r.next
    result = list(symbol "quasiquote", r.read_form)
  of "~":
    discard r.next
    result = list(symbol "unquote", r.read_form)
  of "~@":
    discard r.next
    result = list(symbol "splice-unquote", r.read_form)
  of "^":
    discard r.next
    let meta = r.read_form
    result = list(symbol "with-meta", r.read_form, meta)
  of "@":
    discard r.next
    result = list(symbol "deref", r.read_form)

  # list
  of "(": result = r.read_list
  of ")": raise newException(ValueError, "unexpected ')'")

  # vector
  of "[": result = r.read_vector
  of "]": raise newException(ValueError, "unexpected ']'")

  # hash-map
  of "{": result = r.read_hash_map
  of "}": raise newException(ValueError, "unexpected '}'")

  # atom
  else:   result = r.read_atom

proc read_str*(str: string): MalType =
  var r = Reader(tokens: str.tokenize)
  if r.tokens.len == 0:
    raise newException(Blank, "Blank line")
  r.read_form