Allow to pass bindings to RDF.Graph.build blocks

This commit is contained in:
Marcel Otto 2022-06-19 19:55:10 +02:00
parent 65bb0831b8
commit 3f0c6635c1
4 changed files with 125 additions and 12 deletions

View file

@ -37,7 +37,8 @@ The generated namespaces are much more flexible now and compile faster.
import no longer affect the caller context. `alias`es in the caller context are
still available in the build block, but `import`s not and must be reimported in
the build block. Variables in the caller context are also no longer available
build block.
build block, but must be passed explicitly with the new optional `bindings`
argument of `RDF.Graph.build/3`.
- `RDF.Data.merge/2` and `RDF.Data.equal?/2` are now commutative, i.e. structs
which implement the `RDF.Data` protocol can be given also as the second argument
(previously custom structs with `RDF.Data` protocol implementations always

View file

@ -144,8 +144,8 @@ defmodule RDF.Graph do
For a description of the DSL see [this guide](https://rdf-elixir.dev/rdf-ex/description-and-graph-dsl.html).
"""
defmacro build(opts \\ [], do: block) do
Builder.build(block, __CALLER__, Builder.builder_mod(__CALLER__), opts)
defmacro build(bindings \\ [], opts \\ [], do: block) do
Builder.build(block, __CALLER__, Builder.builder_mod(__CALLER__), bindings, opts)
end
@doc """

View file

@ -17,13 +17,15 @@ defmodule RDF.Graph.Builder do
def exclude(_), do: nil
end
def build(do_block, env, opts) do
build(do_block, env, builder_mod(env), opts)
def build(do_block, env, bindings, opts) do
build(do_block, env, builder_mod(env), bindings, opts)
end
def build({:__block__, _, block}, env, builder_mod, opts) do
def build({:__block__, _, block}, env, builder_mod, bindings, opts) do
env_aliases = env_aliases(env)
block = expand_aliased_modules(block, env_aliases)
{bindings_vars, bindings_pattern} = bindings_vars(bindings, builder_mod)
block = bind_vars(block, bindings_vars, builder_mod)
non_strict_ns = extract_non_strict_ns(block)
{declarations, data} = Enum.split_with(block, &declaration?/1)
{base, declarations} = extract_base(declarations)
@ -39,7 +41,7 @@ defmodule RDF.Graph.Builder do
@compile {:no_warn_undefined, mod}
end
def build(opts) do
def build(unquote(bindings_pattern), opts) do
alias RDF.XSD
alias RDF.NS.{RDFS, OWL}
@ -60,12 +62,12 @@ defmodule RDF.Graph.Builder do
Module.create(builder_mod, mod_body, Macro.Env.location(env))
quote do
apply(unquote(builder_mod), :build, [unquote(opts)])
apply(unquote(builder_mod), :build, [Map.new(unquote(bindings)), unquote(opts)])
end
end
def build(single, env, builder_mod, opts) do
build({:__block__, [], List.wrap(single)}, env, builder_mod, opts)
def build(single, env, builder_mod, bindings, opts) do
build({:__block__, [], List.wrap(single)}, env, builder_mod, bindings, opts)
end
@doc false
@ -247,6 +249,19 @@ defmodule RDF.Graph.Builder do
end
end
defp bindings_vars(bindings, mod) do
vars = Enum.map(bindings, fn {key, _} -> key end)
{vars, {:%{}, [], Enum.map(vars, &{&1, Macro.var(&1, mod)})}}
end
defp bind_vars(block, bindings_vars, mod) do
Macro.prewalk(block, fn
{var, line, nil} = expr -> if var in bindings_vars, do: {var, line, mod}, else: expr
other -> other
end)
end
defp env_aliases(env) do
Map.new(env.aliases, fn {short, full} ->
{

View file

@ -495,7 +495,7 @@ defmodule RDF.Graph.BuilderTest do
test "merge with prefixes opt" do
graph =
RDF.Graph.build prefixes: [custom: EX] do
RDF.Graph.build [], prefixes: [custom: EX] do
@prefix custom: TestNS.Custom
Custom.S |> Custom.p(Custom.O)
@ -604,6 +604,103 @@ defmodule RDF.Graph.BuilderTest do
assert graph == RDF.graph(EX.S |> EX.p(EX.O))
end
describe "bindings" do
test "passing a variable from the outer context" do
literal = "foo"
graph =
RDF.Graph.build literal: literal do
EX.S1 |> EX.p1(EX.O1)
EX.S2 |> EX.p2(literal)
end
assert graph ==
RDF.graph([
EX.S1 |> EX.p1(EX.O1),
EX.S2 |> EX.p2("foo")
])
end
test "passing a complex expression from the outer context" do
literal = "foo"
graph =
RDF.Graph.build literal: String.upcase(literal <> "bar") do
EX.S1 |> EX.p1(EX.O1)
EX.S2 |> EX.p2(literal)
end
assert graph ==
RDF.graph([
EX.S1 |> EX.p1(EX.O1),
EX.S2 |> EX.p2("FOOBAR")
])
end
test "passing a graph from a previous build step" do
graph =
(fn ->
graph1 =
RDF.Graph.build do
@prefix ad: "http://example.com/ad-hoc/"
@prefix ex1: "http://example.com/ad-hoc/ex1#"
Ad.S |> Ad.p(Ex1.O)
end
RDF.Graph.build graph: graph1 do
@prefix ad: "http://example.com/ad-hoc/"
@prefix ex2: "http://example.com/ad-hoc/ex2#"
graph
Ad.S |> Ad.p(Ex2.O)
end
end).()
assert graph ==
RDF.graph(
[
{
RDF.iri("http://example.com/ad-hoc/S"),
RDF.iri("http://example.com/ad-hoc/p"),
RDF.iri("http://example.com/ad-hoc/ex1#O")
},
{
RDF.iri("http://example.com/ad-hoc/S"),
RDF.iri("http://example.com/ad-hoc/p"),
RDF.iri("http://example.com/ad-hoc/ex2#O")
}
],
prefixes:
RDF.default_prefixes(
ad: "http://example.com/ad-hoc/",
ex1: "http://example.com/ad-hoc/ex1#",
ex2: "http://example.com/ad-hoc/ex2#"
)
)
end
test "with opts" do
literal = "foo"
graph =
RDF.Graph.build [literal: literal], name: EX.Graph do
EX.S1 |> EX.p1(EX.O1)
EX.S2 |> EX.p2(literal)
end
assert graph ==
RDF.graph(
[
EX.S1 |> EX.p1(EX.O1),
EX.S2 |> EX.p2("foo")
],
name: EX.Graph
)
end
end
test "opts" do
initial = {EX.S, EX.p(), "init"}
@ -615,7 +712,7 @@ defmodule RDF.Graph.BuilderTest do
]
graph =
RDF.Graph.build opts do
RDF.Graph.build [], opts do
EX.S |> EX.p(EX.O)
end