Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
arnodirlam committed Nov 10, 2024
1 parent d599f33 commit b4f8d90
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 56 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,6 @@ All syntax except `for` and `with` is supported in `defd` and (private) `defdp`
- All `Kernel` functions **except**:
- `apply/2`, `apply/3`
- `spawn/1`, `spawn_link/1`, `spawn_monitor/1`
- `tap/2`
- `then/2`

### Translatable to database queries

Expand Down
8 changes: 2 additions & 6 deletions lib/dx/date_time.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
defmodule Dx.DateTime do
@moduledoc false

def __scopable?(_fun_name, _arity), do: false
def __scopable_args(_fun_name, _arity), do: []
def __fn_args(_fun_name, _arity), do: []
def __warn_not_ok(_fun_name, _arity, _args), do: nil
def __warn_always(_fun_name, _arity), do: nil
use Dx.Defd.Ext

import Dx.Defd.Ext
def __kernel?(_fun_name, _arity), do: true

defscope after?(left, right, generate_fallback) do
quote do: {:gt, unquote(left), unquote(right), unquote(generate_fallback.())}
Expand Down
4 changes: 3 additions & 1 deletion lib/dx/defd/ast/loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ defmodule Dx.Defd.Ast.Loader do
{assigns, loaders} = Enum.split_with(loaders, &match?(%{ast: {:ok, _}}, &1))

