1
1
mirror of https://github.com/kanaka/mal.git synced 2024-08-18 02:00:40 +03:00
mal/impls/scala/reader.scala
Joel Martin 8a19f60386 Move implementations into impls/ dir
- Reorder README to have implementation list after "learning tool"
  bullet.

- This also moves tests/ and libs/ into impls. It would be preferrable
  to have these directories at the top level.  However, this causes
  difficulties with the wasm implementations which need pre-open
  directories and have trouble with paths starting with "../../". So
  in lieu of that, symlink those directories to the top-level.

- Move the run_argv_test.sh script into the tests directory for
  general hygiene.
2020-02-10 23:50:16 -06:00

98 lines
3.2 KiB
Scala

import scala.util.matching.Regex
import types.{MalList, _list, MalVector, _vector, MalHashMap, _hash_map}
object reader {
class Reader (tokens: Array[String]) {
var data = tokens
var position: Int = 0
def peek(): String = {
if (position >= data.length) return(null)
data(position)
}
def next(): String = {
if (position >= data.length) return(null)
position = position + 1
data(position-1)
}
}
def tokenize(str: String): Array[String] = {
val re = """[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)""".r
re.findAllMatchIn(str).map{ _.group(1) }
.filter{ s => s != "" && s(0) != ';' }
.toArray
}
def parse_str(s: String): String = {
// TODO: use re.replaceAllIn instead for single pass
s.replace("\\\\", "\u029e")
.replace("\\\"", "\"")
.replace("\\n", "\n")
.replace("\u029e", "\\")
}
def read_atom(rdr: Reader): Any = {
val token = rdr.next()
val re_int = """^(-?[0-9]+)$""".r
val re_flt = """^(-?[0-9][0-9.]*)$""".r
val re_str = """^"((?:\\.|[^\\"])*)"$""".r
val re_str_bad = """^"(.*)$""".r
val re_key = """^:(.*)$""".r
return token match {
case re_int(i) => i.toLong // integer
case re_flt(f) => f.toDouble // float
case re_str(s) => parse_str(s) // string
case re_str_bad(s) =>
throw new Exception("expected '\"', got EOF")
case re_key(k) => "\u029e" + k // keyword
case "nil" => null
case "true" => true
case "false" => false
case _ => Symbol(token) // symbol
}
}
def read_list(rdr: Reader,
start: String = "(", end: String = ")"): MalList = {
var ast: MalList = _list()
var token = rdr.next()
if (token != start) throw new Exception("expected '" + start + "', got EOF")
while ({token = rdr.peek(); token != end}) {
if (token == null) throw new Exception("expected '" + end + "', got EOF")
ast = ast :+ read_form(rdr)
}
rdr.next()
ast
}
def read_form(rdr: Reader): Any = {
return rdr.peek() match {
case "'" => { rdr.next; _list(Symbol("quote"), read_form(rdr)) }
case "`" => { rdr.next; _list(Symbol("quasiquote"), read_form(rdr)) }
case "~" => { rdr.next; _list(Symbol("unquote"), read_form(rdr)) }
case "~@" => { rdr.next; _list(Symbol("splice-unquote"), read_form(rdr)) }
case "^" => { rdr.next; val meta = read_form(rdr);
_list(Symbol("with-meta"), read_form(rdr), meta) }
case "@" => { rdr.next; _list(Symbol("deref"), read_form(rdr)) }
case "(" => read_list(rdr)
case ")" => throw new Exception("unexpected ')')")
case "[" => _vector(read_list(rdr, "[", "]").value:_*)
case "]" => throw new Exception("unexpected ']')")
case "{" => _hash_map(read_list(rdr, "{", "}").value:_*)
case "}" => throw new Exception("unexpected '}')")
case _ => read_atom(rdr)
}
}
def read_str(str: String): Any = {
val tokens = tokenize(str)
if (tokens.length == 0) return null
return read_form(new Reader(tokens))
}
}
// vim: ts=2:sw=2