tests: make throw of non-strings optional/soft.
[jackhill/mal.git] / vb / reader.vb
1 Imports System
2 Imports System.Collections
3 Imports System.Collections.Generic
4 Imports System.Text.RegularExpressions
5 Imports Mal
6 Imports MalVal = Mal.types.MalVal
7 Imports MalSymbol = Mal.types.MalSymbol
8 Imports MalList = Mal.types.MalList
9 Imports MalVector = Mal.types.MalVector
10 Imports MalHashMap = Mal.types.MalHashMap
11 Imports MalThrowable = Mal.types.MalThrowable
12 Imports MalContinue = Mal.types.MalContinue
13
14 Namespace 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)
51 Dim pattern As String = "[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""|;.*|[^\s \[\]{}()'""`~@,;]*)"
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()
67 Dim pattern As String = "(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*"")$|^:(.*)|(^[^""]*$)"
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) _
86 .Replace("\""", """") _
87 .Replace("\n", Environment.NewLine) _
88 .Replace("\\", "\"))
89 Else If match.Groups(7).Value <> String.Empty Then
90 return New Mal.types.MalString(ChrW(&H029e) & match.Groups(7).Value)
91 Else If match.Groups(8).Value <> String.Empty Then
92 return New Mal.types.MalSymbol(match.Groups(8).Value)
93 Else
94 throw New ParseError("unrecognized '" & match.Groups(0).Value & "'")
95 End If
96 End Function
97
98 Shared Function read_list(rdr As Reader, lst As MalList,
99 start As String, last As String) As MalVal
100 Dim token As String = rdr.get_next()
101 If token(0) <> start Then
102 throw New ParseError("expected '" & start & "'")
103 End If
104
105 token = rdr.peek()
106 While token IsNot Nothing AndAlso token(0) <> last
107 lst.conj_BANG(read_form(rdr))
108 token = rdr.peek()
109 End While
110
111 If token Is Nothing Then
112 throw New ParseError("expected '" & last & "', got EOF")
113 End If
114 rdr.get_next()
115
116 return lst
117 End Function
118
119 Shared Function read_hash_map(rdr As Reader) As MalVal
120 Dim lst As MalList = DirectCast(read_list(rdr, new MalList(),
121 "{", "}"),MalList)
122 return New MalHashMap(lst)
123 End Function
124
125
126 Shared Function read_form(rdr As Reader) As MalVal
127 Dim token As String = rdr.peek()
128 If token Is Nothing Then
129 throw New MalContinue()
130 End If
131 Dim form As MalVal = Nothing
132
133 Select token
134 Case "'"
135 rdr.get_next()
136 return New MalList(New MalSymbol("quote"),
137 read_form(rdr))
138 Case "`"
139 rdr.get_next()
140 return New MalList(New MalSymbol("quasiquote"),
141 read_form(rdr))
142 Case "~"
143 rdr.get_next()
144 return New MalList(New MalSymbol("unquote"),
145 read_form(rdr))
146 Case "~@"
147 rdr.get_next()
148 return new MalList(New MalSymbol("splice-unquote"),
149 read_form(rdr))
150 Case "^"
151 rdr.get_next()
152 Dim meta As MalVal = read_form(rdr)
153 return new MalList(New MalSymbol("with-meta"),
154 read_form(rdr),
155 meta)
156 Case "@"
157 rdr.get_next()
158 return new MalList(New MalSymbol("deref"),
159 read_form(rdr))
160
161 Case "("
162 form = read_list(rdr, New MalList(), "(" , ")")
163 Case ")"
164 throw New ParseError("unexpected ')'")
165 Case "["
166 form = read_list(rdr, New MalVector(), "[" , "]")
167 Case "]"
168 throw New ParseError("unexpected ']'")
169 Case "{"
170 form = read_hash_map(rdr)
171 Case "}"
172 throw New ParseError("unexpected '}'")
173 Case Else
174 form = read_atom(rdr)
175 End Select
176 return form
177 End Function
178
179
180 Shared Function read_str(str As string) As MalVal
181 return read_form(New Reader(tokenize(str)))
182 End Function
183 End Class
184 End Namespace