Current section
Files
Jump to
Current section
Files
lib/tokenizer.ex
defmodule Sammal.Tokenizer do
@moduledoc """
Tokenizer and various helper methods.
"""
alias Sammal.{Expr, SammalError}
@tokenizer_regex ~r/(['()]|"[^"]*"?|[\w-+\/.#]+)/
@doc ~S"""
Split a line of raw input into a list of Node structs.
## Example
iex> Sammal.Tokenizer.tokenize("(define x 10)")
{:ok, [%Sammal.Expr{lex: "(", line: 0, row: 0, val: :"(", ctx: "(define x 10)"},
%Sammal.Expr{lex: "define", line: 0, row: 1, val: :define, ctx: "(define x 10)"},
%Sammal.Expr{lex: "x", line: 0, row: 8, val: :x, ctx: "(define x 10)"},
%Sammal.Expr{lex: "10", line: 0, row: 10, val: 10, ctx: "(define x 10)"},
%Sammal.Expr{lex: ")", line: 0, row: 12, val: :")", ctx: "(define x 10)"}]}
"""
def tokenize(line, line_index \\ 0)
def tokenize(";" <> _, _), do: {:ok, []}
def tokenize(line, line_index) do
tokens =
@tokenizer_regex
|> Regex.scan(line, capture: :first, return: :index)
|> Enum.map(fn [{row, n}] ->
lex = String.slice(line, row, n)
token = %Expr{ctx: line, row: row, lex: lex, line: line_index}
case lexeme_to_value(lex) do
{:ok, val} -> %{token | val: val}
{:error, error} -> throw SammalError.new(error, token)
end
end)
{:ok, tokens}
catch
%SammalError{} = error -> {:error, error}
end
@doc ~S"""
Given a lexeme, return a matching Elixir value (or an error).
## Example
iex> Sammal.Tokenizer.lexeme_to_value("12")
{:ok, 12}
iex> Sammal.Tokenizer.lexeme_to_value("12.12")
{:ok, 12.12}
iex> Sammal.Tokenizer.lexeme_to_value("\"12\"")
{:ok, "12"}
"""
def lexeme_to_value("#t"), do: {:ok, true}
def lexeme_to_value("#f"), do: {:ok, false}
def lexeme_to_value("\"" <> tail) do
if String.ends_with?(tail, "\"") do
{:ok, String.slice(tail, 0..-2)}
else
{:error, :ending_quote}
end
end
def lexeme_to_value(lex) do
case Integer.parse(lex) do
{val, ""} -> {:ok, val}
:error -> {:ok, String.to_atom(lex)}
{val, _} -> case Float.parse(lex) do
{val, ""} -> {:ok, val}
_ -> {:ok, String.to_atom(lex)}
end
end
end
end