Merge pull request #387 from asarhaddon/test-macroexpand-no-quasiquote
[jackhill/mal.git] / vb / reader.vb
CommitLineData
ee7cd585
JM
1Imports System
2Imports System.Collections
3Imports System.Collections.Generic
4Imports System.Text.RegularExpressions
5Imports Mal
6Imports MalVal = Mal.types.MalVal
7Imports MalSymbol = Mal.types.MalSymbol
8Imports MalList = Mal.types.MalList
9Imports MalVector = Mal.types.MalVector
10Imports MalHashMap = Mal.types.MalHashMap
11Imports MalThrowable = Mal.types.MalThrowable
12Imports MalContinue = Mal.types.MalContinue
13
14Namespace Mal
15 Public Class reader
16 Public Class ParseError
17 Inherits MalThrowable
18 Public Sub New(msg As String)
19 MyBase.New(msg)
20 End Sub
21 End Class
22
23 Public Class Reader
24 Private tokens As New List(Of String)
25 Private position As Int32 = 0
26 Sub New(t As List(Of String))
27 tokens = t
28 position = 0
29 End Sub
30
31 Public Function peek() As String
32 If position >= tokens.Count Then
33 return Nothing
34 Else
35 return tokens(position)
36 End If
37 End Function
38
39 Public Function get_next() As String
40 If position >= tokens.Count Then
41 return Nothing
42 Else
43 position += 1
44 return tokens(position-1)
45 End If
46 End Function
47 End Class
48
49 Shared Function tokenize(str As String) As List(Of String)
50 Dim tokens As New List(Of String)
4aa0ebdf 51 Dim pattern As String = "[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""?|;.*|[^\s \[\]{}()'""`~@,;]*)"
ee7cd585
JM
52 Dim regex As New Regex(pattern)
53 For Each match As Match In regex.Matches(str)
54 Dim token As String = match.Groups(1).Value
55 If Not token Is Nothing _
56 AndAlso Not token = "" _
57 AndAlso Not token(0) = ";" Then
58 'Console.WriteLine("match: ^" & match.Groups[1] & "$")
59 tokens.Add(token)
60 End If
61 Next
62 return tokens
63 End Function
64
65 Shared Function read_atom(rdr As Reader) As MalVal
66 Dim token As String = rdr.get_next()
c1982f1a 67 Dim pattern As String = "(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|(^""(?:[\\].|[^\\""])*""$)|^("".*)|^:(.*)|(^[^""]*$)"
ee7cd585
JM
68 Dim regex As Regex = New Regex(pattern)
69 Dim match As Match = regex.Match(token)
70 'Console.WriteLine("token: ^" + token + "$")
71 If not match.Success Then
72 throw New ParseError("unrecognized token '" & token & "'")
73 End If
74 If match.Groups(1).Value <> String.Empty Then
75 return New Mal.types.MalInt(Integer.Parse(match.Groups(1).Value))
76 Else If match.Groups(3).Value <> String.Empty Then
77 return Mal.types.Nil
78 Else If match.Groups(4).Value <> String.Empty Then
79 return Mal.types.MalTrue
80 Else If match.Groups(5).Value <> String.Empty Then
81 return Mal.types.MalFalse
82 Else If match.Groups(6).Value <> String.Empty Then
83 Dim str As String = match.Groups(6).Value
84 return New Mal.types.MalString(
85 str.Substring(1, str.Length-2) _
42aecee6
JM
86 .Replace("\\", ChrW(&H029e)) _
87 .Replace("\""", """") _
88 .Replace("\n", Environment.NewLine) _
89 .Replace(ChrW(&H029e), "\"))
ee7cd585 90 Else If match.Groups(7).Value <> String.Empty Then
c1982f1a 91 throw New ParseError("expected '""', got EOF")
b8ee29b2 92 Else If match.Groups(8).Value <> String.Empty Then
c1982f1a
BH
93 return New Mal.types.MalString(ChrW(&H029e) & match.Groups(8).Value)
94 Else If match.Groups(9).Value <> String.Empty Then
95 return New Mal.types.MalSymbol(match.Groups(9).Value)
ee7cd585
JM
96 Else
97 throw New ParseError("unrecognized '" & match.Groups(0).Value & "'")
98 End If
99 End Function
100
101 Shared Function read_list(rdr As Reader, lst As MalList,
102 start As String, last As String) As MalVal
103 Dim token As String = rdr.get_next()
104 If token(0) <> start Then
105 throw New ParseError("expected '" & start & "'")
106 End If
107
108 token = rdr.peek()
109 While token IsNot Nothing AndAlso token(0) <> last
110 lst.conj_BANG(read_form(rdr))
111 token = rdr.peek()
112 End While
113
114 If token Is Nothing Then
115 throw New ParseError("expected '" & last & "', got EOF")
116 End If
117 rdr.get_next()
118
119 return lst
120 End Function
121
122 Shared Function read_hash_map(rdr As Reader) As MalVal
123 Dim lst As MalList = DirectCast(read_list(rdr, new MalList(),
124 "{", "}"),MalList)
125 return New MalHashMap(lst)
126 End Function
127
128
129 Shared Function read_form(rdr As Reader) As MalVal
130 Dim token As String = rdr.peek()
131 If token Is Nothing Then
132 throw New MalContinue()
133 End If
134 Dim form As MalVal = Nothing
135
136 Select token
137 Case "'"
138 rdr.get_next()
139 return New MalList(New MalSymbol("quote"),
140 read_form(rdr))
141 Case "`"
142 rdr.get_next()
143 return New MalList(New MalSymbol("quasiquote"),
144 read_form(rdr))
145 Case "~"
146 rdr.get_next()
147 return New MalList(New MalSymbol("unquote"),
148 read_form(rdr))
149 Case "~@"
150 rdr.get_next()
151 return new MalList(New MalSymbol("splice-unquote"),
152 read_form(rdr))
153 Case "^"
154 rdr.get_next()
155 Dim meta As MalVal = read_form(rdr)
156 return new MalList(New MalSymbol("with-meta"),
157 read_form(rdr),
158 meta)
159 Case "@"
160 rdr.get_next()
161 return new MalList(New MalSymbol("deref"),
162 read_form(rdr))
163
164 Case "("
165 form = read_list(rdr, New MalList(), "(" , ")")
166 Case ")"
167 throw New ParseError("unexpected ')'")
168 Case "["
169 form = read_list(rdr, New MalVector(), "[" , "]")
170 Case "]"
171 throw New ParseError("unexpected ']'")
172 Case "{"
173 form = read_hash_map(rdr)
174 Case "}"
175 throw New ParseError("unexpected '}'")
176 Case Else
177 form = read_atom(rdr)
178 End Select
179 return form
180 End Function
181
182
183 Shared Function read_str(str As string) As MalVal
184 return read_form(New Reader(tokenize(str)))
185 End Function
186 End Class
187End Namespace