Add implementation of RDF.Datatype.cast/1 on numeric datatypes

This commit is contained in:
Marcel Otto 2018-09-09 15:28:35 +02:00
parent 1e94842bc0
commit 56c153198f
6 changed files with 255 additions and 0 deletions

View file

@ -4,6 +4,8 @@ defmodule RDF.Decimal do
"""
use RDF.Datatype, id: RDF.Datatype.NS.XSD.decimal
import RDF.Literal.Guards
alias Elixir.Decimal, as: D
@ -64,6 +66,36 @@ defmodule RDF.Decimal do
do: canonical_decimal(%{decimal | coef: Kernel.div(coef, 10), exp: exp + 1})
def cast(%RDF.Literal{datatype: datatype} = literal) do
cond do
not RDF.Literal.valid?(literal) ->
nil
is_xsd_decimal(datatype) ->
literal
literal == RDF.false ->
new(0.0)
literal == RDF.true ->
new(1.0)
is_xsd_string(datatype) ->
literal.value
|> new()
|> canonical()
is_number(literal.value) and (is_xsd_integer(datatype) or
is_xsd_double(datatype) or is_xsd_float(datatype)) ->
new(literal.value)
true ->
nil
end
end
def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right)
end

View file

@ -5,6 +5,8 @@ defmodule RDF.Double do
use RDF.Datatype, id: RDF.Datatype.NS.XSD.double
import RDF.Literal.Guards
def build_literal_by_value(value, opts) do
case convert(value, opts) do
@ -91,6 +93,41 @@ defmodule RDF.Double do
end
end
def cast(%RDF.Literal{datatype: datatype} = literal) do
cond do
not RDF.Literal.valid?(literal) ->
nil
is_xsd_double(datatype) ->
literal
literal == RDF.false ->
new(0.0)
literal == RDF.true ->
new(1.0)
is_xsd_string(datatype) ->
literal.value
|> new()
|> canonical()
is_xsd_decimal(datatype) ->
literal.value
|> Decimal.to_float()
|> new()
is_xsd_integer(datatype) or is_xsd_float(datatype) ->
new(literal.value)
true ->
nil
end
end
def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right)
end

View file

@ -5,6 +5,8 @@ defmodule RDF.Integer do
use RDF.Datatype, id: RDF.Datatype.NS.XSD.integer
import RDF.Literal.Guards
def convert(value, _) when is_integer(value), do: value
@ -18,6 +20,44 @@ defmodule RDF.Integer do
def convert(value, opts), do: super(value, opts)
def cast(%RDF.Literal{datatype: datatype} = literal) do
cond do
not RDF.Literal.valid?(literal) ->
nil
is_xsd_integer(datatype) ->
literal
literal == RDF.false ->
new(0)
literal == RDF.true ->
new(1)
is_xsd_string(datatype) ->
literal.value
|> new()
|> canonical()
is_xsd_decimal(datatype) ->
literal.value
|> Decimal.round(0, :down)
|> Decimal.to_integer()
|> new()
is_float(literal.value) and
(is_xsd_double(datatype) or is_xsd_float(datatype)) ->
literal.value
|> trunc()
|> new()
true ->
nil
end
end
def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right)
end

View file

@ -65,6 +65,57 @@ defmodule RDF.DecimalTest do
end
describe "cast/1" do
test "casting a decimal returns the input as it is" do
assert RDF.decimal(0) |> RDF.Decimal.cast() == RDF.decimal(0)
assert RDF.decimal("-0.0") |> RDF.Decimal.cast() == RDF.decimal("-0.0")
assert RDF.decimal(1) |> RDF.Decimal.cast() == RDF.decimal(1)
assert RDF.decimal(0.1) |> RDF.Decimal.cast() == RDF.decimal(0.1)
end
test "casting a boolean" do
assert RDF.true |> RDF.Decimal.cast() == RDF.decimal(1.0)
assert RDF.false |> RDF.Decimal.cast() == RDF.decimal(0.0)
end
test "casting a string" do
assert RDF.string("0") |> RDF.Decimal.cast() == RDF.decimal(0)
assert RDF.string("3.14") |> RDF.Decimal.cast() == RDF.decimal(3.14)
end
test "casting an integer" do
assert RDF.integer(0) |> RDF.Decimal.cast() == RDF.decimal(0.0)
assert RDF.integer(42) |> RDF.Decimal.cast() == RDF.decimal(42.0)
end
test "casting a double" do
assert RDF.double(0.0) |> RDF.Decimal.cast() == RDF.decimal(0.0)
assert RDF.double("-0.0") |> RDF.Decimal.cast() == RDF.decimal(0.0)
assert RDF.double(0.1) |> RDF.Decimal.cast() == RDF.decimal(0.1)
assert RDF.double(1) |> RDF.Decimal.cast() == RDF.decimal(1.0)
assert RDF.double(3.14) |> RDF.Decimal.cast() == RDF.decimal(3.14)
assert RDF.double(10.1e1) |> RDF.Decimal.cast() == RDF.decimal(101.0)
assert RDF.double("NAN") |> RDF.Decimal.cast() == nil
assert RDF.double("+INF") |> RDF.Decimal.cast() == nil
end
@tag skip: "TODO: RDF.Float datatype"
test "casting a float"
test "with invalid literals" do
assert RDF.boolean("42") |> RDF.Decimal.cast() == nil
assert RDF.integer(3.14) |> RDF.Decimal.cast() == nil
assert RDF.decimal("NAN") |> RDF.Decimal.cast() == nil
assert RDF.double(true) |> RDF.Decimal.cast() == nil
end
test "with literals of unsupported datatypes" do
assert RDF.DateTime.now() |> RDF.Decimal.cast() == nil
end
end
defmacrop sigil_d(str, _opts) do
quote do
Elixir.Decimal.new(unquote(str))

View file

@ -58,4 +58,48 @@ defmodule RDF.DoubleTest do
end
end
describe "cast/1" do
test "casting a double returns the input as it is" do
assert RDF.double(3.14) |> RDF.Double.cast() == RDF.double(3.14)
assert RDF.double("NAN") |> RDF.Double.cast() == RDF.double("NAN")
assert RDF.double("+INF") |> RDF.Double.cast() == RDF.double("+INF")
end
test "casting a boolean" do
assert RDF.true |> RDF.Double.cast() == RDF.double(1.0)
assert RDF.false |> RDF.Double.cast() == RDF.double(0.0)
end
test "casting a string" do
assert RDF.string("1.0") |> RDF.Double.cast() == RDF.double("1.0E0")
assert RDF.string("3.14") |> RDF.Double.cast() == RDF.double("3.14E0")
end
test "casting an integer" do
assert RDF.integer(0) |> RDF.Double.cast() == RDF.double(0.0)
assert RDF.integer(42) |> RDF.Double.cast() == RDF.double(42.0)
end
test "casting a decimal" do
assert RDF.decimal(0) |> RDF.Double.cast() == RDF.double(0)
assert RDF.decimal(1) |> RDF.Double.cast() == RDF.double(1)
assert RDF.decimal(3.14) |> RDF.Double.cast() == RDF.double(3.14)
end
@tag skip: "TODO: RDF.Float datatype"
test "casting a float"
test "with invalid literals" do
assert RDF.boolean("42") |> RDF.Double.cast() == nil
assert RDF.integer(3.14) |> RDF.Double.cast() == nil
assert RDF.decimal("NAN") |> RDF.Double.cast() == nil
assert RDF.double(true) |> RDF.Double.cast() == nil
end
test "with literals of unsupported datatypes" do
assert RDF.DateTime.now() |> RDF.Double.cast() == nil
end
end
end

View file

@ -16,6 +16,57 @@ defmodule RDF.IntegerTest do
invalid: ~w(foo 10.1 12xyz) ++ [true, false, 3.14, "1 2", "foo 1", "1 foo"]
describe "cast/1" do
test "casting an integer returns the input as it is" do
assert RDF.integer(0) |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.integer(1) |> RDF.Integer.cast() == RDF.integer(1)
end
test "casting a boolean" do
assert RDF.false |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.true |> RDF.Integer.cast() == RDF.integer(1)
end
test "casting a string" do
assert RDF.string("0") |> RDF.Integer.cast() == RDF.integer(0)
end
test "casting an decimal" do
assert RDF.decimal(0) |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.decimal(1.0) |> RDF.Integer.cast() == RDF.integer(1)
assert RDF.decimal(3.14) |> RDF.Integer.cast() == RDF.integer(3)
end
test "casting a double" do
assert RDF.double(0) |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.double(0.0) |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.double(0.1) |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.double("+0") |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.double("+0.0") |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.double("-0.0") |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.double("0.0E0") |> RDF.Integer.cast() == RDF.integer(0)
assert RDF.double(1) |> RDF.Integer.cast() == RDF.integer(1)
assert RDF.double(3.14) |> RDF.Integer.cast() == RDF.integer(3)
assert RDF.double("NAN") |> RDF.Integer.cast() == nil
assert RDF.double("+INF") |> RDF.Integer.cast() == nil
end
@tag skip: "TODO: RDF.Float datatype"
test "casting a float"
test "with invalid literals" do
assert RDF.integer(3.14) |> RDF.Integer.cast() == nil
assert RDF.decimal("NAN") |> RDF.Integer.cast() == nil
assert RDF.double(true) |> RDF.Integer.cast() == nil
end
test "with literals of unsupported datatypes" do
assert RDF.DateTime.now() |> RDF.Integer.cast() == nil
end
end
describe "equality" do
test "two literals are equal when they have the same datatype and lexical form" do
[