diff --git a/lib/rdf/serializations/turtle_decoder.ex b/lib/rdf/serializations/turtle_decoder.ex
index 9308ebf..38027b1 100644
--- a/lib/rdf/serializations/turtle_decoder.ex
+++ b/lib/rdf/serializations/turtle_decoder.ex
@@ -69,9 +69,8 @@ defmodule RDF.Turtle.Decoder do
{graph, %State{namespaces: namespaces, base_iri: base_iri}} =
Enum.reduce(ast, {RDF.Graph.new(), %State{base_iri: base_iri}}, fn
{:triples, triples_ast}, {graph, state} ->
- with {statements, state} = triples(triples_ast, state) do
- {RDF.Graph.add(graph, statements), state}
- end
+ {statements, state} = triples(triples_ast, state)
+ {RDF.Graph.add(graph, statements), state}
{:directive, directive_ast}, {graph, state} ->
{graph, directive(directive_ast, state)}
@@ -89,49 +88,49 @@ defmodule RDF.Turtle.Decoder do
end
defp directive({:prefix, {:prefix_ns, _, ns}, iri}, state) do
- if IRI.absolute?(iri) do
- State.add_namespace(state, ns, iri)
- else
- with absolute_iri = IRI.absolute(iri, state.base_iri) do
- State.add_namespace(state, ns, to_string(absolute_iri))
+ absolute_iri =
+ if IRI.absolute?(iri) do
+ iri
+ else
+ iri |> IRI.absolute(state.base_iri) |> to_string()
end
- end
+
+ State.add_namespace(state, ns, absolute_iri)
end
defp directive({:base, iri}, %State{base_iri: base_iri} = state) do
cond do
- IRI.absolute?(iri) ->
- %State{state | base_iri: RDF.iri(iri)}
-
- base_iri != nil ->
- with absolute_iri = IRI.absolute(iri, base_iri) do
- %State{state | base_iri: absolute_iri}
- end
-
- true ->
- raise "Could not resolve relative IRI '#{iri}', no base iri provided"
+ IRI.absolute?(iri) -> %State{state | base_iri: RDF.iri(iri)}
+ not is_nil(base_iri) -> %State{state | base_iri: IRI.absolute(iri, base_iri)}
+ true -> raise "Could not resolve relative IRI '#{iri}', no base iri provided"
end
end
defp triples({:blankNodePropertyList, _} = ast, state) do
- with {_, statements, state} = resolve_node(ast, [], state) do
- {statements, state}
- end
+ {_, statements, state} = resolve_node(ast, [], state)
+ {statements, state}
end
defp triples({subject, predications}, state) do
- with {subject, statements, state} = resolve_node(subject, [], state) do
- Enum.reduce(predications, {statements, state}, fn {predicate, objects},
- {statements, state} ->
- with {predicate, statements, state} = resolve_node(predicate, statements, state) do
- Enum.reduce(objects, {statements, state}, fn object, {statements, state} ->
- with {object, statements, state} = resolve_node(object, statements, state) do
- {[{subject, predicate, object} | statements], state}
- end
- end)
- end
- end)
- end
+ {subject, statements, state} = resolve_node(subject, [], state)
+
+ predications(subject, predications, statements, state)
+ end
+
+ defp predications(subject, predications, statements, state) do
+ Enum.reduce(predications, {statements, state}, fn
+ {predicate, objects}, {statements, state} ->
+ {predicate, statements, state} = resolve_node(predicate, statements, state)
+
+ Enum.reduce(objects, {statements, state}, fn
+ {:annotation, annotation}, {[last_statement | _] = statements, state} ->
+ predications(last_statement, annotation, statements, state)
+
+ object, {statements, state} ->
+ {object, statements, state} = resolve_node(object, statements, state)
+ {[{subject, predicate, object} | statements], state}
+ end)
+ end)
end
defp resolve_node({:prefix_ln, line_number, {prefix, name}}, statements, state) do
@@ -159,16 +158,14 @@ defmodule RDF.Turtle.Decoder do
end
defp resolve_node({:anon}, statements, state) do
- with {node, state} = State.next_bnode(state) do
- {node, statements, state}
- end
+ {node, state} = State.next_bnode(state)
+ {node, statements, state}
end
defp resolve_node({:blankNodePropertyList, property_list}, statements, state) do
- with {subject, state} = State.next_bnode(state),
- {new_statements, state} = triples({subject, property_list}, state) do
- {subject, statements ++ new_statements, state}
- end
+ {subject, state} = State.next_bnode(state)
+ {new_statements, state} = triples({subject, property_list}, state)
+ {subject, statements ++ new_statements, state}
end
defp resolve_node(
@@ -176,9 +173,8 @@ defmodule RDF.Turtle.Decoder do
statements,
state
) do
- with {datatype, statements, state} = resolve_node(datatype, statements, state) do
- {RDF.literal(value, datatype: datatype), statements, state}
- end
+ {datatype, statements, state} = resolve_node(datatype, statements, state)
+ {RDF.literal(value, datatype: datatype), statements, state}
end
defp resolve_node({:collection, []}, statements, state) do
@@ -186,29 +182,36 @@ defmodule RDF.Turtle.Decoder do
end
defp resolve_node({:collection, elements}, statements, state) do
- with {first_list_node, state} = State.next_bnode(state),
- [first_element | rest_elements] = elements,
- {first_element_node, statements, state} = resolve_node(first_element, statements, state),
- first_statement = [{first_list_node, RDF.first(), first_element_node}] do
- {last_list_node, statements, state} =
- Enum.reduce(
- rest_elements,
- {first_list_node, statements ++ first_statement, state},
- fn element, {list_node, statements, state} ->
- with {element_node, statements, state} = resolve_node(element, statements, state),
- {next_list_node, state} = State.next_bnode(state) do
- {next_list_node,
- statements ++
- [
- {list_node, RDF.rest(), next_list_node},
- {next_list_node, RDF.first(), element_node}
- ], state}
- end
- end
- )
+ {first_list_node, state} = State.next_bnode(state)
+ [first_element | rest_elements] = elements
+ {first_element_node, statements, state} = resolve_node(first_element, statements, state)
+ first_statement = [{first_list_node, RDF.first(), first_element_node}]
- {first_list_node, statements ++ [{last_list_node, RDF.rest(), RDF.nil()}], state}
- end
+ {last_list_node, statements, state} =
+ Enum.reduce(
+ rest_elements,
+ {first_list_node, statements ++ first_statement, state},
+ fn element, {list_node, statements, state} ->
+ {element_node, statements, state} = resolve_node(element, statements, state)
+ {next_list_node, state} = State.next_bnode(state)
+
+ {next_list_node,
+ statements ++
+ [
+ {list_node, RDF.rest(), next_list_node},
+ {next_list_node, RDF.first(), element_node}
+ ], state}
+ end
+ )
+
+ {first_list_node, statements ++ [{last_list_node, RDF.rest(), RDF.nil()}], state}
+ end
+
+ defp resolve_node({:quoted_triple, s_node, p_node, o_node}, statements, state) do
+ {subject, statements, state} = resolve_node(s_node, statements, state)
+ {predicate, statements, state} = resolve_node(p_node, statements, state)
+ {object, statements, state} = resolve_node(o_node, statements, state)
+ {{subject, predicate, object}, statements, state}
end
defp resolve_node(node, statements, state), do: {node, statements, state}
diff --git a/src/turtle_lexer.xrl b/src/turtle_lexer.xrl
index 7e1c614..f291484 100644
--- a/src/turtle_lexer.xrl
+++ b/src/turtle_lexer.xrl
@@ -70,6 +70,10 @@ a : {token, {'a', TokenLine}}.
\( : {token, {'(', TokenLine}}.
\) : {token, {')', TokenLine}}.
\^\^ : {token, {'^^', TokenLine}}.
+\<\< : {token, {'<<', TokenLine}}.
+\>\> : {token, {'>>', TokenLine}}.
+\{\| : {token, {'{|', TokenLine}}.
+\|\} : {token, {'|}', TokenLine}}.
{WS}+ : skip_token.
{COMMENT} : skip_token.
diff --git a/src/turtle_parser.yrl b/src/turtle_parser.yrl
index d78b977..885daee 100644
--- a/src/turtle_parser.yrl
+++ b/src/turtle_parser.yrl
@@ -3,11 +3,13 @@
Nonterminals turtleDoc statement directive prefixID base sparqlPrefix sparqlBase
triples predicateObjectList objectList blankNodePropertyList semicolonSequence
verb subject predicate object collection collection_elements
- literal numericLiteral rdfLiteral booleanLiteral iri prefixedName blankNode.
+ literal numericLiteral rdfLiteral booleanLiteral iri prefixedName blankNode
+ annotation quotedTriple qtSubject qtObject .
Terminals prefix_ns prefix_ln iriref blank_node_label anon
string_literal_quote langtag integer decimal double boolean
- '.' ';' ',' '[' ']' '(' ')' '^^' '@prefix' '@base' 'PREFIX' 'BASE' 'a' .
+ '.' ';' ',' '[' ']' '(' ')' '^^' '@prefix' '@base' 'PREFIX' 'BASE' 'a'
+ '<<' '>>' '{|' '|}' .
Rootsymbol turtleDoc.
@@ -37,10 +39,11 @@ predicateObjectList -> verb objectList semicolonSequence : [{'$1', '$2'}] .
predicateObjectList -> verb objectList semicolonSequence predicateObjectList : [{'$1', '$2'} | '$4'] .
semicolonSequence -> ';' .
semicolonSequence -> ';' semicolonSequence .
-%%: '$1' .
objectList -> object : ['$1'] .
objectList -> object ',' objectList : ['$1' | '$3'] .
+objectList -> object annotation : ['$1', {annotation, '$2'}] .
+objectList -> object annotation ',' objectList : ['$1', {annotation, '$2'} | '$4'] .
blankNodePropertyList -> '[' predicateObjectList ']' : {blankNodePropertyList, '$2'} .
@@ -49,12 +52,24 @@ verb -> predicate : '$1' .
subject -> iri : '$1' .
subject -> blankNode : '$1' .
subject -> collection : '$1' .
+subject -> quotedTriple : '$1' .
predicate -> iri : '$1' .
object -> iri : '$1' .
object -> blankNode : '$1' .
object -> collection : '$1' .
object -> blankNodePropertyList : '$1' .
object -> literal : '$1' .
+object -> quotedTriple : '$1' .
+
+quotedTriple -> '<<' qtSubject verb qtObject '>>' : {quoted_triple, '$2', '$3', '$4' } .
+qtSubject -> iri : '$1' .
+qtSubject -> blankNode : '$1' .
+qtSubject -> quotedTriple : '$1' .
+qtObject -> iri : '$1' .
+qtObject -> blankNode : '$1' .
+qtObject -> literal : '$1' .
+qtObject -> quotedTriple : '$1' .
+annotation -> '{|' predicateObjectList '|}' : '$2' .
collection -> '(' ')' : {collection, []} .
collection -> '(' collection_elements ')' : {collection, '$2'} .
diff --git a/test/acceptance/turtle_star_w3c_eval_test.exs b/test/acceptance/turtle_star_w3c_eval_test.exs
new file mode 100644
index 0000000..4ebc216
--- /dev/null
+++ b/test/acceptance/turtle_star_w3c_eval_test.exs
@@ -0,0 +1,40 @@
+defmodule RDF.Star.Turtle.W3C.EvalTest do
+ @moduledoc """
+ The official RDF-star Turtle eval test suite.
+
+ from
+ """
+
+ use ExUnit.Case, async: false
+
+ @turtle_star_eval_test_suite Path.join(RDF.TestData.dir(), "rdf-star/turtle/eval")
+
+ ExUnit.Case.register_attribute(__ENV__, :turtle_test)
+
+ @turtle_star_eval_test_suite
+ |> File.ls!()
+ |> Enum.filter(fn file -> Path.extname(file) == ".ttl" and file != "manifest.ttl" end)
+ |> Enum.each(fn file ->
+ base = Path.basename(file, ".ttl")
+
+ if base in [
+ "turtle-star-eval-bnode-1",
+ "turtle-star-eval-bnode-2",
+ # TODO: this one just fails, because our blank node counter starts at 0 instead of 1
+ "turtle-star-eval-annotation-2"
+ ] do
+ @tag skip: """
+ The produced graphs are correct, but have different blank node labels than the result graph.
+ TODO: Implement a graph isomorphism algorithm.
+ """
+ end
+
+ @turtle_test ttl_file: Path.join(@turtle_star_eval_test_suite, file),
+ nt_file: Path.join(@turtle_star_eval_test_suite, base <> ".nt")
+ test "eval test: #{file}", context do
+ assert RDF.Turtle.read_file!(context.registered.turtle_test[:ttl_file])
+ |> RDF.Graph.clear_metadata() ==
+ RDF.NTriples.read_file!(context.registered.turtle_test[:nt_file])
+ end
+ end)
+end
diff --git a/test/acceptance/turtle_star_w3c_syntax_test.exs b/test/acceptance/turtle_star_w3c_syntax_test.exs
new file mode 100644
index 0000000..2266500
--- /dev/null
+++ b/test/acceptance/turtle_star_w3c_syntax_test.exs
@@ -0,0 +1,29 @@
+defmodule RDF.Star.Turtle.W3C.SyntaxTest do
+ @moduledoc """
+ The official RDF-star Turtle syntax test suite.
+
+ from
+ """
+
+ use ExUnit.Case, async: false
+
+ @turtle_star_syntax_test_suite Path.join(RDF.TestData.dir(), "rdf-star/turtle/syntax")
+
+ ExUnit.Case.register_attribute(__ENV__, :turtle_test)
+
+ @turtle_star_syntax_test_suite
+ |> File.ls!()
+ |> Enum.filter(fn file -> Path.extname(file) == ".ttl" and file != "manifest.ttl" end)
+ |> Enum.each(fn file ->
+ @turtle_test file: Path.join(@turtle_star_syntax_test_suite, file)
+ if file |> String.contains?("-bad-") do
+ test "Negative syntax test: #{file}", context do
+ assert {:error, _} = RDF.Turtle.read_file(context.registered.turtle_test[:file])
+ end
+ else
+ test "Positive syntax test: #{file}", context do
+ assert {:ok, %RDF.Graph{}} = RDF.Turtle.read_file(context.registered.turtle_test[:file])
+ end
+ end
+ end)
+end
diff --git a/test/data/rdf-star/turtle/eval/manifest.ttl b/test/data/rdf-star/turtle/eval/manifest.ttl
new file mode 100644
index 0000000..d9ac334
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/manifest.ttl
@@ -0,0 +1,101 @@
+## Distributed under both the "W3C Test Suite License" [1]
+## and the "W3C 3-clause BSD License".
+## [1] https://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+## [2] https://www.w3.org/Consortium/Legal/2008/03-bsd-license
+
+PREFIX rdf:
+PREFIX rdfs:
+PREFIX mf:
+PREFIX test:
+PREFIX rdft:
+PREFIX trs:
+
+trs:manifest rdf:type mf:Manifest ;
+ rdfs:label "Turtle-star Evaluation Tests" ;
+ mf:entries
+ (
+ trs:turtle-star-1
+ trs:turtle-star-2
+ trs:turtle-star-bnode-1
+ trs:turtle-star-bnode-2
+ trs:turtle-star-annotation-1
+ trs:turtle-star-annotation-2
+ trs:turtle-star-annotation-3
+ trs:turtle-star-annotation-4
+ trs:turtle-star-annotation-5
+ trs:turtle-star-quoted-annotation-1
+ trs:turtle-star-quoted-annotation-2
+ trs:turtle-star-quoted-annotation-3
+ ) .
+
+trs:turtle-star-1 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - subject quoted triple" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-2 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - object quoted triple" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-bnode-1 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - blank node label" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-bnode-2 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - blank node labels" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-annotation-1 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation form" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-annotation-2 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation example" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-annotation-3 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation - predicate and object lists" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-annotation-4 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation - nested" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-annotation-5 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation object list" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-quoted-annotation-1 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation with quoting" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-quoted-annotation-2 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation on triple with quoted subject" ;
+ mf:action ;
+ mf:result ;
+ .
+
+trs:turtle-star-quoted-annotation-3 rdf:type rdft:TestTurtleEval ;
+ mf:name "Turtle-star - Annotation on triple with quoted object" ;
+ mf:action ;
+ mf:result ;
+ .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-01.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-01.nt
new file mode 100644
index 0000000..7f2be99
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-01.nt
@@ -0,0 +1 @@
+<< >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-01.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-01.ttl
new file mode 100644
index 0000000..ad4940b
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-01.ttl
@@ -0,0 +1,3 @@
+PREFIX :
+
+<<:s :p :o>> :q :z .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-02.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-02.nt
new file mode 100644
index 0000000..28f1501
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-02.nt
@@ -0,0 +1 @@
+ << >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-02.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-02.ttl
new file mode 100644
index 0000000..6e76ac6
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-02.ttl
@@ -0,0 +1,3 @@
+PREFIX :
+
+:a :q <<:s :p :o>> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-1.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-1.nt
new file mode 100644
index 0000000..8165c58
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-1.nt
@@ -0,0 +1,2 @@
+ .
+<< >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-1.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-1.ttl
new file mode 100644
index 0000000..fdba9df
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-1.ttl
@@ -0,0 +1,3 @@
+PREFIX :
+
+:s :p :o {| :r :z |} .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-2.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-2.nt
new file mode 100644
index 0000000..87450c5
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-2.nt
@@ -0,0 +1,7 @@
+ .
+_:b1 .
+_:b1 "2020-01-20"^^ .
+<< >> _:b1 .
+_:b2 .
+_:b2 "2020-12-31"^^ .
+<< >> _:b2 .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-2.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-2.ttl
new file mode 100644
index 0000000..06ef8ea
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-2.ttl
@@ -0,0 +1,10 @@
+PREFIX :
+PREFIX xsd:
+
+:s :p :o {| :source [ :graph ;
+ :date "2020-01-20"^^xsd:date
+ ] ;
+ :source [ :graph ;
+ :date "2020-12-31"^^xsd:date
+ ]
+ |} .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-3.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-3.nt
new file mode 100644
index 0000000..af17aa5
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-3.nt
@@ -0,0 +1,6 @@
+ .
+<< >> .
+ .
+<< >> .
+ .
+<< >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-3.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-3.ttl
new file mode 100644
index 0000000..8b2bbd6
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-3.ttl
@@ -0,0 +1,5 @@
+PREFIX :
+
+:s :p :o {| :a :b |};
+ :p2 :o2 {| :a2 :b2 |},
+ :o3 {| :a3 :b3 |}.
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-4.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-4.nt
new file mode 100644
index 0000000..4bf32e2
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-4.nt
@@ -0,0 +1,3 @@
+ .
+<< >> .
+<< << >> >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-4.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-4.ttl
new file mode 100644
index 0000000..c07c701
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-4.ttl
@@ -0,0 +1,3 @@
+PREFIX :
+
+:s :p :o {| :a :b {| :a2 :b2 |} |}.
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-5.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-5.nt
new file mode 100644
index 0000000..1238d92
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-5.nt
@@ -0,0 +1,3 @@
+ .
+ .
+<< >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-5.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-5.ttl
new file mode 100644
index 0000000..751dc67
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-annotation-5.ttl
@@ -0,0 +1,4 @@
+PREFIX :
+
+:s :p :o1, :o2 {| :a :b |} .
+
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-1.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-1.nt
new file mode 100644
index 0000000..dcd268d
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-1.nt
@@ -0,0 +1,2 @@
+_:b9 .
+<< _:b9 >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-1.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-1.ttl
new file mode 100644
index 0000000..a2c01ff
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-1.ttl
@@ -0,0 +1,4 @@
+PREFIX :
+
+_:b :p :o .
+<<_:b :p :o>> :q :z .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-2.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-2.nt
new file mode 100644
index 0000000..42dcac5
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-2.nt
@@ -0,0 +1,2 @@
+_:label1 _:label1 .
+<< _:label1 _:label1 >> << _:label1 >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-2.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-2.ttl
new file mode 100644
index 0000000..8b2e542
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-bnode-2.ttl
@@ -0,0 +1,4 @@
+PREFIX :
+
+_:a :p1 _:a .
+<<_:a :p1 _:a >> :q <<_:a :p2 :o>> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-1.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-1.nt
new file mode 100644
index 0000000..31eba74
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-1.nt
@@ -0,0 +1,2 @@
+ .
+<< >> << >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-1.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-1.ttl
new file mode 100644
index 0000000..5245264
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-1.ttl
@@ -0,0 +1,3 @@
+PREFIX :
+
+:s :p :o {| :r <<:s1 :p1 :o1>> |} .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-2.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-2.nt
new file mode 100644
index 0000000..d7efbdf
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-2.nt
@@ -0,0 +1,2 @@
+<< >> .
+<<<< >> >> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-2.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-2.ttl
new file mode 100644
index 0000000..e4f7021
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-2.ttl
@@ -0,0 +1,3 @@
+PREFIX :
+
+<<:s1 :p1 :o1>> :p :o {| :r :z |} .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-3.nt b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-3.nt
new file mode 100644
index 0000000..a655abb
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-3.nt
@@ -0,0 +1,2 @@
+ << >> .
+<< << >>>> .
diff --git a/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-3.ttl b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-3.ttl
new file mode 100644
index 0000000..f19ee11
--- /dev/null
+++ b/test/data/rdf-star/turtle/eval/turtle-star-eval-quoted-annotation-3.ttl
@@ -0,0 +1,3 @@
+PREFIX :
+
+:s :p <<:s2 :p2 :o2>> {| :r :z |} .
diff --git a/test/data/rdf-star/turtle/syntax/manifest.ttl b/test/data/rdf-star/turtle/syntax/manifest.ttl
new file mode 100644
index 0000000..eff4fd1
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/manifest.ttl
@@ -0,0 +1,259 @@
+## Distributed under both the "W3C Test Suite License" [1]
+## and the "W3C 3-clause BSD License".
+## [1] https://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+## [2] https://www.w3.org/Consortium/Legal/2008/03-bsd-license
+
+PREFIX rdf:
+PREFIX rdfs:
+PREFIX mf:
+PREFIX test:
+PREFIX rdft:
+PREFIX trs:
+
+trs:manifest rdf:type mf:Manifest ;
+ rdfs:label "Turtle-star Syntax Tests" ;
+ mf:entries
+ (
+ trs:turtle-star-1
+ trs:turtle-star-2
+
+ trs:turtle-star-inside-1
+ trs:turtle-star-inside-2
+
+ trs:turtle-star-nested-1
+ trs:turtle-star-nested-2
+
+ trs:turtle-star-compound-1
+
+ trs:turtle-star-bnode-1
+ trs:turtle-star-bnode-2
+ trs:turtle-star-bnode-3
+
+ trs:turtle-star-bad-1
+ trs:turtle-star-bad-2
+ trs:turtle-star-bad-3
+ trs:turtle-star-bad-4
+ trs:turtle-star-bad-5
+ trs:turtle-star-bad-6
+ trs:turtle-star-bad-7
+ trs:turtle-star-bad-8
+
+ trs:turtle-star-ann-1
+ trs:turtle-star-ann-2
+
+ trs:turtle-star-bad-ann-1
+ trs:turtle-star-bad-ann-2
+
+## The same data as the N-Triples-star syntax tests,
+## except in file *.ttl and "TestTurtle"
+
+ trs:nt-ttl-star-1
+ trs:nt-ttl-star-2
+ trs:nt-ttl-star-3
+ trs:nt-ttl-star-4
+ trs:nt-ttl-star-5
+
+ trs:nt-ttl-star-bnode-1
+ trs:nt-ttl-star-bnode-2
+
+ trs:nt-ttl-star-nested-1
+ trs:nt-ttl-star-nested-2
+
+ trs:nt-ttl-star-bad-1
+ trs:nt-ttl-star-bad-2
+ trs:nt-ttl-star-bad-3
+ trs:nt-ttl-star-bad-4
+ ) .
+
+## Good Syntax
+
+trs:turtle-star-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - subject quoted triple" ;
+ mf:action ;
+ .
+
+trs:turtle-star-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - object quoted triple" ;
+ mf:action ;
+ .
+
+trs:turtle-star-inside-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - quoted triple inside blankNodePropertyList" ;
+ mf:action ;
+ .
+
+trs:turtle-star-inside-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - quoted triple inside collection" ;
+ mf:action ;
+ .
+
+trs:turtle-star-nested-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - nested quoted triple, subject position" ;
+ mf:action ;
+ .
+
+trs:turtle-star-nested-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - nested quoted triple, object position" ;
+ mf:action ;
+ .
+
+trs:turtle-star-compound-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - compound forms" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bnode-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - blank node subject" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bnode-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - blank node object" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bnode-3 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - blank node" ;
+ mf:action ;
+ .
+
+## Bad Syntax
+
+trs:turtle-star-bad-1 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - quoted triple as predicate" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bad-2 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - quoted triple outside triple" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bad-3 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - collection list in quoted triple" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bad-4 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - literal in subject position of quoted triple" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bad-5 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - blank node as predicate in quoted triple";
+ mf:action ;
+ .
+
+trs:turtle-star-bad-6 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - compound blank node expression";
+ mf:action ;
+ .
+
+trs:turtle-star-bad-7 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - incomplete quoted triple";
+ mf:action ;
+ .
+
+trs:turtle-star-bad-8 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - over-long quoted triple";
+ mf:action ;
+ .
+
+## Annotation syntax
+
+trs:turtle-star-ann-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - Annotation form" ;
+ mf:action ;
+ .
+
+trs:turtle-star-ann-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "Turtle-star - Annotation example" ;
+ mf:action ;
+ .
+
+## Bad annotation syntax
+
+trs:turtle-star-bad-ann-1 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - empty annotation" ;
+ mf:action ;
+ .
+
+trs:turtle-star-bad-ann-2 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "Turtle-star - bad - triple as annotation" ;
+ mf:action ;
+ .
+
+## --------------------------------------------------
+## Same data as the N-triples-star tests.
+## N-Triples is a subset of Turtle, and the same is true for the "star" feature.
+
+## Good Syntax
+
+trs:nt-ttl-star-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - subject quoted triple" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - object quoted triple" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-3 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - subject and object quoted triples" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-4 rdf:type rdft:TestNTriplesPositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - whitespace and terms" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-5 rdf:type rdft:TestNTriplesPositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Nested, no whitespace" ;
+ mf:action ;
+ .
+
+# Blank nodes
+
+trs:nt-ttl-star-bnode-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Blank node subject" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-bnode-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Blank node object" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-nested-1 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Nested subject term" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-nested-2 rdf:type rdft:TestTurtlePositiveSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Nested object term" ;
+ mf:action ;
+ .
+
+## Bad syntax
+
+trs:nt-ttl-star-bad-1 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Bad - quoted triple as predicate" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-bad-2 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Bad - quoted triple, literal subject" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-bad-3 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Bad - quoted triple, literal predicate" ;
+ mf:action ;
+ .
+
+trs:nt-ttl-star-bad-4 rdf:type rdft:TestTurtleNegativeSyntax ;
+ mf:name "N-Triples-star as Turtle-star - Bad - quoted triple, blank node predicate" ;
+ mf:action ;
+ .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-1.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-1.ttl
new file mode 100644
index 0000000..401f4b8
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-1.ttl
@@ -0,0 +1 @@
+ << >> .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-2.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-2.ttl
new file mode 100644
index 0000000..1e47b36
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-2.ttl
@@ -0,0 +1 @@
+<< "XYZ" >> .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-3.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-3.ttl
new file mode 100644
index 0000000..eaeb6f2
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-3.ttl
@@ -0,0 +1 @@
+<< "XYZ" >> .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-4.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-4.ttl
new file mode 100644
index 0000000..af41d20
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bad-syntax-4.ttl
@@ -0,0 +1 @@
+<< _:label >> .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-bnode-1.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bnode-1.ttl
new file mode 100644
index 0000000..2d94448
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bnode-1.ttl
@@ -0,0 +1,2 @@
+_:b0 .
+<< _:b0 >> "ABC" .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-bnode-2.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bnode-2.ttl
new file mode 100644
index 0000000..2dc337a
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-bnode-2.ttl
@@ -0,0 +1,2 @@
+ _:b1 .
+<< _:b1 >> "456"^^ .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-nested-1.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-nested-1.ttl
new file mode 100644
index 0000000..d6a50cb
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-nested-1.ttl
@@ -0,0 +1,3 @@
+ .
+<< >> .
+<< << >> >> "1"^^ .
diff --git a/test/data/rdf-star/turtle/syntax/nt-ttl-star-nested-2.ttl b/test/data/rdf-star/turtle/syntax/nt-ttl-star-nested-2.ttl
new file mode 100644
index 0000000..7a8d313
--- /dev/null
+++ b/test/data/rdf-star/turtle/syntax/nt-ttl-star-nested-2.ttl
@@ -0,0 +1,3 @@
+ .
+ << >> .
+<< <<