assigns_ast =
Enum.map(assigns, fn %{ast: {:ok, right}} = loader ->
assigns
|> :lists.reverse()
|> Enum.map(fn %{ast: {:ok, right}} = loader ->
{:ok, {:=, [], [loader.data_var, right]}}
end)

Expand Down
14 changes: 7 additions & 7 deletions lib/dx/defd/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ defmodule Dx.Defd.Compiler do
{parts, state} =
Enum.map_reduce(parts, state, fn
{:"::", meta, [ast, {:binary, binary_meta, context}]}, state ->
{ast, state} = normalize_load_unwrap(ast, state)
{ast, state} = normalize_load_unwrap(ast, state) |> Ast.load_scopes()

{:"::", meta, [ast, {:binary, binary_meta, context}]}
|> with_state(state)
Expand Down Expand Up @@ -793,34 +793,34 @@ defmodule Dx.Defd.Compiler do
""")
end

def maybe_load_scope({:ok, module}, state) when is_atom(module) do
def maybe_load_scope({:ok, module}, true, state) when is_atom(module) do
quote do
Dx.Scope.lookup(Dx.Scope.all(unquote(module)), unquote(state.eval_var))
end
|> Loader.add(state)
end

def maybe_load_scope({:ok, var}, state) when is_var(var) do
def maybe_load_scope({:ok, var}, _convert_atoms_to_scopes?, state) when is_var(var) do
quote do
Dx.Scope.maybe_lookup(unquote(var), unquote(state.eval_var))
end
|> Loader.add(state)
end

def maybe_load_scope({:ok, {:%{}, _meta, [{:__struct__, Dx.Scope} | _]} = ast}, state) do
def maybe_load_scope({:ok, {:%{}, _meta, [{:__struct__, Dx.Scope} | _]} = ast}, _, state) do
quote do
Dx.Scope.lookup(unquote(ast), unquote(state.eval_var))
end
|> Loader.add(state)
end

def maybe_load_scope({:ok, ast}, state) do
def maybe_load_scope({:ok, ast}, _, state) do
{{:ok, ast}, state}
end

# for undefined variables
def maybe_load_scope(other, state) do
{other, state}
def maybe_load_scope(other, _, state) do
{{:ok, other}, state}
end

def add_scope_loader_for({:ok, ast}, state) do
Expand Down
20 changes: 20 additions & 0 deletions lib/dx/defd/ext.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
defmodule Dx.Defd.Ext do
@moduledoc false

defmacro __using__(_opts) do
quote do
import Dx.Defd.Ext

def __scopable?(_fun_name, _arity), do: false
def __scopable_args(_fun_name, _arity), do: []
def __convert_atoms_to_scopes?(_fun_name, _arity), do: false
def __fn_args(_fun_name, _arity), do: []
def __warn_not_ok(_fun_name, _arity, _args), do: nil
def __warn_always(_fun_name, _arity), do: nil

defoverridable __scopable?: 2,
__scopable_args: 2,
__convert_atoms_to_scopes?: 2,
__fn_args: 2,
__warn_not_ok: 3,
__warn_always: 2
end
end

alias Dx.Defd.Util

defmacro defscope(call) do
Expand Down
22 changes: 15 additions & 7 deletions lib/dx/defd/kernel.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
defmodule Dx.Defd.Kernel do
@moduledoc false

def __scopable?(_fun_name, _arity), do: false
def __scopable_args(_fun_name, _arity), do: []
def __fn_args(_fun_name, _arity), do: []
def __warn_not_ok(_fun_name, _arity, _args), do: nil
def __warn_always(_fun_name, _arity), do: nil
use Dx.Defd.Ext

import Kernel, except: [==: 2]
import Dx.Defd.Ext
def __scopable_args(_fun_name, arity), do: 0..(arity - 1)
def __kernel?(_fun_name, _arity), do: true

defscope unquote(:==)({:error, _left}, _right, generate_fallback) do
{:error, generate_fallback.()}
Expand All @@ -24,6 +20,14 @@ defmodule Dx.Defd.Kernel do
end
end

def apply(%Dx.Defd.Fn{fun: fun}, args) do
Kernel.apply(fun, args)
end

def apply(fun, args) do
Kernel.apply(fun, args)
end

defscope unquote(:not)(term, _generate_fallback) do
{:not, term}
end
Expand All @@ -36,6 +40,10 @@ defmodule Dx.Defd.Kernel do
:erlang.is_function(term)
end

def is_function(%Dx.Defd.Fn{fun: fun}, arity) do
:erlang.is_function(fun, arity)
end

def is_function(term, arity) do
:erlang.is_function(term, arity)
end
Expand Down
99 changes: 68 additions & 31 deletions lib/dx/defd/rewriter.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Dx.Defd.Rewriter do
@moduledoc false

alias Dx.Defd.Ast
alias Dx.Defd.Ast.Loader
alias Dx.Defd.Compiler
Expand Down Expand Up @@ -47,45 +49,77 @@ defmodule Dx.Defd.Rewriter do
maybe_warn_static(rewriter, meta, fun_name, arity, state)

cond do
Util.is_scopable?(rewriter, fun_name, arity) ->
maybe_warn(rewriter, meta, fun_name, arity, args, state)
Util.kernel?(rewriter, fun_name, arity) ->
dbg({:kernel, rewriter, fun_name, arity})
{args, state} = Enum.map_reduce(orig_args, state, &Compiler.normalize_load_unwrap/2)
{args, state} = Compiler.finalize_args(args, state)

ast =
cond do
Enum.all?(args, &Ast.ok?/1) ->
args = Enum.map(args, &Ast.unwrap_inner/1)

quote do
unquote({:ok, {{:., meta, [:erlang, fun_name]}, meta2, args}})
end

function_exported?(:erlang, fun_name, arity) ->
Compiler.compile_error!(meta, state, """
#{fun_name}/#{arity} is not supported by Dx yet.
Please check the issues in the repo, upvote, comment, or create an issue for it.
""")

true ->
{:ok, orig}
end

args = Enum.map(args, &Ast.unwrap/1)
{ast, state}

{{:., meta, [rewriter, fun_name]}, meta2, args}
|> Loader.add(state)
true ->
dbg({:NOkernel, rewriter, fun_name, arity})

args_ok?(args, Util.fn_args(rewriter, fun_name, arity)) ->
{args, state} =
maybe_preload_scopes(args, Util.scopable_args(rewriter, fun_name, arity), state)
cond do
Util.is_scopable?(rewriter, fun_name, arity) ->
maybe_warn(rewriter, meta, fun_name, arity, args, state)

args = Enum.map(args, &Ast.unwrap_final_args_inner/1)
args = Enum.map(args, &Ast.unwrap/1)

{:ok, {{:., meta, [module, fun_name]}, meta2, args}}
|> Ast.with_state(state)
{{:., meta, [rewriter, fun_name]}, meta2, args}
|> Loader.add(state)

function_exported?(rewriter, fun_name, arity) ->
maybe_warn(rewriter, meta, fun_name, arity, args, state)
args_ok?(args, Util.fn_args(rewriter, fun_name, arity)) ->
{args, state} = maybe_preload_scopes(rewriter, fun_name, arity, args, state)
args = Enum.map(args, &Ast.unwrap_final_args_inner/1)

{args, state} =
maybe_preload_scopes(args, Util.scopable_args(rewriter, fun_name, arity), state)
{:ok, {{:., meta, [module, fun_name]}, meta2, args}}
|> Ast.with_state(state)

args = Enum.map(args, &Ast.unwrap_maybe_fn/1)
function_exported?(rewriter, fun_name, arity) ->
maybe_warn(rewriter, meta, fun_name, arity, args, state)

{{:., meta, [rewriter, fun_name]}, meta2, args}
|> Loader.add(state)
{args, state} = maybe_preload_scopes(rewriter, fun_name, arity, args, state)
args = Enum.map(args, &Ast.unwrap_maybe_fn/1)

function_exported?(module, fun_name, arity) ->
Compiler.compile_error!(meta, state, """
#{fun_name}/#{arity} is not supported by Dx yet.
{{:., meta, [rewriter, fun_name]}, meta2, args}
|> Loader.add(state)

Please check the issues in the repo, upvote, comment, or create an issue for it.
""")
# function_exported?(module, fun_name, arity) ->
# Compiler.compile_error!(meta, state, """
# #{fun_name}/#{arity} is not supported by Dx yet.

# unknown function
true ->
{:ok, orig}
|> Ast.with_state(state)
# Please check the issues in the repo, upvote, comment, or create an issue for it.
# """)

# unknown function
true ->
maybe_warn(rewriter, meta, fun_name, arity, args, state)

{args, state} = maybe_preload_scopes(rewriter, fun_name, arity, args, state)
args = Enum.map(args, &Ast.unwrap_maybe_fn/1)

{:ok, {{:., meta, [module, fun_name]}, meta2, args}}
|> Ast.with_state(state)
end
end
end

Expand All @@ -97,26 +131,29 @@ defmodule Dx.Defd.Rewriter do
end)
end

defp maybe_preload_scopes(args, scopable_arg_indexes, state) do
defp maybe_preload_scopes(rewriter, fun_name, arity, args, state) do
scopable_arg_indexes = Util.scopable_args(rewriter, fun_name, arity)
convert_atoms_to_scopes? = Util.convert_atoms_to_scopes?(rewriter, fun_name, arity)

args
|> Enum.with_index()
|> Enum.map_reduce(state, fn {arg, i}, state ->
if i in scopable_arg_indexes do
Compiler.maybe_load_scope(arg, state)
Compiler.maybe_load_scope(arg, convert_atoms_to_scopes?, state)
else
{arg, state}
end
end)
end

defp maybe_warn(rewriter, meta, fun_name, arity, args, state) do
if warning = rewriter.__warn_not_ok(fun_name, arity, args) do
if warning = Util.warn_not_ok(rewriter, fun_name, arity, args) do
Compiler.warn(meta, state, warning)
end
end

defp maybe_warn_static(rewriter, meta, fun_name, arity, state) do
if warning = rewriter.__warn_always(fun_name, arity) do
if warning = Util.warn_always(rewriter, fun_name, arity) do
Compiler.warn(meta, state, warning)
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/dx/defd/scope.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ defmodule Dx.Scope do
%__MODULE__{type: module, plan: {:queryable, module}}
end

# def maybe_atom(reserved) when reserved in [nil, true, false], do: reserved
def maybe_atom(atom) when is_atom(atom), do: all(atom)
def maybe_atom(other), do: other

Expand Down
32 changes: 32 additions & 0 deletions lib/dx/defd/util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ defmodule Dx.Defd.Util do
function_exported?(module, :__scopable?, 2) and module.__scopable?(fun_name, arity)
end

def kernel?(module, fun_name, arity) do
Code.ensure_loaded(module)
function_exported?(module, :__kernel?, 2) and module.__kernel?(fun_name, arity)
end

def scopable_args(module, fun_name, arity) do
Code.ensure_loaded(module)

Expand All @@ -28,6 +33,13 @@ defmodule Dx.Defd.Util do
end
end

def convert_atoms_to_scopes?(module, fun_name, arity) do
Code.ensure_loaded(module)

function_exported?(module, :__convert_atoms_to_scopes?, 2) and
module.__convert_atoms_to_scopes?(fun_name, arity)
end

def fn_args(module, fun_name, arity) do
Code.ensure_loaded(module)

Expand All @@ -38,6 +50,26 @@ defmodule Dx.Defd.Util do
end
end

def warn_always(module, fun_name, arity) do
Code.ensure_loaded(module)

if function_exported?(module, :__warn_always, 2) do
module.__warn_always(fun_name, arity)
else
nil
end
end

def warn_not_ok(module, fun_name, arity, args) do
Code.ensure_loaded(module)

if function_exported?(module, :__warn_not_ok, 3) do
module.__warn_not_ok(fun_name, arity, args)
else
nil
end
end

def is_defd?(module, fun_name, arity) do
Code.ensure_loaded(module)

Expand Down
6 changes: 4 additions & 2 deletions lib/dx/enum.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
defmodule Dx.Enum do
@moduledoc false

use Dx.Defd.Ext

alias Dx.Defd.Ast
alias Dx.Defd.Result

import Dx.Defd.Ext

@chunk_while_chunk_fun_warning """
Dx can't load data efficiently within functions passed as chunk_fun to Enum.chunk_while
Expand Down Expand Up @@ -242,6 +242,8 @@ defmodule Dx.Enum do
def __scopable_args(:zip_with, 3), do: [0, 1]
def __scopable_args(_fun_name, _arity), do: [0]

def __convert_atoms_to_scopes?(_fun_name, _arity), do: true

def __fn_args(:chunk_while, 4), do: [2, 3]
def __fn_args(:count_until, 3), do: [1]
def __fn_args(:filter_map, 3), do: [1, 2]
Expand Down
Loading

0 comments on commit b4f8d90

Please sign in to comment.