defmodule BitcoinLib.Script.Parser do @moduledoc """ Converts scripts into opcode lists """ alias BitcoinLib.Script.{OpcodeManager} alias BitcoinLib.Script.Opcodes.Data @doc """ Takes a binary script and converts it into an opcode list ## Examples iex> <<0x76a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac::200>> ...> |> BitcoinLib.Script.Parser.parse { :ok, [ %BitcoinLib.Script.Opcodes.Stack.Dup{}, %BitcoinLib.Script.Opcodes.Crypto.Hash160{}, %BitcoinLib.Script.Opcodes.Data{value: <<0xcbc20a7664f2f69e5355aa427045bc15e7c6c772::160>>}, %BitcoinLib.Script.Opcodes.BitwiseLogic.EqualVerify{}, %BitcoinLib.Script.Opcodes.Crypto.CheckSig{script: <<0x76a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac::200>>} ] } """ @spec parse(bitstring()) :: {:ok, list()} | {:error, binary()} def parse(script) do iterate(%{script: script, opcodes: [], whole_script: script}) end defp iterate(%{error: message}), do: {:error, message} defp iterate(%{script: <<>>, opcodes: opcodes}), do: {:ok, Enum.reverse(opcodes)} defp iterate(%{script: script, opcodes: opcodes, whole_script: whole_script}) do %{script: script, opcodes: opcodes, whole_script: whole_script} |> extract_next_opcode() |> iterate() end defp extract_next_opcode(%{script: script, opcodes: opcodes, whole_script: whole_script} = map) do case OpcodeManager.extract_from_script(script, whole_script) do {:empty_script} -> map {:error, message, remaining} -> map |> Map.put(:error, message) |> Map.put(:script, remaining) {:opcode, opcode, remaining} -> %{map | script: remaining, opcodes: [opcode | opcodes]} {:data, data, remaining} -> opcode = %Data{value: data} %{map | script: remaining, opcodes: [opcode | opcodes]} end end end