Add RDF.Literal.Guards

This commit is contained in:
Marcel Otto 2018-09-07 21:42:38 +02:00
parent da48d02977
commit 93b932620c
7 changed files with 114 additions and 29 deletions

View file

@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
- `RDF.LangString.match_language?/2`
- possibility to configure an application-specific default base IRI; for now it
is used only on reading of RDF serializations (when no `base` specified)
- `RDF.Literal.Guards` which allow pattern matching of common literal datatypes
### Changed

View file

@ -8,6 +8,9 @@ defmodule RDF.Numeric do
alias Elixir.Decimal, as: D
import RDF.Literal.Guards
@types MapSet.new [
XSD.integer,
XSD.decimal,
@ -27,9 +30,6 @@ defmodule RDF.Numeric do
XSD.positiveInteger,
]
@xsd_decimal XSD.decimal
@xsd_double XSD.double
@doc """
The list of all numeric datatypes.
@ -68,7 +68,7 @@ defmodule RDF.Numeric do
def equal_value?(%Literal{datatype: left_datatype, value: left},
%Literal{datatype: right_datatype, value: right})
when left_datatype == @xsd_decimal or right_datatype == @xsd_decimal,
when is_xsd_decimal(left_datatype) or is_xsd_decimal(right_datatype),
do: equal_decimal_value?(left, right)
def equal_value?(%Literal{datatype: left_datatype, value: left},
@ -94,8 +94,8 @@ defmodule RDF.Numeric do
defp zero_value?(_), do: false
def negative_zero?(%Literal{value: zero, uncanonical_lexical: "-" <> _, datatype: @xsd_double})
when zero == 0, do: true
def negative_zero?(%Literal{value: zero, uncanonical_lexical: "-" <> _, datatype: datatype})
when zero == 0 and is_xsd_double(datatype), do: true
def negative_zero?(%Literal{value: %D{sign: -1, coef: 0}}), do: true
@ -241,7 +241,7 @@ defmodule RDF.Numeric do
"""
def abs(literal)
def abs(%Literal{datatype: @xsd_decimal} = literal) do
def abs(%Literal{datatype: datatype} = literal) when is_xsd_decimal(datatype) do
if RDF.Decimal.valid?(literal) do
literal.value
|> D.abs()
@ -279,7 +279,7 @@ defmodule RDF.Numeric do
"""
def round(literal, precision \\ 0)
def round(%Literal{datatype: @xsd_decimal} = literal, precision) do
def round(%Literal{datatype: datatype} = literal, precision) when is_xsd_decimal(datatype) do
if RDF.Decimal.valid?(literal) do
literal.value
|> xpath_round(precision)
@ -288,10 +288,11 @@ defmodule RDF.Numeric do
end
end
def round(%Literal{datatype: @xsd_double, value: value} = literal, _)
when value in ~w[nan positive_infinity negative_infinity]a, do: literal
def round(%Literal{datatype: datatype, value: value} = literal, _)
when is_xsd_double(datatype) and value in ~w[nan positive_infinity negative_infinity]a,
do: literal
def round(%Literal{datatype: @xsd_double} = literal, precision) do
def round(%Literal{datatype: datatype} = literal, precision) when is_xsd_double(datatype) do
if RDF.Double.valid?(literal) do
literal.value
|> D.new()
@ -330,7 +331,7 @@ defmodule RDF.Numeric do
"""
def ceil(literal)
def ceil(%Literal{datatype: @xsd_decimal} = literal) do
def ceil(%Literal{datatype: datatype} = literal) when is_xsd_decimal(datatype)do
if RDF.Decimal.valid?(literal) do
literal.value
|> D.round(0, (if literal.value.sign == -1, do: :down, else: :up))
@ -339,10 +340,11 @@ defmodule RDF.Numeric do
end
end
def ceil(%Literal{datatype: @xsd_double, value: value} = literal)
when value in ~w[nan positive_infinity negative_infinity]a, do: literal
def ceil(%Literal{datatype: datatype, value: value} = literal)
when is_xsd_double(datatype) and value in ~w[nan positive_infinity negative_infinity]a,
do: literal
def ceil(%Literal{datatype: @xsd_double} = literal) do
def ceil(%Literal{datatype: datatype} = literal) when is_xsd_double(datatype) do
if RDF.Double.valid?(literal) do
literal.value
|> Float.ceil()
@ -368,7 +370,7 @@ defmodule RDF.Numeric do
"""
def floor(literal)
def floor(%Literal{datatype: @xsd_decimal} = literal) do
def floor(%Literal{datatype: datatype} = literal) when is_xsd_decimal(datatype)do
if RDF.Decimal.valid?(literal) do
literal.value
|> D.round(0, (if literal.value.sign == -1, do: :up, else: :down))
@ -377,10 +379,11 @@ defmodule RDF.Numeric do
end
end
def floor(%Literal{datatype: @xsd_double, value: value} = literal)
when value in ~w[nan positive_infinity negative_infinity]a, do: literal
def floor(%Literal{datatype: datatype, value: value} = literal)
when is_xsd_double(datatype) and value in ~w[nan positive_infinity negative_infinity]a,
do: literal
def floor(%Literal{datatype: @xsd_double} = literal) do
def floor(%Literal{datatype: datatype} = literal) when is_xsd_double(datatype)do
if RDF.Double.valid?(literal) do
literal.value
|> Float.floor()
@ -410,18 +413,21 @@ defmodule RDF.Numeric do
end
defp type_conversion(%Literal{datatype: @xsd_decimal} = arg1,
%Literal{value: arg2}, @xsd_decimal),
defp type_conversion(%Literal{datatype: datatype} = arg1,
%Literal{value: arg2}, datatype) when is_xsd_decimal(datatype),
do: {arg1, RDF.decimal(arg2)}
defp type_conversion(%Literal{value: arg1},
%Literal{datatype: @xsd_decimal} = arg2, @xsd_decimal),
%Literal{datatype: datatype} = arg2, datatype)
when is_xsd_decimal(datatype),
do: {RDF.decimal(arg1), arg2}
defp type_conversion(%Literal{datatype: @xsd_decimal, value: arg1}, arg2, @xsd_double),
defp type_conversion(%Literal{datatype: input_datatype, value: arg1}, arg2, output_datatype)
when is_xsd_decimal(input_datatype) and is_xsd_double(output_datatype),
do: {arg1 |> D.to_float() |> RDF.double(), arg2}
defp type_conversion(arg1, %Literal{datatype: @xsd_decimal, value: arg2}, @xsd_double),
defp type_conversion(arg1, %Literal{datatype: input_datatype, value: arg2}, output_datatype)
when is_xsd_decimal(input_datatype) and is_xsd_double(output_datatype),
do: {arg1, arg2 |> D.to_float() |> RDF.double()}
defp type_conversion(arg1, arg2, _), do: {arg1, arg2}
@ -445,4 +451,5 @@ defmodule RDF.Numeric do
true -> XSD.integer
end
end
end

View file

@ -9,7 +9,7 @@ defmodule RDF.Literal do
alias RDF.Datatype.NS.XSD
# to be able to pattern-match on plain types
# to be able to pattern-match on plain types; we can't use RDF.Literal.Guards here since these aren't compiled here yet
@xsd_string XSD.string
@lang_string RDF.iri("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")
@plain_types [@xsd_string, @lang_string]

77
lib/rdf/literal_guards.ex Normal file
View file

@ -0,0 +1,77 @@
defmodule RDF.Literal.Guards do
@moduledoc """
Guards for working with `RDF.Literal`s.
These are useful for pattern matching on datatypes of literals, since
Elixir doesn't allow function calls in pattern matching clauses, which means
the qualified terms of a `RDF.Vocabulary.Namespace` can't be used.
## Examples
defmodule M do
import RDF.Literal.Guards
def f(%RDF.Literal{datatype: datatype} = literal) when is_xsd_integer(datatype) do
# ...
end
end
"""
alias RDF.Datatype.NS.XSD
@xsd_integer XSD.integer()
@xsd_decimal XSD.decimal()
@xsd_float XSD.float()
@xsd_double XSD.double()
@xsd_string XSD.string()
@xsd_boolean XSD.boolean()
@xsd_dateTime XSD.dateTime()
@xsd_any_uri XSD.anyURI()
@rdf_lang_string RDF.langString
@doc """
Returns `true` if the given datatype is `xsd:integer`; otherwise returns `false`.
"""
defguard is_xsd_integer(datatype) when datatype == @xsd_integer
@doc """
Returns `true` if the given datatype is `xsd:decimal`; otherwise returns `false`.
"""
defguard is_xsd_decimal(datatype) when datatype == @xsd_decimal
@doc """
Returns `true` if the given datatype is `xsd:float`; otherwise returns `false`.
"""
defguard is_xsd_float(datatype) when datatype == @xsd_float
@doc """
Returns `true` if the given datatype is `xsd:double`; otherwise returns `false`.
"""
defguard is_xsd_double(datatype) when datatype == @xsd_double
@doc """
Returns `true` if the given datatype is `xsd:string`; otherwise returns `false`.
"""
defguard is_xsd_string(datatype) when datatype == @xsd_string
@doc """
Returns `true` if the given datatype is `xsd:boolean`; otherwise returns `false`.
"""
defguard is_xsd_boolean(datatype) when datatype == @xsd_boolean
@doc """
Returns `true` if the given datatype is `xsd:dateTime`; otherwise returns `false`.
"""
defguard is_xsd_datetime(datatype) when datatype == @xsd_dateTime
@doc """
Returns `true` if the given datatype is `xsd:anyURI`; otherwise returns `false`.
"""
defguard is_xsd_any_uri(datatype) when datatype == @xsd_any_uri
@doc """
Returns `true` if the given datatype is `rdf:langString`; otherwise returns `false`.
"""
defguard is_rdf_lang_string(datatype) when datatype == @rdf_lang_string
end

View file

@ -28,6 +28,8 @@ defmodule RDF.Serialization.Encoder do
quote bind_quoted: [], unquote: true do
@behaviour unquote(__MODULE__)
import RDF.Literal.Guards
def encode!(data, opts \\ []) do
case encode(data, opts) do
{:ok, data} -> data

View file

@ -5,7 +5,6 @@ defmodule RDF.NTriples.Encoder do
alias RDF.{IRI, Literal, BlankNode}
@xsd_string RDF.Datatype.NS.XSD.string
def encode(data, _opts \\ []) do
result =
@ -34,7 +33,7 @@ defmodule RDF.NTriples.Encoder do
~s["#{value}"@#{language}]
end
def term(%Literal{datatype: @xsd_string} = literal) do
def term(%Literal{datatype: datatype} = literal) when is_xsd_string(datatype) do
~s["#{Literal.lexical(literal)}"]
end

View file

@ -9,7 +9,6 @@ defmodule RDF.Turtle.Encoder do
@indentation_char " "
@indentation 4
@xsd_string RDF.Datatype.NS.XSD.string
@native_supported_datatypes [
RDF.Datatype.NS.XSD.boolean,
RDF.Datatype.NS.XSD.integer,
@ -279,7 +278,7 @@ defmodule RDF.Turtle.Encoder do
defp term(%Literal{value: value, language: language}, _,_ , _) when not is_nil(language),
do: ~s["#{value}"@#{language}]
defp term(%Literal{datatype: @xsd_string} = literal, _, _,_),
defp term(%Literal{datatype: datatype} = literal, _, _,_) when is_xsd_string(datatype),
do: literal |> Literal.lexical |> quoted()
defp term(%Literal{datatype: datatype} = literal, state, _, nesting)