diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e019c67 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright © 2024–now Jitse De Smet +Comunica Association and Ghent University – imec, Belgium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index ee1871e..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,2 +0,0 @@ -best not unpack the context entry since its contents are reset between parsing runs. -You should thus only have the context object itself withing your closure. diff --git a/engines/engine-sparql-1-1-adjust/README.md b/engines/engine-sparql-1-1-adjust/README.md new file mode 100644 index 0000000..e6dfd09 --- /dev/null +++ b/engines/engine-sparql-1-1-adjust/README.md @@ -0,0 +1,46 @@ +# TRAQULA parser engine for SPARQL 1.1 + Adjust + +TRAQULA Sparql 1.1 Adjust is a SPARQL 1.1 query parser that also parses the [builtin function ADJUST](https://github.com/w3c/sparql-dev/blob/main/SEP/SEP-0002/sep-0002.md) for TypeScript. +Simple grammar extension of [TRAQULA engine-sparql-1-1](https://github.com/comunica/traqula/tree/main/engines/engine-sparql-1-1) + +## Installation + +```bash +npm install @traqula/engine-sparql-1-1 +``` + +or + +```bash +yarn add @traqula/engine-sparql-1-1 +``` + +## Import + +Either through ESM import: + +```javascript +import { Sparql11AdjustParser } from '@traqula/engine-sparql-1-1-adjust'; +``` + +_or_ CJS require: + +```javascript +const Sparql11AdjustParser = require('@traqula/engine-sparql-1-1-adjust').Sparql11AdjustParser; +``` + +## Usage + +This package contains a `Sparql11AdjustParser` that is able to parse SPARQL 1.1 queries including the [builtin function ADJUST](https://github.com/w3c/sparql-dev/blob/main/SEP/SEP-0002/sep-0002.md): + +```typescript +const parser = new Sparql11Parser(); +const abstractSyntaxTree = parser.parse(` +SELECT ?s ?p (ADJUST(?o, "-PT10H"^^) as ?adjusted) WHERE { + ?s ?p ?o +} +`); +``` + +This parser is a simple grammar extension to the [engine-sparql-1-1](https://github.com/comunica/traqula/tree/main/engines/engine-sparql-1-1). +As such, most, if not all, documentation of that parser holds for this one too. \ No newline at end of file diff --git a/engines/engine-sparql-1-1-adjust/lib/Sparql11AdjustParser.ts b/engines/engine-sparql-1-1-adjust/lib/Sparql11AdjustParser.ts index 3eddf5a..c7d5e75 100644 --- a/engines/engine-sparql-1-1-adjust/lib/Sparql11AdjustParser.ts +++ b/engines/engine-sparql-1-1-adjust/lib/Sparql11AdjustParser.ts @@ -1,14 +1,15 @@ import {Builder, type ImplArgs} from '@traqula/core'; -import { gram } from '@traqula/rules-sparql-1-1-adjust'; +import {gram, lex} from '@traqula/rules-sparql-1-1-adjust'; import { Expression, - IriTerm, lex as l, + gram as g11, + lex as l11, + IriTerm, PropertyPath, SparqlParser as ISparqlParser, SparqlQuery } from '@traqula/rules-sparql-1-1'; -import { gram as g11 } from '@traqula/rules-sparql-1-1'; -import { sparql11ParserBuilder } from '@traqula/engine-sparql-1-1'; +import {sparql11ParserBuilder} from '@traqula/engine-sparql-1-1'; import {DataFactory} from "rdf-data-factory"; import type * as RDF from "@rdfjs/types"; @@ -35,8 +36,8 @@ export class Sparql11AdjustParser implements ISparqlParser { public constructor(context: Partial = {}) { this.dataFactory = context.dataFactory ?? new DataFactory({ blankNodePrefix: 'g_' }); - this.parser = sparql11ParserBuilder.consumeToParser({ - tokenVocabulary: l.sparql11Tokens.build(), + this.parser = adjustBuilder.consumeToParser({ + tokenVocabulary: l11.sparql11Tokens.addBefore(l11.a, lex.BuiltInAdjust).build(), }, { parseMode: new Set([ g11.canParseVars, g11.canCreateBlankNodes ]), ...context, diff --git a/engines/engine-sparql-1-1-adjust/package.json b/engines/engine-sparql-1-1-adjust/package.json index 4937e70..fa753eb 100644 --- a/engines/engine-sparql-1-1-adjust/package.json +++ b/engines/engine-sparql-1-1-adjust/package.json @@ -17,6 +17,7 @@ "files": [ "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/engines/engine-sparql-1-1-adjust/test/statics.test.ts b/engines/engine-sparql-1-1-adjust/test/statics.test.ts index c4dc276..befaa8d 100644 --- a/engines/engine-sparql-1-1-adjust/test/statics.test.ts +++ b/engines/engine-sparql-1-1-adjust/test/statics.test.ts @@ -4,7 +4,7 @@ import {positiveTest, importSparql11NoteTests} from "@traqula/test-utils"; import {DataFactory} from "rdf-data-factory"; import {BaseQuad} from "@rdfjs/types"; -describe('a SPARQL 1.1 parser', () => { +describe('a SPARQL 1.1 + adjust parser', () => { const parser = new Sparql11AdjustParser({ prefixes: { ex: 'http://example.org/' }}); beforeEach(() => { parser._resetBlanks(); @@ -27,4 +27,14 @@ describe('a SPARQL 1.1 parser', () => { } importSparql11NoteTests(parser, new DataFactory()); + + it('parses ADJUST function', ({expect}) => { + const query = ` +SELECT ?s ?p (ADJUST(?o, "-PT10H"^^) as ?adjusted) WHERE { + ?s ?p ?o +} +`; + const res: unknown = parser.parse(query); + expect(res).toMatchObject({}); + }) }); \ No newline at end of file diff --git a/engines/engine-sparql-1-1/README.md b/engines/engine-sparql-1-1/README.md new file mode 100644 index 0000000..65abb3b --- /dev/null +++ b/engines/engine-sparql-1-1/README.md @@ -0,0 +1,79 @@ +# TRAQULA parser engine for SPARQL 1.1 + +TRAQULA Sparql 1.1 is a SPARQL 1.1 query parser for TypeScript. + + +## Installation + +```bash +npm install @traqula/engine-sparql-1-1 +``` + +or + +```bash +yarn add @traqula/engine-sparql-1-1 +``` + +## Import + +Either through ESM import: + +```javascript +import { Sparql11Parser } from '@traqula/engine-sparql-1-1'; +``` + +_or_ CJS require: + +```javascript +const Sparql11Parser = require('@traqula/engine-sparql-1-1').Sparql11Parser; +``` + +## Usage + +This package contains a `Sparql11Parser` that is able to parse SPARQL 1.1 queries: + +```typescript +const parser = new Sparql11Parser(); +const abstractSyntaxTree = parser.parse('SELECT * { ?s ?p ?o }'); +``` + +The package also contains multiple parserBuilders. +These builders can be used either to consume to a parser, +or to usage as a starting point for your own grammar. + +### Consuming parserBuilder to parser + +At the core of TRAQULA, parser are constructed of multiple parser rules that have been consumed by the builder. +This consumption returns a parser that can parse strings starting from any grammar rule. + +The `sparql11ParserBuilder` for example contains both the rules `queryOrUpdate` and `path` (among many others). +The consumption of `sparql11ParserBuilder` will thus return an object that has function `queryOrUpdate` and `path`. +Calling those function with a string will cause that string to be parsed using the appropriate rule as a starting rule. + +```typescript +const parser: { + queryOrUpdate: (input: string) => SparqlQuery; + path: (input: string) => PropertyPath | IriTerm; +} = sparql11ParserBuilder.consumeToParser({ + tokenVocabulary: l.sparql11Tokens.build(), +}, { + parseMode: new Set([ gram.canParseVars, gram.canCreateBlankNodes ]), + dataFactory: new DataFactory(), +}); +``` + +### Constructing a new grammar from an existing one + +The builders can also be used to construct new parsers. +As an example the `triplesBlockParserBuilder` is created by merging the `objectListBuilder` with some new rules. + +## Configuration + +Optionally, the following parameters can be set in the `Sparql11Parser` constructor: + +* `dataFactory`: A custom [RDFJS DataFactory](http://rdf.js.org/#datafactory-interface) to construct terms and triples. _(Default: `require('@rdfjs/data-model')`)_ +* `baseIRI`: An initial default base IRI. _(Default: none)_ +* `prefixes`: An initial map of prefixes +* `skipValidation`: Can be used to disable the validation that used variables in a select clause are in scope. + diff --git a/engines/engine-sparql-1-1/package.json b/engines/engine-sparql-1-1/package.json index 33104b3..6858b69 100644 --- a/engines/engine-sparql-1-1/package.json +++ b/engines/engine-sparql-1-1/package.json @@ -22,6 +22,7 @@ "files": [ "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/engines/engine-sparql-1-2/README.md b/engines/engine-sparql-1-2/README.md new file mode 100644 index 0000000..6d49c7b --- /dev/null +++ b/engines/engine-sparql-1-2/README.md @@ -0,0 +1,42 @@ +# TRAQULA parser engine for SPARQL 1.2 + +TRAQULA Sparql 1.2 is a SPARQL 1.2 query parser for TypeScript. +It is a grammar extension of [TRAQULA engine-sparql-1-1](https://github.com/comunica/traqula/tree/main/engines/engine-sparql-1-1) + +## Installation + +```bash +npm install @traqula/engine-sparql-1-1 +``` + +or + +```bash +yarn add @traqula/engine-sparql-1-1 +``` + +## Import + +Either through ESM import: + +```javascript +import { Sparql12Parser } from '@traqula/engine-sparql-1-2'; +``` + +_or_ CJS require: + +```javascript +const Sparql12Parser = require('@traqula/engine-sparql-1-2').Sparql12Parser; +``` + +## Usage + +This package contains a `Sparql12Parser` that is able to parse SPARQL 1.2 queries: + +```typescript +const parser = new Sparql12Parser(); +const abstractSyntaxTree = parser.parse('SELECT * { ?s ?p ?o }'); +``` + +This parser is a simple grammar extension to the [engine-sparql-1-1](https://github.com/comunica/traqula/tree/main/engines/engine-sparql-1-1). +As such, most, if not all, documentation of that parser holds for this one too. \ No newline at end of file diff --git a/engines/engine-sparql-1-2/package.json b/engines/engine-sparql-1-2/package.json index 164c5c5..0543302 100644 --- a/engines/engine-sparql-1-2/package.json +++ b/engines/engine-sparql-1-2/package.json @@ -22,6 +22,7 @@ "files": [ "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/package.json b/package.json index 881aef2..4fcede1 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,6 @@ "spec:all": "yarn workspaces foreach --all -pt --include \"engines/*\" run spec:all", "spec:earl": "yarn workspaces foreach --all -pt --include \"engines/*\" run spec:earl" }, - "dependencies": { - "chevrotain": "^11.0.3", - "rdf-data-factory": "^2.0.1" - }, "devDependencies": { "@rdfjs/types": "^2.0.0", "@types/sparqljs": "^3.1.12", diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..1314be6 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,115 @@ +# TRAQULA core package + +TRAQULA core contains core components of traqula. +Most importantly, its `lexer-builder` and `grammar-builder`. +This library heavily relies on the amazing [chevrotain package](https://chevrotain.io/docs/). +Knowing the basics of that package will allow you to quickly generate your own grammars + +## Installation + +```bash +npm install @traqula/core +``` + +or + +```bash +yarn add @traqula/core +``` + +## Usage + +Each parser contains two steps: +1. a lexer +2. a grammar + abstract syntax tree generation step. + +Sometimes grammar definitions and abstract syntax tree generation is split into separate steps. +In this library, we choose to keep the two together. + +### Lexer Builder + +To tackle the first step, a lexer should be created. +This is a system that separates different groups of characters into annotated groups. +In human language for example the sentence 'I eat apples' is lexed into different groups called **tokens** namely `words` and `spaces`: +`I`, ` `, `eat`, ` `, `apples`. + +To create a token definition, you use the provided function `createToken` like: +```typescript +const select = createToken({ name: 'Select', pattern: /select/i, label: 'SELECT' }); +``` + +Lexer definitions are then put in a list and when a lexer is build, the lexer will match a string to the first token in the list that matches. +Note that the order of definitions in the list is thus essential. + +We therefore use a `lexer-builder` which allows you to easily: +1. change the order of lexer rules, +2. and create a new lexer staring from an existing one. + +Creating a builder is as easy as: + +```typescript +const sparql11Tokens = LexerBuilder.create( [select, describe]); +``` + +A new lexer can be created from an existing one by calling: +```typescript +const sparql11AdjustTokens = sparql11Tokens.addBefore(select, BuiltInAdjust); +``` + +### Grammar Builder + +The grammar builder is used to link together grammar rules such that they can be converted into a parser. +Grammar rule definitions come in the form of `RuleDef` objects. +Each `RuleDef` object contains its name and its returnType. +Optionally, it can also contain arguments that should be provided to the SUBRULE calls. +A simple example of a grammar rule is the rule bellow that allows you to parse booleanLiterals. + +```typescript +/** + * Parses a boolean literal. + * [[134]](https://www.w3.org/TR/sparql11-query/#rBooleanLiteral) + */ +export const booleanLiteral: RuleDef<'booleanLiteral', LiteralTerm> = { + name: 'booleanLiteral', + impl: ({ CONSUME, OR, context }) => () => OR([ + { ALT: () => context.dataFactory.literal( + CONSUME(l.true_).image.toLowerCase(), + context.dataFactory.namedNode(CommonIRIs.BOOLEAN), + ) }, + { ALT: () => context.dataFactory.literal( + CONSUME(l.false_).image.toLowerCase(), + context.dataFactory.namedNode(CommonIRIs.BOOLEAN), + ) }, + ]), + }; +``` + +The `impl` member of `RuleDef` is a function that receives: +1. essential functions to create a grammar rule (capitalized members), +2. a context object that can be used by the rules, +3. a cache object ([WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)) that can be used to cache the creation of long lists in the parser. + +As a warning, we advise against unpacking the context entry in the function definition itself. +Doing so will cause the unpacked entries to be captured in the closure which will cause unwanted behaviour since the context entry can be reset between runs. + +The result of an `impl` call is a function called a `rule`. +Rules can be [parameterized](https://chevrotain.io/docs/features/parameterized_rules.html), although I have not found a scenario where that is usefully. +Personally I create a function that can be used to create multiple `RuleDef` objects. +The result of a rule should match the type provided in the `RuleDef` definition, and is the result of a call of `SUBRULE` with that rule. + +### Patching rules + +When a rule definition calls to a subrule using `SUBRULE(mySub)`, the implementation itself is not necessarily called. +That is because the SUBRULE function will call the function with the same name as `mySub` that is present in the current grammarBuilder. + +A builder is thus free to override definitions as it pleases. Doing so does however **break the types** and should thus only be done with care. +An example patch is: + +```typescript +const myBuilder = Builder + .createBuilder( [selectOrDescribe, selectRule, describeRule]) + .patchRule(selectRuleAlternative); +``` + +When `selectOrDescribe` calls what it thinks to be `selectRule`, +it will instead call `selectRuleAlternative` since it overwrote the function `selectRule` with the same name. diff --git a/packages/core/package.json b/packages/core/package.json index 0e7d563..76b77c2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -22,6 +22,7 @@ "files": [ "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/packages/rules-sparal-1-1-adjust/package.json b/packages/rules-sparal-1-1-adjust/package.json index 8dad1c8..d6b58a8 100644 --- a/packages/rules-sparal-1-1-adjust/package.json +++ b/packages/rules-sparal-1-1-adjust/package.json @@ -16,6 +16,7 @@ "files": [ "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/packages/rules-sparql-1-1/package.json b/packages/rules-sparql-1-1/package.json index fd63c63..1d11759 100644 --- a/packages/rules-sparql-1-1/package.json +++ b/packages/rules-sparql-1-1/package.json @@ -16,6 +16,7 @@ "files": [ "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/packages/rules-sparql-1-2/package.json b/packages/rules-sparql-1-2/package.json index c839def..94070bb 100644 --- a/packages/rules-sparql-1-2/package.json +++ b/packages/rules-sparql-1-2/package.json @@ -16,6 +16,7 @@ "files": [ "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 133df62..503cf05 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -17,6 +17,7 @@ "lib/statics/**", "lib/**/*.d.ts", "lib/**/*.js", + "lib/**/*.cjs", "lib/**/*.js.map" ], "engines": { diff --git a/yarn.lock b/yarn.lock index 73e416d..adebea6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3578,10 +3578,8 @@ __metadata: "@rdfjs/types": "npm:^2.0.0" "@types/sparqljs": "npm:^3.1.12" "@vitest/coverage-v8": "npm:2.1.8" - chevrotain: "npm:^11.0.3" esbuild: "npm:^0.24.0" eslint: "npm:^8.57.0" - rdf-data-factory: "npm:^2.0.1" rdf-test-suite: "npm:^2.0.0" sparqljs: "npm:^3.7.3" typescript: "npm:^5.3.3"