mirror of
https://github.com/kanaka/mal.git
synced 2024-09-21 02:27:10 +03:00
4aa0ebdf47
Add a step1 test to make sure that implementations are properly throwing an error on unclosed strings. Fix 47 implementations and update the guide to note the correct behavior.
189 lines
7.1 KiB
VB.net
189 lines
7.1 KiB
VB.net
Imports System
|
|
Imports System.Collections
|
|
Imports System.Collections.Generic
|
|
Imports System.Text.RegularExpressions
|
|
Imports Mal
|
|
Imports MalVal = Mal.types.MalVal
|
|
Imports MalSymbol = Mal.types.MalSymbol
|
|
Imports MalList = Mal.types.MalList
|
|
Imports MalVector = Mal.types.MalVector
|
|
Imports MalHashMap = Mal.types.MalHashMap
|
|
Imports MalThrowable = Mal.types.MalThrowable
|
|
Imports MalContinue = Mal.types.MalContinue
|
|
|
|
Namespace Mal
|
|
Public Class reader
|
|
Public Class ParseError
|
|
Inherits MalThrowable
|
|
Public Sub New(msg As String)
|
|
MyBase.New(msg)
|
|
End Sub
|
|
End Class
|
|
|
|
Public Class Reader
|
|
Private tokens As New List(Of String)
|
|
Private position As Int32 = 0
|
|
Sub New(t As List(Of String))
|
|
tokens = t
|
|
position = 0
|
|
End Sub
|
|
|
|
Public Function peek() As String
|
|
If position >= tokens.Count Then
|
|
return Nothing
|
|
Else
|
|
return tokens(position)
|
|
End If
|
|
End Function
|
|
|
|
Public Function get_next() As String
|
|
If position >= tokens.Count Then
|
|
return Nothing
|
|
Else
|
|
position += 1
|
|
return tokens(position-1)
|
|
End If
|
|
End Function
|
|
End Class
|
|
|
|
Shared Function tokenize(str As String) As List(Of String)
|
|
Dim tokens As New List(Of String)
|
|
Dim pattern As String = "[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""?|;.*|[^\s \[\]{}()'""`~@,;]*)"
|
|
Dim regex As New Regex(pattern)
|
|
For Each match As Match In regex.Matches(str)
|
|
Dim token As String = match.Groups(1).Value
|
|
If Not token Is Nothing _
|
|
AndAlso Not token = "" _
|
|
AndAlso Not token(0) = ";" Then
|
|
'Console.WriteLine("match: ^" & match.Groups[1] & "$")
|
|
tokens.Add(token)
|
|
End If
|
|
Next
|
|
return tokens
|
|
End Function
|
|
|
|
Shared Function read_atom(rdr As Reader) As MalVal
|
|
Dim token As String = rdr.get_next()
|
|
Dim pattern As String = "(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*)|^:(.*)|(^[^""]*$)"
|
|
Dim regex As Regex = New Regex(pattern)
|
|
Dim match As Match = regex.Match(token)
|
|
'Console.WriteLine("token: ^" + token + "$")
|
|
If not match.Success Then
|
|
throw New ParseError("unrecognized token '" & token & "'")
|
|
End If
|
|
If match.Groups(1).Value <> String.Empty Then
|
|
return New Mal.types.MalInt(Integer.Parse(match.Groups(1).Value))
|
|
Else If match.Groups(3).Value <> String.Empty Then
|
|
return Mal.types.Nil
|
|
Else If match.Groups(4).Value <> String.Empty Then
|
|
return Mal.types.MalTrue
|
|
Else If match.Groups(5).Value <> String.Empty Then
|
|
return Mal.types.MalFalse
|
|
Else If match.Groups(6).Value <> String.Empty Then
|
|
Dim str As String = match.Groups(6).Value
|
|
If str(str.Length-1) <> """" Then
|
|
throw New ParseError("expected '""', got EOF")
|
|
End If
|
|
return New Mal.types.MalString(
|
|
str.Substring(1, str.Length-2) _
|
|
.Replace("\\", ChrW(&H029e)) _
|
|
.Replace("\""", """") _
|
|
.Replace("\n", Environment.NewLine) _
|
|
.Replace(ChrW(&H029e), "\"))
|
|
Else If match.Groups(7).Value <> String.Empty Then
|
|
return New Mal.types.MalString(ChrW(&H029e) & match.Groups(7).Value)
|
|
Else If match.Groups(8).Value <> String.Empty Then
|
|
return New Mal.types.MalSymbol(match.Groups(8).Value)
|
|
Else
|
|
throw New ParseError("unrecognized '" & match.Groups(0).Value & "'")
|
|
End If
|
|
End Function
|
|
|
|
Shared Function read_list(rdr As Reader, lst As MalList,
|
|
start As String, last As String) As MalVal
|
|
Dim token As String = rdr.get_next()
|
|
If token(0) <> start Then
|
|
throw New ParseError("expected '" & start & "'")
|
|
End If
|
|
|
|
token = rdr.peek()
|
|
While token IsNot Nothing AndAlso token(0) <> last
|
|
lst.conj_BANG(read_form(rdr))
|
|
token = rdr.peek()
|
|
End While
|
|
|
|
If token Is Nothing Then
|
|
throw New ParseError("expected '" & last & "', got EOF")
|
|
End If
|
|
rdr.get_next()
|
|
|
|
return lst
|
|
End Function
|
|
|
|
Shared Function read_hash_map(rdr As Reader) As MalVal
|
|
Dim lst As MalList = DirectCast(read_list(rdr, new MalList(),
|
|
"{", "}"),MalList)
|
|
return New MalHashMap(lst)
|
|
End Function
|
|
|
|
|
|
Shared Function read_form(rdr As Reader) As MalVal
|
|
Dim token As String = rdr.peek()
|
|
If token Is Nothing Then
|
|
throw New MalContinue()
|
|
End If
|
|
Dim form As MalVal = Nothing
|
|
|
|
Select token
|
|
Case "'"
|
|
rdr.get_next()
|
|
return New MalList(New MalSymbol("quote"),
|
|
read_form(rdr))
|
|
Case "`"
|
|
rdr.get_next()
|
|
return New MalList(New MalSymbol("quasiquote"),
|
|
read_form(rdr))
|
|
Case "~"
|
|
rdr.get_next()
|
|
return New MalList(New MalSymbol("unquote"),
|
|
read_form(rdr))
|
|
Case "~@"
|
|
rdr.get_next()
|
|
return new MalList(New MalSymbol("splice-unquote"),
|
|
read_form(rdr))
|
|
Case "^"
|
|
rdr.get_next()
|
|
Dim meta As MalVal = read_form(rdr)
|
|
return new MalList(New MalSymbol("with-meta"),
|
|
read_form(rdr),
|
|
meta)
|
|
Case "@"
|
|
rdr.get_next()
|
|
return new MalList(New MalSymbol("deref"),
|
|
read_form(rdr))
|
|
|
|
Case "("
|
|
form = read_list(rdr, New MalList(), "(" , ")")
|
|
Case ")"
|
|
throw New ParseError("unexpected ')'")
|
|
Case "["
|
|
form = read_list(rdr, New MalVector(), "[" , "]")
|
|
Case "]"
|
|
throw New ParseError("unexpected ']'")
|
|
Case "{"
|
|
form = read_hash_map(rdr)
|
|
Case "}"
|
|
throw New ParseError("unexpected '}'")
|
|
Case Else
|
|
form = read_atom(rdr)
|
|
End Select
|
|
return form
|
|
End Function
|
|
|
|
|
|
Shared Function read_str(str As string) As MalVal
|
|
return read_form(New Reader(tokenize(str)))
|
|
End Function
|
|
End Class
|
|
End Namespace
|