Skip to content

Commit 96a6440

Browse files
committed
use enum approach to sourceLocation
1 parent a8307cf commit 96a6440

18 files changed

Lines changed: 795 additions & 594 deletions

engines/parser-sparql-1-1/test/objectListParser.test.ts

Lines changed: 222 additions & 193 deletions
Large diffs are not rendered by default.

engines/parser-sparql-1-1/test/triplesTemplateParser.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { SparqlContext, Triple, Wrap } from '@traqula/rules-sparql-1-1';
1+
import type { SparqlContext, TripleNesting, Wrap } from '@traqula/rules-sparql-1-1';
22
import { TraqulaFactory, completeParseContext, lex as l } from '@traqula/rules-sparql-1-1';
33
import { describe, it } from 'vitest';
44
import { triplesTemplateParserBuilder } from '../lib';
55

66
describe('a SPARQL 1.1 objectlist parser', () => {
77
const F = new TraqulaFactory();
8-
function parse(query: string, context: Partial<SparqlContext>): Wrap<Triple[]> {
8+
function parse(query: string, context: Partial<SparqlContext>): Wrap<TripleNesting[]> {
99
const parser = triplesTemplateParserBuilder.consumeToParser({
1010
tokenVocabulary: l.sparql11Tokens.build(),
1111
});

packages/core/lib/CoreFactory.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { IToken } from 'chevrotain';
2+
3+
import type {
4+
SourceLocation,
5+
SourceLocationNodeAutoGenerate,
6+
SourceLocationNodeReplace,
7+
SourceLocationNoMaterialize,
8+
SourceLocationSource,
9+
SourceLocationStringReplace,
10+
Node,
11+
} from './nodeTypings';
12+
13+
export class CoreFactory {
14+
public sourceLocation(...elements: (IToken | SourceLocation)[]): SourceLocation {
15+
const filtered =
16+
elements.filter(element => !this.isSourceLocation(element) || this.isSourceLocationSource(element));
17+
if (filtered.length === 0) {
18+
return this.sourceLocationNoMaterialize();
19+
}
20+
const first = filtered[0];
21+
const last = filtered.at(-1)!;
22+
return {
23+
sourceLocationType: 'source',
24+
start: this.isSourceLocationSource(first) ? first.start : first.startOffset,
25+
end: this.isSourceLocationSource(last) ? last.end : (last.endOffset! + 1),
26+
};
27+
}
28+
29+
public sourceLocationNoMaterialize(): SourceLocationNoMaterialize {
30+
return { sourceLocationType: 'noMaterialize' };
31+
}
32+
33+
public isSourceLocation(loc: object): loc is SourceLocation {
34+
return 'sourceLocationType' in loc;
35+
}
36+
37+
public sourceLocationSource(start: number, end: number): SourceLocationSource {
38+
return {
39+
sourceLocationType: 'source',
40+
start,
41+
end,
42+
};
43+
}
44+
45+
public isSourceLocationSource(loc: object): loc is SourceLocationSource {
46+
return this.isSourceLocation(loc) && loc.sourceLocationType === 'source';
47+
}
48+
49+
public isSourceLocationStringReplace(loc: object): loc is SourceLocationStringReplace {
50+
return this.isSourceLocation(loc) && loc.sourceLocationType === 'stringReplace';
51+
}
52+
53+
public sourceLocationNodeReplace(start: number, end: number): SourceLocationNodeReplace {
54+
return {
55+
sourceLocationType: 'nodeReplace',
56+
start,
57+
end,
58+
};
59+
}
60+
61+
public isSourceLocationNodeReplace(loc: object): loc is SourceLocationNodeReplace {
62+
return this.isSourceLocation(loc) && loc.sourceLocationType === 'nodeReplace';
63+
}
64+
65+
public isSourceLocationNodeAutoGenerate(loc: object): loc is SourceLocationNodeAutoGenerate {
66+
return this.isSourceLocation(loc) && loc.sourceLocationType === 'autoGenerate';
67+
}
68+
69+
public nodeShouldPrint(node: Node): boolean {
70+
return this.isSourceLocationNodeReplace(node.loc) ||
71+
this.isSourceLocationNodeAutoGenerate(node.loc);
72+
}
73+
74+
public printFilter(node: Node, callback: () => void): void {
75+
if (this.nodeShouldPrint(node)) {
76+
callback();
77+
}
78+
}
79+
80+
public isSourceLocationNoMaterialize(loc: object): loc is SourceLocationNoMaterialize {
81+
return this.isSourceLocation(loc) && loc.sourceLocationType === 'noMaterialize';
82+
}
83+
}

packages/core/lib/RangeArithmetic.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ export type Range<A extends number = number, B extends number = number> = [A, B]
55

66
export class RangeArithmetic {
77
public ranges: Range[] = [];
8+
private readonly initRange: Range;
89
public constructor(start: number, end: number) {
9-
this.ranges.push([ start, end ]);
10+
this.initRange = RangeArithmetic.validate(start, end);
11+
this.ranges.push(this.initRange);
1012
}
1113

1214
private static validate(...range: Range): Range {
@@ -21,7 +23,7 @@ export class RangeArithmetic {
2123
const [ sMinus, eMinus ] = RangeArithmetic.validate(...range);
2224

2325
return included.flatMap(([ sCur, eCur ]) => {
24-
// Split in half
26+
// Split in half
2527
if (sCur < sMinus && eMinus < eCur) {
2628
return [[ sCur, sMinus ], [ eMinus, eCur ]];
2729
}
@@ -43,6 +45,16 @@ export class RangeArithmetic {
4345
return this;
4446
}
4547

48+
public negate(): this {
49+
// Can be optimized
50+
let iter = [ this.initRange ];
51+
for (const range of this.ranges) {
52+
iter = RangeArithmetic.substractRange(iter, ...range);
53+
}
54+
this.ranges = iter;
55+
return this;
56+
}
57+
4658
public projection(...range: Range): Range[] {
4759
const [ sProj, eProj ] = range;
4860
if (sProj >= eProj) {

packages/core/lib/generator-builder/builderTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ export type GenRulesToObject<
2727

2828
export type GeneratorFromRules<Context, Names extends string, RuleDefs extends GenRuleMap<Names>> = {
2929
[K in Names]: RuleDefs[K] extends GeneratorRule<Context, K, infer RET, infer ARGS> ?
30-
(input: RET, context: Context & { skipRanges: [number, number][] }, args: ARGS) => string : never
30+
(input: RET, context: Context & { origSource: string; offset?: number }, args: ARGS) => string : never
3131
};

packages/core/lib/generator-builder/generatorBuilder.ts

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { RangeArithmetic } from '../RangeArithmetic';
2-
import type { Node, CheckOverlap } from '../utils';
1+
import { CoreFactory } from '../CoreFactory';
2+
import type { Node } from '../nodeTypings';
3+
import type { CheckOverlap } from '../utils';
34
import type { GeneratorFromRules, GenRuleMap, GenRulesToObject, GenNamesFromList } from './builderTypes';
45
import type { GeneratorRule, RuleDefArg } from './generatorTypes';
56

@@ -179,21 +180,21 @@ export class GeneratorBuilder<Context, Names extends string, RuleDefs extends Ge
179180
}
180181

181182
export class Generator<Context, Names extends string, RuleDefs extends GenRuleMap<Names>> {
183+
protected readonly factory = new CoreFactory();
182184
protected __context: Context | undefined = undefined;
183-
protected readonly sourceStack: { source: string; generatedUntil: number; allowedRanges: RangeArithmetic }[] = [];
185+
protected origSource = '';
186+
protected generatedUntil = 0;
184187
protected readonly stringBuilder: string[] = [];
185-
protected readonly skipRanges: [number, number][] = [];
186188

187189
public constructor(protected rules: RuleDefs) {
188190
// eslint-disable-next-line ts/no-unnecessary-type-assertion
189191
for (const rule of <GeneratorRule[]> Object.values(rules)) {
190192
// Define function implementation
191193
this[<keyof (typeof this)> rule.name] =
192-
<any> ((input: any, context: Context & { skipRanges: [number, number][] }, args: any) => {
194+
<any> ((input: any, context: Context & { origSource: string; offset?: number }, args: any) => {
193195
this.stringBuilder.length = 0;
194-
this.sourceStack.length = 0;
195-
this.skipRanges.length = 0;
196-
this.skipRanges.push(...context.skipRanges);
196+
this.origSource = context.origSource;
197+
this.generatedUntil = context?.offset ?? 0;
197198
this.setContext(context);
198199

199200
this.subrule(rule, input, args);
@@ -216,56 +217,41 @@ export class Generator<Context, Names extends string, RuleDefs extends GenRuleMa
216217
if (!def) {
217218
throw new Error(`Rule ${cstDef.name} not found`);
218219
}
219-
if (ast.loc && ast.loc.noStringManifestation) {
220+
if (this.factory.isSourceLocationNoMaterialize(ast.loc)) {
220221
return;
221222
}
222-
if (ast.loc?.source) {
223-
this.pushSource(ast.loc.source);
223+
if (this.factory.isSourceLocationStringReplace(ast.loc)) {
224+
this.catchup(ast.loc.start);
225+
this.print(ast.loc.newSource);
226+
this.generatedUntil = ast.loc.end;
227+
return;
224228
}
225-
if (ast.loc) {
229+
if (this.factory.isSourceLocationNodeReplace(ast.loc) || this.factory.isSourceLocationSource(ast.loc)) {
226230
this.catchup(ast.loc.start);
227231
}
232+
// If autoGenerate - do nothing
233+
234+
// Do call generation
228235
def.gImpl({
229236
SUBRULE: this.subrule,
230237
PRINT: this.print,
231238
PRINT_WORD: this.printWord,
232239
CATCHUP: this.catchup,
233-
PUSH_SOURCE: this.pushSource,
234-
POP_SOURCE: this.popSource,
235240
})(ast, this.getSafeContext(), arg);
236-
// TODO: You cannot close at end because scopes dont work like that for blankNodes like `[ ... ]`
237-
// That than gives errors since you don't close at the time you will insert something new.
238-
// if (ast.loc) {
239-
// this.catchup(ast.loc.end);
240-
// }
241-
if (ast.loc?.source) {
242-
this.popSource();
243-
}
244-
};
245241

246-
protected readonly catchup: RuleDefArg['CATCHUP'] = (until) => {
247-
const currentSource = this.sourceStack.at(-1);
248-
if (!currentSource) {
249-
throw new Error('No source to catchup to');
250-
}
251-
const { source, generatedUntil, allowedRanges } = currentSource;
252-
for (const range of allowedRanges.projection(generatedUntil, until)) {
253-
this.stringBuilder.push(source.slice(...range));
242+
if (this.factory.isSourceLocationNodeReplace(ast.loc)) {
243+
this.generatedUntil = ast.loc.end;
244+
} else if (this.factory.isSourceLocationSource(ast.loc)) {
245+
this.catchup(ast.loc.end);
254246
}
255-
currentSource.generatedUntil = Math.max(generatedUntil, until);
256247
};
257248

258-
protected readonly pushSource: RuleDefArg['PUSH_SOURCE'] = (source) => {
259-
const allowedRanges = new RangeArithmetic(0, source.length);
260-
for (const skip of this.skipRanges) {
261-
allowedRanges.subtract(...skip);
249+
protected readonly catchup: RuleDefArg['CATCHUP'] = (until) => {
250+
const start = this.generatedUntil;
251+
if (start < until) {
252+
this.stringBuilder.push(this.origSource.slice(start, until));
262253
}
263-
this.sourceStack.push({ source, generatedUntil: 0, allowedRanges });
264-
};
265-
266-
protected readonly popSource: RuleDefArg['POP_SOURCE'] = () => {
267-
this.catchup(Number.MAX_VALUE);
268-
this.sourceStack.pop();
254+
this.generatedUntil = Math.max(this.generatedUntil, until);
269255
};
270256

271257
protected readonly print: RuleDefArg['PRINT'] = (...args) => {

packages/core/lib/generator-builder/generatorTypes.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Node } from '../utils';
1+
import type { Node } from '../nodeTypings';
22

33
/**
44
* Type used to declare generator rules.
@@ -13,9 +13,9 @@ export type GeneratorRule<
1313
*/
1414
NameType extends string = string,
1515
/**
16-
* Type that will be returned after a correct parse of this rule.
17-
* This type will be the return type of calling SUBRULE with this grammar rule.
18-
* Generation typically happens on a per AST node basis.
16+
* Type that of the AST that we will generate the string for.
17+
* This type will be the provided when calling SUBRULE with this generator rule.
18+
* Generation happens on a per AST node basis.
1919
* The core will implement the generation as such. If this ever changes, we will cross that bridge when we get there.
2020
*/
2121
ReturnType = any,
@@ -34,6 +34,4 @@ export interface RuleDefArg {
3434
PRINT: (...args: string[]) => void;
3535
PRINT_WORD: (...args: string[]) => void;
3636
CATCHUP: (until: number) => void;
37-
PUSH_SOURCE: (source: string) => void;
38-
POP_SOURCE: () => void;
3937
}

packages/core/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ export * from './parser-builder/ruleDefTypes';
1212
// Export general types
1313
export * from './utils';
1414
export * from './RangeArithmetic';
15+
export * from './CoreFactory';
16+
export * from "./nodeTypings";

packages/core/lib/nodeTypings.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* A AST node. Nodes are indexable by their types.
3+
* When generating, the SUBRULES called should be located within the current location range.
4+
*/
5+
export type Node = {
6+
type: string;
7+
/**
8+
* Location undefined means the node does have a string representation, but it was not clarified.
9+
* This happens when an AST node is patched by a client of the lib.
10+
*/
11+
loc: SourceLocation;
12+
};
13+
14+
export interface SourceLocationBase {
15+
sourceLocationType: string;
16+
}
17+
18+
export interface SourceLocationSource extends SourceLocationBase {
19+
sourceLocationType: 'source';
20+
start: number;
21+
end: number;
22+
}
23+
24+
/**
25+
* NoStringManifestation means the node does not have a string representation.
26+
* For example the literal '5' has an integer type (which is an AST node),
27+
* but the type does not have an associated string representation.
28+
* When set to true, the node will not be printed, start and end are meaningless in this case.
29+
*/
30+
export interface SourceLocationNoMaterialize extends SourceLocationBase {
31+
sourceLocationType: 'noMaterialize';
32+
}
33+
34+
export interface SourceLocationStringReplace extends SourceLocationBase {
35+
sourceLocationType: 'stringReplace';
36+
newSource: string;
37+
start: number;
38+
end: number;
39+
}
40+
41+
export interface SourceLocationNodeReplace extends SourceLocationBase {
42+
sourceLocationType: 'nodeReplace';
43+
start: number;
44+
end: number;
45+
}
46+
/**
47+
* Must have an ancestor of type {@link SourceLocationNodeReplace}
48+
*/
49+
export interface SourceLocationNodeAutoGenerate extends SourceLocationBase {
50+
sourceLocationType: 'autoGenerate';
51+
}
52+
53+
export type SourceLocation =
54+
| SourceLocationSource
55+
| SourceLocationNoMaterialize
56+
| SourceLocationStringReplace
57+
| SourceLocationNodeReplace
58+
| SourceLocationNodeAutoGenerate;

packages/core/lib/utils.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,3 @@ export function unCapitalize<T extends string>(str: T): Uncapitalize<T> {
77
return <Uncapitalize<T>> (str.charAt(0).toLowerCase() + str.slice(1));
88
}
99

10-
/**
11-
* A AST node. Nodes are indexable by their types.
12-
*/
13-
export type Node = {
14-
type: string;
15-
/**
16-
* Location undefined means the node does have a string representation, but it was not clarified.
17-
* This happens when an AST node is patched by a client of the lib.
18-
*/
19-
loc: undefined | SourceLocation;
20-
};
21-
22-
export type SourceLocation = {
23-
// When null, traverse the tree up until you find a non-null source
24-
source?: string | undefined;
25-
/**
26-
* NoStringManifestation means the node does not have a string representation.
27-
* For example the literal '5' has an integer type (which is an AST node),
28-
* but the type does not have an associated string representation.
29-
* When set to true, the node will not be printed, start and end are meaningless in this case.
30-
*/
31-
noStringManifestation?: true;
32-
start: number;
33-
end: number;
34-
};

0 commit comments

Comments
 (0)