LoginSignup
2
0

More than 5 years have passed since last update.

ElixirでElixirインタプリタを作成していく(第3回 文字列のリストを値として変換していく)

Posted at

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

今から型の推論部分を実装していくがここでは正規表現を用いて実装していく、またListMap等の方を組み合わせた型は省いたものを実装していく

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\""

となっていることがわかるだろうか
そこで最初と最後の"を削除することを考える
そこで次の関数を定義する

Core.utils.ex
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

この関数は文字列を引数に持ちそのheadlastの文字数分を削除する関数となっている
これよりも良い関数がありそうなのでその際は知らせていただけると助かります

また次の事柄に気づくだろうか(ここが一番考えた気がする)
例えば

"\"\""

の場合、おそらくコーダーが意図したものは""という文字列だろう
しかし、実際は異なる実際にはこれは文字列そのままとして認識されるため\"\"となっている
そこで次の関数を使用することとする

Core.utils.ex
  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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0