Packages

Prolog interpreter/compiler in Elixir

Current section

Files

Jump to
elxlog lib read.ex
Raw

lib/read.ex

# ---------------read----------------------------------
defmodule Read do
@moduledoc """
Read module is input functions of Elxlog.
main function is parse/2
parse/2 recieve string from stdio or file
and return listed predicate or clause data
{return_val, buffer}
"""
@doc """
lowercase or number char or underbar
iex> Read.is_atom_str("asdf")
true
iex> Read.is_atom_str("asdf123")
true
iex> Read.is_atom_str("asdf_abc")
true
iex> Read.is_atom_str("Asdf")
false
"""
def is_atom_str(x) do
y = String.to_charlist(x)
if hd(y) >= 97 && hd(y) <= 122 &&
Enum.all?(y, fn z ->
(z >= 97 && z <= 122) ||
(z >= 48 && z <= 57) ||
z == 95
end) do
true
else
false
end
end
@doc """
iex> Read.is_builtin_str("halt")
true
iex> Read.is_builtin_str("halz")
false
"""
def is_builtin_str(x) do
Enum.member?(
[
"halt",
"write",
"nl",
"is",
"listing",
"ask",
"debug",
"trace",
"notrace",
"atom",
"atomic",
"integer",
"float",
"number",
"reconsult",
"var",
"nonvar",
"elixir",
"true",
"fail",
"between",
"not",
"length",
"time",
"append",
"functor",
"arg",
"name",
"read",
"member",
"compile",
"parallel",
":-",
">",
"<",
"=>",
"=<",
"=..",
"==",
"!=",
"="
],
x
)
end
def is_func_str(x) do
Enum.member?(["+", "-", "*", "/", "^"], x)
end
@doc """
if prefix of str is elx_ it is Elixir function fname
iex> Read.is_elixir_func_str("elx_foo")
true
iex> Read.is_elixir_func_str("foo")
false
"""
def is_elixir_func_str(x) do
if String.at(x, 0) == "e" && String.at(x, 1) == "l" && String.at(x, 2) == "x" &&
String.at(x, 3) == "_" do
true
else
false
end
end
@doc """
iex(1)> Read.elixir_name("elx_foo")
"foo"
"""
def elixir_name(x) do
{_, name} = String.split_at(x, 4)
name
end
@doc """
if head charactor is upper case it is variable
if head charactor is underbar it is variable
iex> Read.is_var_str("Abc")
true
iex> Read.is_var_str("abc")
false
"""
def is_var_str(x) do
x1 = String.at(x, 0)
Enum.member?(
[
"_",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"j",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z"
],
x1
)
end
def is_func_atom(x) do
Enum.member?([:+, :-, :*, :/, :^], x)
end
def is_infix_builtin(x) do
Enum.member?([:is, :=, :"=..", :==, :>=, :<=, :>, :<, :^, :==, :!=], x)
end
@doc """
parse/2 is main function in Read module
1st arg is tokenized list data e.g. ["1","+","2"]
2nd arg is :sidio or file
return lited predicate ot clause data
{return_val,buffer}
"""
def parse(buf, stream) do
{s1, buf1} = read(buf, stream)
{s2, buf2} = read(buf1, stream)
cond do
s2 == :. ->
cond do
is_atom(s1) ->
Elxlog.error("Error: parse expected () ", [s1])
!Elxlog.is_pred(s1) && !Elxlog.is_builtin(s1) ->
{[:builtin, [:reconsult | s1]], buf2}
true ->
{s1, buf2}
end
s2 == :":-" ->
{s3, buf3} = parse1(buf2, [], stream)
{[:clause, s1, s3], buf3}
s2 == :"," ->
{s3, buf3} = parse1(buf2, [s1], stream)
{s3, buf3}
is_infix_builtin(s2) ->
{s3, buf3, status} = parse2([], [], buf2, stream)
cond do
status == :. -> {[:builtin, [s2, s1, s3]], buf3}
status == :"," -> parse1(buf3, [[:builtin, [s2, s1, s3]]], stream)
true -> Elxlog.error("Error: illegal delimiter ", [s3, status])
end
true ->
Elxlog.error("Error: syntax error ", [s1, s2])
end
end
def parse1(buf, res, stream) do
{s1, buf1} = read(buf, stream)
{s2, buf2} = read(buf1, stream)
cond do
s2 == :. ->
{res ++ [s1], buf2}
s2 == :")" ->
{res ++ [s1], buf2}
s2 == :"," ->
if !Elxlog.is_pred(s1) && !Elxlog.is_builtin(s1) do
Elxlog.error("Error: expected () ", [s1])
end
parse1(buf2, res ++ [s1], stream)
is_infix_builtin(s2) ->
{s3, buf3, status} = parse2([], [], buf2, stream)
cond do
status == :"," ->
parse1(buf3, res ++ [[:builtin, [s2, s1, s3]]], stream)
status == :. ->
{res ++ [[:builtin, [s2, s1, s3]]], buf3}
status == :")" ->
{res ++ [[:builtin, [s2, s1, s3]]], buf3}
end
true ->
Elxlog.error("Error illegal body ", [s1, s2])
end
end
# parse formula
# 1st arg operand list
# 2nd arg operator list
# 3rd arg buffer
# 4th arg stream (:sidin or file)
# return {value,buffer,last-token}
def parse2([], [], buf, stream) do
# IO.inspect binding()
{s, buf1} = read(buf, stream)
cond do
s == :. -> Elxlog.error("Error: illegal formula1 ", [s])
is_func_atom(s) -> parse2([], [s], buf1, stream)
true -> parse2([s], [], buf1, stream)
end
end
# minus number e.g. ["-", 2, ...] -> -2
def parse2([], [:-], buf, stream) do
{s, buf1} = read(buf, stream)
if !is_number(s) do
Elxlog.error("Error: illegal formula2 ", [s])
end
parse2([-1 * s], [], buf1, stream)
end
def parse2([o1], [], buf, stream) do
# IO.inspect binding()
{s, buf1} = read(buf, stream)
cond do
s == :. -> {o1, buf1, :.}
s == :"," -> {o1, buf1, :","}
s == :")" -> {o1, buf1, :")"}
is_func_atom(s) -> parse2([o1], [s], buf1, stream)
true -> Elxlog.error("Error: illegal formula3 ", [o1, s])
end
end
def parse2([o1], [f1], buf, stream) do
# IO.inspect binding()
{s, buf1} = read(buf, stream)
cond do
s == :- ->
{s1, buf2} = read(buf1, stream)
parse2([-1 * s1, o1], [f1], buf2, stream)
s == :"(" ->
{[_, s1], buf2, term} = parse2([], [], buf1, stream)
if term != :")" do
Elxlog.error("Error: illegal formula paren", [s1])
end
parse2([s1, o1], [f1], buf2, stream)
is_func_atom(s) ->
Elxlog.error("Error: illegal formula4 ", [s])
true ->
parse2([s, o1], [f1], buf1, stream)
end
end
def parse2([o1, o2], [f1], buf, stream) do
# IO.inspect binding()
{s, buf1} = read(buf, stream)
cond do
s == :. -> {[:formula, [f1, o2, o1]], buf1, :.}
s == :"," -> {[:formula, [f1, o2, o1]], buf1, :","}
s == :")" -> {[:formula, [f1, o2, o1]], buf1, :")"}
is_func_atom(s) && weight(s) >= weight(f1) -> parse2([[f1, o2, o1]], [s], buf1, stream)
is_func_atom(s) && weight(s) < weight(f1) -> parse2([o1, o2], [s, f1], buf1, stream)
true -> Elxlog.error("Error: illegal formula5 ", [s])
end
end
def parse2([o1, o2], [f1, f2], buf, stream) do
# IO.inspect binding()
{s, buf1} = read(buf, stream)
cond do
s == :. ->
Elxlog.error("Error: illegal formula6 ", [f1, s])
s == :- ->
{s1, buf1} = read(buf1, stream)
parse2([[f2, o2, [f1, o1, -1 * s1]]], [], buf1, stream)
is_func_atom(s) ->
Elxlog.error("Error: illegal formula7 ", [s])
true ->
parse2([[f2, o2, [f1, o1, s]]], [], buf1, stream)
end
end
defp weight(:+) do
100
end
defp weight(:-) do
100
end
defp weight(:*) do
50
end
defp weight(:/) do
50
end
@doc """
read one unit operator, operand, predicate
e.g. :+ 2 [:foo,1,2]
1st arg is tokenized list
2nd arg is streawm :stdio or file
"""
def read([], stream) do
if stream == :stdin do
buf = IO.gets("") |> tokenize(stream)
read(buf, stream)
else
[]
end
end
def read(["" | xs], stream) do
read(xs, stream)
end
def read(["." | xs], _) do
{:., xs}
end
def read(["," | xs], _) do
{:",", xs}
end
def read([")" | xs], _) do
{:")", xs}
end
def read(["(" | xs], _) do
{:"(", xs}
end
def read(["[" | xs], stream) do
read_list(xs, [], stream)
end
def read([x, "(" | xs], stream) do
# when x = +-*/^
if is_func_str(x) do
{String.to_atom(x), ["(" | xs]}
# when predicate
else
{tuple, rest} = read_tuple(xs, [], stream)
cond do
is_builtin_str(x) -> {[:builtin, [String.to_atom(x) | tuple]], rest}
is_func_str(x) -> {[String.to_atom(x) | tuple], rest}
is_elixir_func_str(x) -> {[:func, [String.to_atom(elixir_name(x)) | tuple]], rest}
true -> {[:pred, [String.to_atom(x) | tuple]], rest}
end
end
end
def read([x, "." | xs], _) do
cond do
is_var_str(x) -> {String.to_atom(x), ["." | xs]}
is_integer_str(x) -> {String.to_integer(x), ["." | xs]}
is_float_str(x) -> {String.to_float(x), ["." | xs]}
true -> {String.to_atom(x), ["." | xs]}
end
end
def read([x, "," | xs], _) do
cond do
is_atom_str(x) -> {String.to_atom(x), ["," | xs]}
is_var_str(x) -> {String.to_atom(x), ["," | xs]}
is_integer_str(x) -> {String.to_integer(x), ["," | xs]}
is_float_str(x) -> {String.to_float(x), ["," | xs]}
true -> {x, ["," | xs]}
end
end
def read([x | xs], _) do
cond do
is_integer_str(x) -> {String.to_integer(x), xs}
is_float_str(x) -> {String.to_float(x), xs}
true -> {String.to_atom(x), xs}
end
end
# for read_list (read simply)
def read1(x) do
cond do
is_integer_str(x) -> String.to_integer(x)
is_float_str(x) -> String.to_float(x)
true -> String.to_atom(x)
end
end
@doc """
1st arg is tokenized buffer list
2nd arg is return value
3rd arg is stream :stdin or file
return {return_val,buffer}
"""
def read_list([], ls, stream) do
if stream == :stdin do
buf = IO.gets("") |> tokenize(stream)
read_list(buf, ls, stream)
else
Elxlog.error("Error: read list", [])
end
end
def read_list(["]" | xs], ls, _) do
{ls, xs}
end
def read_list(["[" | xs], ls, stream) do
{s, rest} = read_list(xs, [], stream)
read_list(rest, ls ++ [s], stream)
end
def read_list(["" | xs], ls, stream) do
read_list(xs, ls, stream)
end
def read_list([x, "|" | xs], ls, stream) do
s = read1(x)
{s1, rest} = read_list(xs, [], stream)
{ls ++ [s] ++ hd(s1), rest}
end
def read_list([x, "," | xs], ls, stream) do
s = read1(x)
read_list(xs, ls ++ [s], stream)
end
def read_list([x, "]" | xs], ls, _) do
s = read1(x)
{ls ++ [s], xs}
end
def read_list(x, _, _) do
IO.inspect(x)
Elxlog.error("Error: read_list ", [])
end
defp read_tuple([], ls, stream) do
if stream == :stdin do
buf = IO.gets("") |> tokenize(stream)
read_tuple(buf, ls, stream)
else
Elxlog.error("Error: read tuple", [])
end
end
defp read_tuple([")" | xs], ls, _) do
{ls, xs}
end
defp read_tuple(["(" | xs], ls, stream) do
{s, rest} = parse(xs, stream)
read_tuple(rest, ls ++ [s], stream)
end
defp read_tuple(["" | xs], ls, stream) do
read_tuple(xs, ls, stream)
end
defp read_tuple(["," | xs], ls, stream) do
read_tuple(xs, ls, stream)
end
defp read_tuple(x, ls, stream) do
{s, rest} = read(x, stream)
read_tuple(rest, ls ++ [s], stream)
end
def tokenize(str, stream) do
str |> String.to_charlist() |> tokenize1([], [], stream)
end
def tokenize1([], [], res, _) do
Enum.reverse(res)
end
def tokenize1([], token, res, _) do
token1 = Enum.reverse(token) |> List.to_string()
res1 = [token1 | res]
Enum.reverse(res1)
end
# tab
def tokenize1([9 | ls], [], res, stream) do
tokenize1(ls, [], res, stream)
end
# LF
def tokenize1([10 | ls], [], res, stream) do
tokenize1(ls, [], res, stream)
end
def tokenize1([10 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [token1 | res], stream)
end
# CR
def tokenize1([13 | ls], [], res, stream) do
tokenize1(ls, [], res, stream)
end
def tokenize1([13 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [token1 | res], stream)
end
# comment %
def tokenize1([37 | ls], [], res, stream) do
ls1 = comment_skip(ls)
tokenize1(ls1, [], res, stream)
end
# space
def tokenize1([32, 32 | ls], token, res, stream) do
tokenize1(ls, token, res, stream)
end
def tokenize1([32 | ls], [], res, stream) do
tokenize1(ls, [], res, stream)
end
def tokenize1([32 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [token1 | res], stream)
end
# left paren
def tokenize1([40 | ls], [], res, stream) do
tokenize1(ls, [], ["(" | res], stream)
end
def tokenize1([40 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["(", token1 | res], stream)
end
# right paren
def tokenize1([41 | ls], [], res, stream) do
tokenize1(ls, [], [")" | res], stream)
end
def tokenize1([41 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [")", token1 | res], stream)
end
# left bracket
def tokenize1([91 | ls], [], res, stream) do
tokenize1(ls, [], ["[" | res], stream)
end
def tokenize1([91 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["[", token1 | res], stream)
end
# right bracket
def tokenize1([93 | ls], [], res, stream) do
tokenize1(ls, [], ["]" | res], stream)
end
def tokenize1([93 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["]", token1 | res], stream)
end
# virtical bar |
def tokenize1([124 | ls], [], res, stream) do
tokenize1(ls, [], ["|" | res], stream)
end
def tokenize1([124 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["|", token1 | res], stream)
end
# comma ,
def tokenize1([44 | ls], [], res, stream) do
tokenize1(ls, [], ["," | res], stream)
end
def tokenize1([44 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [",", token1 | res], stream)
end
# piriod .
# buffer is empty
# when after period is LF or CR and stream is :stdin, it end of string
# when stream is :filein continue tokenize
def tokenize1([46 | ls], [], res, stream) do
cond do
(ls == [10] || ls == [13]) && stream == :stdin ->
Enum.reverse(["." | res])
(hd(ls) == 10 || hd(ls) == 13) && stream == :filein ->
tokenize1(ls, [], ["." | res], stream)
true ->
tokenize1(ls, [46], res, stream)
end
end
# buffer is not empty
def tokenize1([46 | ls], token, res, stream) do
cond do
(ls == [10] || ls == [13]) && stream == :stdin ->
token1 = token |> Enum.reverse() |> List.to_string()
Enum.reverse([".", token1 | res])
(hd(ls) == 10 || hd(ls) == 13) && stream == :filein ->
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [".", token1 | res], stream)
true ->
tokenize1(ls, [46 | token], res, stream)
end
end
# +
def tokenize1([43 | ls], [], res, stream) do
tokenize1(ls, [], ["+" | res], stream)
end
def tokenize1([43 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["+", token1 | res], stream)
end
# :-
def tokenize1([58, 45 | ls], [], res, stream) do
tokenize1(ls, [], [":-" | res], stream)
end
def tokenize1([58, 45 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [":-", token1 | res], stream)
end
# =..
def tokenize1([61, 46, 46 | ls], [], res, stream) do
tokenize1(ls, [], ["=.." | res], stream)
end
def tokenize1([61, 46, 46 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["=..", token1 | res], stream)
end
# ==
def tokenize1([61, 61 | ls], [], res, stream) do
tokenize1(ls, [], ["==" | res], stream)
end
def tokenize1([61, 61 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["==", token1 | res], stream)
end
# !=
def tokenize1([33, 61 | ls], [], res, stream) do
tokenize1(ls, [], ["!=" | res], stream)
end
def tokenize1([3, 61 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["!=", token1 | res], stream)
end
# >=
def tokenize1([62, 61 | ls], [], res, stream) do
tokenize1(ls, [], [">=" | res], stream)
end
def tokenize1([62, 61 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [">=", token1 | res], stream)
end
# >
def tokenize1([62 | ls], [], res, stream) do
tokenize1(ls, [], [">" | res], stream)
end
def tokenize1([62 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], [">", token1 | res], stream)
end
# <=
def tokenize1([60, 61 | ls], [], res, stream) do
tokenize1(ls, [], ["<=" | res], stream)
end
def tokenize1([60, 61 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["<=", token1 | res], stream)
end
# <
def tokenize1([60 | ls], [], res, stream) do
tokenize1(ls, [], ["<" | res], stream)
end
def tokenize1([60 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["<", token1 | res], stream)
end
# =
def tokenize1([61 | ls], [], res, stream) do
tokenize1(ls, [], ["=" | res], stream)
end
def tokenize1([61 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["=", token1 | res], stream)
end
# -
def tokenize1([45 | ls], [], res, stream) do
tokenize1(ls, [], ["-" | res], stream)
end
def tokenize1([45 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["-", token1 | res], stream)
end
# *
def tokenize1([42 | ls], [], res, stream) do
tokenize1(ls, [], ["*" | res], stream)
end
def tokenize1([42 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["*", token1 | res], stream)
end
# /
def tokenize1([47 | ls], [], res, stream) do
tokenize1(ls, [], ["/" | res], stream)
end
def tokenize1([47 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["/", token1 | res], stream)
end
# ^
def tokenize1([94 | ls], [], res, stream) do
tokenize1(ls, [], ["^" | res], stream)
end
def tokenize1([94 | ls], token, res, stream) do
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(ls, [], ["^", token1 | res], stream)
end
# '....' quote
def tokenize1([39 | ls], [], res, stream) do
{atom, rest} = quote_token(ls, [])
tokenize1(rest, [], [atom | res], stream)
end
def tokenize1([39 | ls], token, res, stream) do
{atom, rest} = quote_token(ls, [])
token1 = token |> Enum.reverse() |> List.to_string()
tokenize1(rest, [], [atom, token1 | res], stream)
end
def tokenize1([l | ls], token, res, stream) do
tokenize1(ls, [l | token], res, stream)
end
defp comment_skip([]) do
[]
end
defp comment_skip([10 | ls]) do
ls
end
defp comment_skip([13 | ls]) do
ls
end
defp comment_skip([_ | ls]) do
comment_skip(ls)
end
defp quote_token([], _) do
Elxlog.error("Error: illegal quote", [])
end
defp quote_token([39 | ls], token) do
atom = token |> Enum.reverse() |> List.to_string()
{atom, ls}
end
defp quote_token([l | ls], token) do
quote_token(ls, [l | token])
end
@doc """
atom + - is separated by tokenizer, thus + - is meaningless
iex>Read.is_integer_str("123")
true
iex>Read.is_integer_str("+123")
true
iex>Read.is_integer_str("-123")
true
"""
def is_integer_str(x) do
cond do
x == "" ->
false
# 123
Enum.all?(x |> String.to_charlist(), fn y -> y >= 48 and y <= 57 end) ->
true
# +123
# +
String.length(x) >= 2 and
x |> String.to_charlist() |> hd == 43 and
Enum.all?(x |> String.to_charlist() |> tl, fn y -> y >= 48 and y <= 57 end) ->
true
# -123
# -
String.length(x) >= 2 and
x |> String.to_charlist() |> hd == 45 and
Enum.all?(x |> String.to_charlist() |> tl, fn y -> y >= 48 and y <= 57 end) ->
true
true ->
false
end
end
@doc """
iex>Read.is_float_str("123")
false
iex>Read.is_float_str("123.45")
true
iex>Read.is_float_str("3.0e1")
true
"""
def is_float_str(x) do
y = String.split(x, ".")
z = String.split(x, "e")
cond do
length(y) == 1 and length(z) == 1 -> false
length(y) == 2 and is_integer_str(hd(y)) and is_integer_str(hd(tl(y))) -> true
length(z) == 2 and is_float_str(hd(z)) and is_integer_str(hd(tl(z))) -> true
true -> false
end
end
end