ElixirでElixirインタプリタを作成していく
第3回 文字列のリストを値として変換していく
前回、一つの文字列として与えられた引数を引数ごとに文字列のリストとして返す関数を作成した
たとえば
"1, 2.0, \"hello\"" |> function
# ["1", "2.0", "\"hello\""]
と分割される
これを
[ 1, 2.0, "hello"]
と変換したい
定義
関数名:arg_convertion
引数:[String]
返り値:List
実装
個人的に本来はガード節を用いて関数を分けることを行いたかったが
ガード節の仕様上無理だということがわかったため今回はcond
を用いて実装する
はじめに関数の宣言部を以下に示す
def arg_convertion(list = [head | tail]) do
end
今から型の推論部分を実装していくがここでは正規表現を用いて実装していく、またList
やMap
等の方を組み合わせた型は省いたものを実装していく
Integer
整数型であるIntegerは正義表現を用いて次のように判別が可能だ
Regex.match?(~r/\d/, head)
この条件がマッチしたときの文字列をString.to_integer
関数を用いてintegerに変換する
Regex.match?(~r/\d/, head) ->
num =
head
|> String.to_integer()
[num | arg_convertion(tail)]
これを同じようにすればFloatでも可能そうだ
Float
Floatは小数が表現可能な型なので小数点を目印にすれば良さそうである
Regex.match?(~r/\d\.\d/, head) ->
num =
head
|> String.to_float()
[num | arg_convertion(tail)]
ただし、このコードを用いる際は注意が必要である
それはこのコードをIntegerよりも前に実装しては行けない点である
Regex.match?
はマッチする部分がある時点でtrue
を返すので
Integerにマッチしてしまうからである
String
Regex.match?(~r/\".*\"/, head)
これを用いることで認識可能である
この時点でhead
には
"\"string\""
となっていることがわかるだろうか
そこで最初と最後の"
を削除することを考える
そこで次の関数を定義する
defmodule ElixirInterpreter.Core.Utils do
...
def remove_head_last(str, head, last) do
len = String.length(str)
str
|> String.slice(head, len)
|> String.reverse()
|> String.slice(last, len)
|> String.reverse()
end
end
この関数は文字列を引数に持ちそのhead
とlast
の文字数分を削除する関数となっている
これよりも良い関数がありそうなのでその際は知らせていただけると助かります
また次の事柄に気づくだろうか(ここが一番考えた気がする)
例えば
"\"\""
の場合、おそらくコーダーが意図したものは""
という文字列だろう
しかし、実際は異なる実際にはこれは文字列そのままとして認識されるため\"\"
となっている
そこで次の関数を使用することとする
def input_convertion(str) when is_binary(str), do: str |> String.replace("\\", "")
(この関数は文字として存在するエスケープ文字を認識させるだけなので本来の意味のエスケープ文字を認識させることはできません)
この2つを仕様して文字列認識は次のように記述できる
Regex.match?(~r/\".*\"/, head) ->
str =
head
|> Core.Utils.remove_head_last(1, 1)
|> Core.Utils.input_convertion()
[str | arg_convertion(tail)]
nil
型ではないがこれは例外としてこの時点で実装していく
Regex.match?(~r/nil/, head) ->
[nil | arg_convertion(tail)]
Atom
申し訳ないこればかりはコードを見ていただく他ない
Atomは:atom
と:"atom"
のような2つの場合がある
このときの分割は
":atom" |> [":atom"]
":\"atom\"" |> [":" , "atom"]
となってしまいコードが汚くなってしまっている
またここでは正規表現を用いず最初の文字が:
であることを条件としている
それはキーワードリスト等と混同しないようにするためである
String.first(head) == ":" ->
[ head | tail] = case String.length(head) do
1 ->
[head | tail] = tail
head = head |> Core.Utils.remove_head_last(1, 1)
[head | tail]
_ ->
head = head |> Core.Utils.remove_head_last(1, 0)
[ head | tail]
end
atom = head |> Core.Utils.input_convertion |> String.to_atom()
[ atom | Core.arg_convertion(tail)]
最後がゴリ押しの終わり方になったのは許してほしい
分割を見直す等してさらなる改善を目標にしていく
次回はリスト等のデータ構造による型を実装していく
第0回 目次
第2回 基本的な引数の分割
第4回 データ構造による型を実装する(作成次第リンク)
以下ソースコード(マスターでゴリゴリ開発してるので悪しからず.)
GitHub: ElixirInterpreter