Packages
vtc
0.13.2
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.2
0.16.1
0.16.0
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.10
0.10.9
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A SMPTE timecode library for Elixir
Current section
Files
Jump to
Current section
Files
lib/framestamp_eval.ex
defmodule Vtc.Framestamp.Eval do
@moduledoc false
alias Vtc.Framerate
alias Vtc.Framestamp
alias Vtc.Source.Frames
@self_alias [:Vtc, :Framestamp, :Eval]
# Escapes variable that the default rate for the eval will be kept in,
@default_rate_var_ast Macro.var(:default_rate, :vtc_eval)
@spec eval([at: Framerate.t() | number() | Ratio.t(), ntsc: Framerate.ntsc()], Macro.input()) :: Macro.t()
def eval(opts, body) do
body =
case body do
[do: body] -> body
body -> body
end
default_rate = Keyword.get(opts, :at, nil)
ntsc = Keyword.take(opts, [:ntsc])
setup_default = aliased_func_call(@self_alias, :setup_rate, [], [default_rate, ntsc])
set_default_var = {:=, [], [@default_rate_var_ast, setup_default]}
body = Macro.postwalk(body, &replace_ops_ast/1)
quote do
with do
unquote(set_default_var)
unquote(body)
end
end
end
@two_stamp_ops %{
{:+, 2} => :add,
{:-, 2} => :sub,
{:==, 2} => :eq?,
{:<, 2} => :lt?,
{:<=, 2} => :lte?,
{:>, 2} => :gt?,
{:>=, 2} => :gte?,
{:*, 2} => :mult,
{:/, 2} => :div,
{:%, 2} => :rem,
{:div, 2} => :div,
{:divrem, 2} => :divrem,
{:-, 1} => :minus,
{:abs, 1} => :abs
}
@one_stamp_ops [
{:*, 2},
{:/, 2},
{:%, 2},
{:div, 2},
{:divrem, 2},
{:-, 1},
{:abs, 1}
]
# Replaces operators with calls to a `Framestamp` function.
@spec replace_ops_ast(Macro.t()) :: Macro.t()
defp replace_ops_ast({op, _, args} = ast) when is_map_key(@two_stamp_ops, {op, length(args)}) do
stamp_func_name = Map.fetch!(@two_stamp_ops, {op, length(args)})
stamp_arg_count = if {op, length(args)} in @one_stamp_ops, do: 1, else: 2
replace_framestamp_op(stamp_func_name, stamp_arg_count, ast)
end
defp replace_ops_ast(ast), do: ast
# Replaces ops where both args are framestamps.
@spec replace_framestamp_op(atom(), pos_integer(), Macro.t()) :: Macro.t()
defp replace_framestamp_op(stamp_func_name, stamp_arg_count, ast) do
{_, meta, args} = ast
args
|> Enum.with_index()
|> Enum.map(fn
{arg_ast, i} when i < stamp_arg_count -> wrap_stamp_val_in_cast(arg_ast, meta)
{arg_ast, _} -> arg_ast
end)
|> then(&framestamp_func(stamp_func_name, meta, &1))
end
# Wraps framestamp args in `cast_framestamp_arg/2`.
@spec wrap_stamp_val_in_cast(Macro.t(), Macro.metadata()) :: Macro.t()
def wrap_stamp_val_in_cast({:num, _, [arg]}, _), do: arg
def wrap_stamp_val_in_cast(arg_ast, meta),
do: aliased_func_call([:Vtc, :Framestamp, :Eval], :cast_framestamp_arg, meta, [arg_ast, @default_rate_var_ast])
# Casts `frames` to `default` rate if one was provided, and `frames` is not already
# a framestamp value.
@doc false
@spec cast_framestamp_arg(Framestamp.t() | Frames.t(), Framerate.t() | nil) :: Framestamp.t() | Frames.t()
def cast_framestamp_arg(%Framestamp{} = framestamp, _), do: framestamp
def cast_framestamp_arg(frames, nil), do: frames
def cast_framestamp_arg(frames, default_rate) do
if Frames.impl_for(frames) do
Framestamp.with_frames!(frames, default_rate)
else
frames
end
end
# Inserted into the head of each eval block. Sets up the default framerate variable.
@spec setup_rate(Framerate.t() | number() | Ratio.t() | nil, ntsc: Framerate.ntsc()) :: Framerate.t() | nil
def setup_rate(nil, _), do: nil
def setup_rate(%Framerate{} = rate, _), do: rate
def setup_rate(rate, opts) do
opts = if is_float(rate), do: Keyword.put_new(opts, :coerce_ntsc?, true), else: opts
Framerate.new!(rate, opts)
end
# Escapes a framestamp function name for use in an ast.
@spec framestamp_func(atom(), Macro.metadata(), [Macro.t()]) :: Macro.t()
defp framestamp_func(name, meta, args), do: aliased_func_call([:Vtc, :Framestamp], name, meta, args)
# Constructs the AST for an alias + function call.
@spec aliased_func_call([atom()], atom(), Macro.metadata(), [Macro.t()]) :: Macro.t()
defp aliased_func_call(aliases, func_name, meta, args) do
func_call = {:., meta, [{:__aliases__, meta, aliases}, func_name]}
{func_call, meta, args}
end
end