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 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 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 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 - `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 which implement the `RDF.Data` protocol can be given also as the second argument
(previously custom structs with `RDF.Data` protocol implementations always (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). 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 defmacro build(bindings \\ [], opts \\ [], do: block) do
Builder.build(block, __CALLER__, Builder.builder_mod(__CALLER__), opts) Builder.build(block, __CALLER__, Builder.builder_mod(__CALLER__), bindings, opts)
end end
@doc """ @doc """

View file

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

View file

@ -495,7 +495,7 @@ defmodule RDF.Graph.BuilderTest do
test "merge with prefixes opt" do test "merge with prefixes opt" do
graph = graph =
RDF.Graph.build prefixes: [custom: EX] do RDF.Graph.build [], prefixes: [custom: EX] do
@prefix custom: TestNS.Custom @prefix custom: TestNS.Custom
Custom.S |> Custom.p(Custom.O) 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)) assert graph == RDF.graph(EX.S |> EX.p(EX.O))
end 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 test "opts" do
initial = {EX.S, EX.p(), "init"} initial = {EX.S, EX.p(), "init"}
@ -615,7 +712,7 @@ defmodule RDF.Graph.BuilderTest do
] ]
graph = graph =
RDF.Graph.build opts do RDF.Graph.build [], opts do
EX.S |> EX.p(EX.O) EX.S |> EX.p(EX.O)
end end