Skip to content

Commit

Permalink
Migrate parser to RDF/JS interface.
Browse files Browse the repository at this point in the history
  • Loading branch information
RubenVerborgh committed Apr 15, 2017
1 parent 3458f14 commit 78e50ae
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 59 deletions.
8 changes: 4 additions & 4 deletions lib/N3Lexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ N3Lexer.prototype = {
}
// Try to find a backwards implication arrow
else if (this._n3Mode && input.length > 1 && input[1] === '=')
type = 'inverse', matchLength = 2, value = 'http://www.w3.org/2000/10/swap/log#implies';
type = 'inverse', matchLength = 2, value = '>';
break;

case '_':
Expand Down Expand Up @@ -245,7 +245,7 @@ N3Lexer.prototype = {
case 'a':
// Try to find an abbreviated predicate
if (match = this._shortPredicates.exec(input))
type = 'abbreviation', value = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
type = 'abbreviation', value = 'a';
else
inconclusive = true;
break;
Expand All @@ -255,9 +255,9 @@ N3Lexer.prototype = {
if (this._n3Mode && input.length > 1) {
type = 'abbreviation';
if (input[1] !== '>')
matchLength = 1, value = 'http://www.w3.org/2002/07/owl#sameAs';
matchLength = 1, value = '=';
else
matchLength = 2, value = 'http://www.w3.org/2000/10/swap/log#implies';
matchLength = 2, value = '>';
}
break;

Expand Down
109 changes: 63 additions & 46 deletions lib/N3Parser.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
// **N3Parser** parses N3 documents.
var N3Lexer = require('./N3Lexer'),
Datatype = require('./Datatypes');
var Term = Datatype.Term,
var NamedNode = Datatype.NamedNode,
BlankNode = Datatype.BlankNode,
Literal = Datatype.Literal,
Variable = Datatype.Variable,
DefaultGraph = Datatype.DefaultGraph,
Quad = Datatype.Quad;

var RDF_PREFIX = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
RDF_NIL = RDF_PREFIX + 'nil',
RDF_FIRST = RDF_PREFIX + 'first',
RDF_REST = RDF_PREFIX + 'rest';

var QUANTIFIERS_GRAPH = 'urn:n3:quantifiers';
var RDF_PREFIX = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
SWAP_PREFIX = 'http://www.w3.org/2000/10/swap/',
RDF_NIL = new NamedNode(RDF_PREFIX + 'nil'),
RDF_FIRST = new NamedNode(RDF_PREFIX + 'first'),
RDF_REST = new NamedNode(RDF_PREFIX + 'rest'),
N3_FORSOME = new NamedNode(SWAP_PREFIX + 'reify#forSome'),
N3_FORALL = new NamedNode(SWAP_PREFIX + 'reify#forAll'),
ABBREVIATIONS = {
'a': new NamedNode(RDF_PREFIX + 'type'),
'=': new NamedNode('http://www.w3.org/2002/07/owl#sameAs'),
'>': new NamedNode(SWAP_PREFIX + 'log#implies'),
},
DEFAULTGRAPH = new DefaultGraph(),
QUANTIFIERS_GRAPH = new NamedNode('urn:n3:quantifiers');

var absoluteIRI = /^[a-z][a-z0-9+.-]*:/i,
schemeAuthority = /^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i,
Expand Down Expand Up @@ -64,6 +76,11 @@ N3Parser._resetBlankNodeIds = function () {
N3Parser.prototype = {
// ## Private methods

// ### `_blankNode` creates a new blank node
_blankNode: function () {
return new BlankNode('b' + blankNodeCount++);
},

// ### `_setBase` sets the base IRI to resolve relative IRIs
_setBase: function (baseIRI) {
if (!baseIRI)
Expand Down Expand Up @@ -100,7 +117,7 @@ N3Parser.prototype = {
this._inversePredicate = false;
// In N3, blank nodes are scoped to a formula
// (using a dot as separator, as a blank node label cannot start with it)
this._prefixes._ = this._graph + '.';
this._prefixes._ = (this._graph ? this._graph.id + '.' : '.');
// Quantifiers are scoped to a formula
this._quantified = Object.create(this._quantified);
}
Expand Down Expand Up @@ -164,8 +181,8 @@ N3Parser.prototype = {
// Read a relative or absolute IRI
case 'IRI':
case 'typeIRI':
value = (this._base === null || absoluteIRI.test(token.value)) ?
token.value : this._resolveIRI(token);
value = new NamedNode(this._base === null || absoluteIRI.test(token.value) ?
token.value : this._resolveIRI(token));
break;
// Read a blank node or prefixed name
case 'type':
Expand All @@ -174,18 +191,19 @@ N3Parser.prototype = {
var prefix = this._prefixes[token.prefix];
if (prefix === undefined)
return this._error('Undefined prefix "' + token.prefix + ':"', token);
value = prefix + token.value;
value = new NamedNode(prefix + token.value);
break;
// Read a variable
case 'var':
return token.value;
value = new Variable(token.value.substr(1));
break;
// Everything else is not an entity
default:
return this._error('Expected entity but got ' + token.type, token);
}
// In N3 mode, replace the entity if it is quantified
if (!quantifier && this._n3Mode && (value in this._quantified))
value = this._quantified[value];
if (!quantifier && this._n3Mode && (value.id in this._quantified))
value = this._quantified[value.id];
return value;
},

Expand All @@ -196,7 +214,7 @@ N3Parser.prototype = {
case '[':
// Start a new triple with a new blank node as subject
this._saveContext('blank', this._graph,
this._subject = '_:b' + blankNodeCount++, null, null);
this._subject = this._blankNode(), null, null);
return this._readBlankNodeHead;
case '(':
// Start a new list
Expand All @@ -208,20 +226,20 @@ N3Parser.prototype = {
if (!this._n3Mode)
return this._error('Unexpected graph', token);
this._saveContext('formula', this._graph,
this._graph = '_:b' + blankNodeCount++, null, null);
this._graph = this._blankNode(), null, null);
return this._readSubject;
case '}':
// No subject; the graph in which we are reading is closed instead
return this._readPunctuation(token);
case '@forSome':
this._subject = null;
this._predicate = 'http://www.w3.org/2000/10/swap/reify#forSome';
this._quantifiedPrefix = '_:b';
this._predicate = N3_FORSOME;
this._quantifier = BlankNode;
return this._readQuantifierList;
case '@forAll':
this._subject = null;
this._predicate = 'http://www.w3.org/2000/10/swap/reify#forAll';
this._quantifiedPrefix = '?b-';
this._predicate = N3_FORALL;
this._quantifier = Variable;
return this._readQuantifierList;
default:
// Read the subject entity
Expand All @@ -244,7 +262,7 @@ N3Parser.prototype = {
case 'inverse':
this._inversePredicate = true;
case 'abbreviation':
this._predicate = token.value;
this._predicate = ABBREVIATIONS[token.value];
break;
case '.':
case ']':
Expand Down Expand Up @@ -272,12 +290,12 @@ N3Parser.prototype = {
_readObject: function (token) {
switch (token.type) {
case 'literal':
this._object = token.value;
this._object = new Literal(token.value);
return this._readDataTypeOrLang;
case '[':
// Start a new triple with a new blank node as subject
this._saveContext('blank', this._graph, this._subject, this._predicate,
this._subject = '_:b' + blankNodeCount++);
this._subject = this._blankNode());
return this._readBlankNodeHead;
case '(':
// Start a new list
Expand All @@ -289,7 +307,7 @@ N3Parser.prototype = {
if (!this._n3Mode)
return this._error('Unexpected graph', token);
this._saveContext('formula', this._graph, this._subject, this._predicate,
this._graph = '_:b' + blankNodeCount++);
this._graph = this._blankNode());
return this._readSubject;
default:
// Read the object entity
Expand Down Expand Up @@ -372,14 +390,15 @@ N3Parser.prototype = {
switch (token.type) {
case '[':
// Stack the current list triple and start a new triple with a blank node as subject
this._saveContext('blank', this._graph, list = '_:b' + blankNodeCount++,
RDF_FIRST, this._subject = item = '_:b' + blankNodeCount++);
this._saveContext('blank', this._graph,
list = this._blankNode(), RDF_FIRST,
this._subject = item = this._blankNode());
next = this._readBlankNodeHead;
break;
case '(':
// Stack the current list triple and start a new list
this._saveContext('list', this._graph, list = '_:b' + blankNodeCount++,
RDF_FIRST, RDF_NIL);
this._saveContext('list', this._graph,
list = this._blankNode(), RDF_FIRST, RDF_NIL);
this._subject = null;
break;
case ')':
Expand Down Expand Up @@ -408,7 +427,7 @@ N3Parser.prototype = {
list = RDF_NIL;
break;
case 'literal':
item = token.value;
item = new Literal(token.value);
itemComplete = false; // Can still have a datatype or language
next = this._readListItemDataTypeOrLang;
break;
Expand All @@ -419,7 +438,7 @@ N3Parser.prototype = {

// Create a new blank node if no item head was assigned yet
if (list === null)
this._subject = list = '_:b' + blankNodeCount++;
this._subject = list = this._blankNode();

// Is this the first element of the list?
if (prevList === null) {
Expand Down Expand Up @@ -471,12 +490,12 @@ N3Parser.prototype = {
case 'type':
case 'typeIRI':
suffix = true;
this._object += '^^' + this._readEntity(token);
this._object.id += '^^' + this._readEntity(token).id;
break;
// Add an "@lang" suffix for language tags
case 'langcode':
suffix = true;
this._object += '@' + token.value.toLowerCase();
this._object.id += '@' + token.value.toLowerCase();
break;
}
// If this literal was part of a list, write the item
Expand Down Expand Up @@ -540,7 +559,7 @@ N3Parser.prototype = {
next = this._readQuadPunctuation;
break;
}
return this._error('Expected punctuation to follow "' + this._object + '"', token);
return this._error('Expected punctuation to follow "' + this._object.id + '"', token);
}
// A triple has been completed now, so return it
if (subject !== null) {
Expand All @@ -566,7 +585,7 @@ N3Parser.prototype = {
next = this._readObject;
break;
default:
return this._error('Expected punctuation to follow "' + this._object + '"', token);
return this._error('Expected punctuation to follow "' + this._object.id + '"', token);
}
// A triple has been completed now, so return it
this._quad(this._subject, this._predicate, this._object, this._graph);
Expand All @@ -592,7 +611,7 @@ N3Parser.prototype = {
_readPrefixIRI: function (token) {
if (token.type !== 'IRI')
return this._error('Expected IRI to follow prefix "' + this._prefix + ':"', token);
var prefixIRI = this._readEntity(token);
var prefixIRI = this._readEntity(token).id;
this._prefixes[this._prefix] = prefixIRI;
this._prefixCallback(this._prefix, prefixIRI);
return this._readDeclarationPunctuation;
Expand Down Expand Up @@ -625,7 +644,7 @@ N3Parser.prototype = {
_readNamedGraphBlankLabel: function (token) {
if (token.type !== ']')
return this._error('Invalid graph label', token);
this._subject = '_:b' + blankNodeCount++;
this._subject = this._blankNode();
return this._readGraph;
},

Expand Down Expand Up @@ -655,17 +674,17 @@ N3Parser.prototype = {
}
// Without explicit quantifiers, map entities to a quantified entity
if (!this._explicitQuantifiers)
this._quantified[entity] = this._quantifiedPrefix + blankNodeCount++;
this._quantified[entity.id] = this._quantifier('b' + blankNodeCount++);
// With explicit quantifiers, output the reified quantifier
else {
// If this is the first item, start a new quantifier list
if (this._subject === null)
this._quad(this._graph || '', this._predicate,
this._subject = '_:b' + blankNodeCount++, QUANTIFIERS_GRAPH);
this._quad(this._graph || DEFAULTGRAPH, this._predicate,
this._subject = this._blankNode(), QUANTIFIERS_GRAPH);
// Otherwise, continue the previous list
else
this._quad(this._subject, RDF_REST,
this._subject = '_:b' + blankNodeCount++, QUANTIFIERS_GRAPH);
this._subject = this._blankNode(), QUANTIFIERS_GRAPH);
// Output the list item
this._quad(this._subject, RDF_FIRST, entity, QUANTIFIERS_GRAPH);
}
Expand Down Expand Up @@ -721,7 +740,7 @@ N3Parser.prototype = {

// ### `_readForwardPath` reads a '!' path
_readForwardPath: function (token) {
var subject, predicate, object = '_:b' + blankNodeCount++;
var subject, predicate, object = this._blankNode();
// The next token is the predicate
if ((predicate = this._readEntity(token)) === undefined)
return;
Expand All @@ -738,7 +757,7 @@ N3Parser.prototype = {

// ### `_readBackwardPath` reads a '^' path
_readBackwardPath: function (token) {
var subject = '_:b' + blankNodeCount++, predicate, object;
var subject = this._blankNode(), predicate, object;
// The next token is the predicate
if ((predicate = this._readEntity(token)) === undefined)
return;
Expand Down Expand Up @@ -771,10 +790,8 @@ N3Parser.prototype = {

// ### `_quad` emits a triple through the callback
_quad: function (subject, predicate, object, graph) {
this._callback(null, new Quad(
Term.fromId(subject), Term.fromId(predicate),
Term.fromId(object), Term.fromId(graph || '')
));
this._callback(null,
new Quad(subject, predicate, object, graph || DEFAULTGRAPH));
},

// ### `_error` emits an error message through the callback
Expand Down
8 changes: 4 additions & 4 deletions test/N3Lexer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ describe('N3Lexer', function () {
it('should tokenize the "a" predicate',
shouldTokenize('<x> a <y>.',
{ type: 'IRI', value: 'x', line: 1 },
{ type: 'abbreviation', value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', line: 1 },
{ type: 'abbreviation', value: 'a', line: 1 },
{ type: 'IRI', value: 'y', line: 1 },
{ type: '.', line: 1 },
{ type: 'eof', line: 1 }));
Expand Down Expand Up @@ -676,21 +676,21 @@ describe('N3Lexer', function () {
it('should tokenize the equality sign',
shouldTokenize('<a> = <b> ',
{ type: 'IRI', value: 'a', line: 1 },
{ type: 'abbreviation', value: 'http://www.w3.org/2002/07/owl#sameAs', line: 1 },
{ type: 'abbreviation', value: '=', line: 1 },
{ type: 'IRI', value: 'b', line: 1 },
{ type: 'eof', line: 1 }));

it('should tokenize the right implication',
shouldTokenize('<a> => <b> ',
{ type: 'IRI', value: 'a', line: 1 },
{ type: 'abbreviation', value: 'http://www.w3.org/2000/10/swap/log#implies', line: 1 },
{ type: 'abbreviation', value: '>', line: 1 },
{ type: 'IRI', value: 'b', line: 1 },
{ type: 'eof', line: 1 }));

it('should tokenize the left implication',
shouldTokenize('<a> <= <b> ',
{ type: 'IRI', value: 'a', line: 1 },
{ type: 'inverse', value: 'http://www.w3.org/2000/10/swap/log#implies', line: 1 },
{ type: 'inverse', value: '>', line: 1 },
{ type: 'IRI', value: 'b', line: 1 },
{ type: 'eof', line: 1 }));

Expand Down
10 changes: 5 additions & 5 deletions test/N3Parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1180,11 +1180,11 @@ describe('N3Parser', function () {

it('should parse a @forAll statement',
shouldParse(parser, '@forAll <x>. <x> <x> <x>.',
['?b-0', '?b-0', '?b-0']));
['?b0', '?b0', '?b0']));

it('should parse a @forAll statement with multiple entities',
shouldParse(parser, '@prefix a: <a:>. @base <b:>. @forAll a:x, <y>, a:z. a:x <y> a:z.',
['?b-0', '?b-1', '?b-2']));
['?b0', '?b1', '?b2']));

it('should not parse a @forAll statement with an invalid prefix',
shouldNotParse(parser, '@forAll a:b.',
Expand All @@ -1200,9 +1200,9 @@ describe('N3Parser', function () {

it('should correctly scope @forAll statements',
shouldParse(parser, '@forAll <x>. <x> <x> { @forAll <x>. <x> <x> <x>. }. <x> <x> <x>.',
['?b-0', '?b-0', '_:b1'],
['?b-2', '?b-2', '?b-2', '_:b1'],
['?b-0', '?b-0', '?b-0']));
['?b0', '?b0', '_:b1'],
['?b2', '?b2', '?b2', '_:b1'],
['?b0', '?b0', '?b0']));

it('should parse a ! path of length 2 as subject',
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
Expand Down

0 comments on commit 78e50ae

Please sign in to comment.