Skip to content

Commit 10b6ae7

Browse files
committed
Iterate, pop, push + tests
1 parent 3eec175 commit 10b6ae7

File tree

8 files changed

+437
-3
lines changed

8 files changed

+437
-3
lines changed

src/IndexerInterceptor.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export class IndexerInterceptor<T> implements ProxyHandler<T[]> {
2+
get(target: T[], property: string | symbol, receiver: any): T | undefined {
3+
if (notNumeric(property)) {
4+
return Reflect.get(target, property, receiver)
5+
}
6+
7+
return target.at(Number.parseInt(property))
8+
}
9+
10+
set(target: T[], property: string | symbol, value: T, receiver: any): boolean {
11+
if (notNumeric(property)) {
12+
return Reflect.set(target, property, value, receiver)
13+
}
14+
15+
const i = Number.parseInt(property)
16+
target.fill(value, i, i + 1)
17+
return true
18+
}
19+
20+
deleteProperty(target: T[], property: string | symbol): boolean {
21+
if (notNumeric(property)) {
22+
return Reflect.deleteProperty(target, property)
23+
}
24+
25+
// Elements in this array cannot be deleted because the underlying RDF Collection does not support sparse arrays
26+
return false
27+
}
28+
}
29+
30+
function notNumeric(property: string | symbol): property is symbol {
31+
// TODO: Decide properly
32+
return typeof property === "symbol" || isNaN(parseInt(property))
33+
}

src/ListItem.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { TermWrapper } from "./TermWrapper.js"
2+
import type { Term } from "@rdfjs/types"
3+
import { ValueMapping } from "./mapping/ValueMapping.js"
4+
import { TermMapping } from "./mapping/TermMapping.js"
5+
import { RDF } from "./vocabulary/RDF.js"
6+
import type { IValueMapping } from "./type/IValueMapping"
7+
import type { ITermMapping } from "./type/ITermMapping.js"
8+
9+
export class ListItem<T> extends TermWrapper {
10+
constructor(term: TermWrapper, private readonly valueMapping: IValueMapping<T>, private readonly termMapping: ITermMapping<T>) {
11+
super(term.term, term.dataset, term.factory)
12+
}
13+
14+
private get firstRaw(): Term | undefined {
15+
return this.singularNullable(RDF.first, ValueMapping.asIs)
16+
}
17+
18+
private set firstRaw(value: Term | undefined) {
19+
this.overwriteNullable(RDF.first, value, TermMapping.asIs)
20+
}
21+
22+
private get restRaw(): Term | undefined {
23+
return this.singularNullable(RDF.rest, ValueMapping.asIs)
24+
}
25+
26+
private set restRaw(value: Term | undefined) {
27+
this.overwriteNullable(RDF.rest, value, TermMapping.asIs)
28+
}
29+
30+
public get isListItem(): boolean {
31+
return this.firstRaw !== undefined && this.restRaw !== undefined
32+
}
33+
34+
public get first(): T {
35+
return this.singular(RDF.first, this.valueMapping)
36+
}
37+
38+
public set first(value: T) {
39+
this.overwrite(RDF.first, value, this.termMapping)
40+
}
41+
42+
public get rest(): ListItem<T> {
43+
return this.singular(RDF.rest, w => new ListItem(w, this.valueMapping, this.termMapping))
44+
}
45+
46+
public pop(): T {
47+
try {
48+
return this.first
49+
} finally {
50+
this.firstRaw = undefined
51+
this.restRaw = this.factory.namedNode(RDF.nil)
52+
}
53+
}
54+
55+
public push(...items: T[]) {
56+
let current: ListItem<T> = this
57+
58+
for (const item of items) {
59+
current.restRaw = this.factory.blankNode()
60+
61+
current.rest.first = item
62+
current.rest.restRaw = this.factory.namedNode(RDF.nil)
63+
64+
current = current.rest
65+
}
66+
67+
return items.length
68+
}
69+
}

src/RdfList.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { TermWrapper } from "./TermWrapper.js"
2+
import type { IValueMapping } from "./type/IValueMapping.js"
3+
import type { ITermMapping } from "./type/ITermMapping.js"
4+
import { IndexerInterceptor } from "./IndexerInterceptor.js"
5+
import { ListItem } from "./ListItem.js"
6+
7+
export class RdfList<T> implements Array<T> {
8+
constructor(private readonly anchor: TermWrapper, private readonly valueMapping: IValueMapping<T>, private readonly termMapping: ITermMapping<T>) {
9+
// TODO: Singleton interceptor?
10+
return new Proxy(this, new IndexerInterceptor<T>)
11+
}
12+
13+
// Never invoked, intercepted by proxy
14+
[n: number]: T
15+
16+
get [Symbol.unscopables](): { [K in keyof any[]]?: boolean } {
17+
throw new Error("not implemented")
18+
}
19+
20+
get length(): number {
21+
return [...this.listItems()].length
22+
}
23+
24+
set length(_: number) {
25+
throw new Error("this array is based on an RDF Collection. Its length cannot be modified like this.")
26+
}
27+
28+
[Symbol.iterator](): ArrayIterator<T> {
29+
return this.values()
30+
}
31+
32+
at(index: number): T | undefined {
33+
return [...this].at(index)
34+
}
35+
36+
concat(...items: Array<ConcatArray<T> | T>): T[] {
37+
throw new Error("not implemented")
38+
}
39+
40+
copyWithin(target: number, start: number, end?: number): this {
41+
throw new Error("not implemented")
42+
}
43+
44+
entries(): ArrayIterator<[number, T]> {
45+
return [...this].entries()
46+
}
47+
48+
every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[] {
49+
return [...this].every(predicate, thisArg)
50+
}
51+
52+
fill(value: T, start?: number, end?: number): this {
53+
throw new Error("not implemented")
54+
}
55+
56+
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[] {
57+
return [...this].filter(predicate, thisArg)
58+
}
59+
60+
find<S extends T>(predicate: (value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined {
61+
return [...this].find(predicate, thisArg)
62+
}
63+
64+
findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number {
65+
return [...this].findIndex(predicate, thisArg)
66+
}
67+
68+
flat<A, D extends number = 1>(depth?: D): FlatArray<A, D>[] {
69+
throw new Error("not implemented")
70+
}
71+
72+
flatMap<U, This = undefined>(callback: (this: This, value: T, index: number, array: T[]) => (ReadonlyArray<U> | U), thisArg?: This): U[] {
73+
return [...this].flatMap(callback, thisArg)
74+
}
75+
76+
forEach(callback: (value: T, index: number, array: T[]) => void, thisArg?: any): void {
77+
[...this].forEach(callback, thisArg)
78+
}
79+
80+
includes(searchElement: T, fromIndex?: number): boolean {
81+
return [...this].includes(searchElement, fromIndex)
82+
}
83+
84+
indexOf(searchElement: T, fromIndex?: number): number {
85+
return [...this].indexOf(searchElement, fromIndex)
86+
}
87+
88+
join(separator?: string): string {
89+
return [...this].join(separator)
90+
}
91+
92+
keys(): ArrayIterator<number> {
93+
return [...this.listItems()].keys()
94+
}
95+
96+
lastIndexOf(searchElement: T, fromIndex?: number): number {
97+
return [...this].lastIndexOf(searchElement, fromIndex)
98+
}
99+
100+
map<U>(callback: (value: T, index: number, array: T[]) => U, thisArg?: any): U[] {
101+
return [...this].map(callback, thisArg)
102+
}
103+
104+
pop(): T | undefined {
105+
return [...this.listItems()].at(-1)?.pop()
106+
}
107+
108+
push(...items: T[]): number {
109+
if (this.length === 0) {
110+
throw new Error("Adding to empty is not implemented yet")
111+
}
112+
113+
return [...this.listItems()].at(-1)!.push(...items)
114+
}
115+
116+
reduce<U>(callback: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue?: U): U {
117+
throw new Error("not implemented")
118+
}
119+
120+
reduceRight<U>(callback: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue?: U): U {
121+
throw new Error("not implemented")
122+
}
123+
124+
reverse(): T[] {
125+
throw new Error("not implemented")
126+
}
127+
128+
shift(): T | undefined {
129+
throw new Error("not implemented")
130+
}
131+
132+
slice(start?: number, end?: number): T[] {
133+
return [...this].slice(start, end)
134+
}
135+
136+
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean {
137+
return [...this].some(predicate, thisArg)
138+
}
139+
140+
sort(compareFn?: (a: T, b: T) => number): this {
141+
throw new Error("not implemented")
142+
}
143+
144+
splice(start: number, deleteCount?: number, ...items: T[]): T[] {
145+
throw new Error("not implemented")
146+
}
147+
148+
unshift(...items: T[]): number {
149+
throw new Error("not implemented")
150+
}
151+
152+
* values(): ArrayIterator<T> {
153+
for (const item of this.listItems()) {
154+
yield item.first
155+
}
156+
}
157+
158+
private* listItems(): Iterable<ListItem<T>> {
159+
let item = new ListItem(this.anchor, this.valueMapping, this.termMapping)
160+
while (item.isListItem) {
161+
yield item
162+
163+
item = item.rest
164+
}
165+
}
166+
}

src/mapping/ObjectMapping.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { IValueMapping } from "../type/IValueMapping.js"
22
import type { ITermWrapperConstructor } from "../type/ITermWrapperConstructor.js"
33
import type { TermWrapper } from "../TermWrapper.js"
4+
import type { ITermMapping } from "../type/ITermMapping.js"
5+
import { RdfList } from "../RdfList.js"
46

57

68
/*
@@ -18,4 +20,8 @@ export namespace ObjectMapping {
1820
export function as<T>(constructor: ITermWrapperConstructor<T>): IValueMapping<T> {
1921
return (termWrapper: TermWrapper) => new constructor(termWrapper.term, termWrapper.dataset, termWrapper.factory)
2022
}
23+
24+
export function asList<T>(valueMapping: IValueMapping<T>, termMapping: ITermMapping<T>): IValueMapping<T[]> {
25+
return w => new RdfList(w, valueMapping, termMapping)
26+
}
2127
}

src/mapping/TermMapping.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DataFactory, DatasetCore } from "@rdfjs/types"
1+
import type { DataFactory, DatasetCore, Term } from "@rdfjs/types"
22
import type { ILangString } from "../type/ILangString.js"
33

44
import { TermWrapper } from "../TermWrapper.js"
@@ -37,4 +37,8 @@ export namespace TermMapping {
3737
export function stringToLiteral(value: string, dataset: DatasetCore, factory: DataFactory): TermWrapper | undefined {
3838
return new TermWrapper(factory.literal(value), dataset, factory)
3939
}
40+
41+
export function asIs(value: Term, dataset: DatasetCore, factory: DataFactory): TermWrapper | undefined {
42+
return new TermWrapper(value, dataset, factory)
43+
}
4044
}

src/mapping/ValueMapping.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Literal } from "@rdfjs/types"
1+
import type { Literal, Term } from "@rdfjs/types"
22
import type { ILangString } from "../type/ILangString.js"
33
import type { TermWrapper } from "../TermWrapper.js"
44

@@ -46,4 +46,8 @@ export namespace ValueMapping {
4646
export function iriOrBlankNodeToString(termWrapper: TermWrapper): string {
4747
return termWrapper.term.value
4848
}
49+
50+
export function asIs(termWrapper: TermWrapper): Term {
51+
return termWrapper.term
52+
}
4953
}

src/vocabulary/RDF.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
export const RDF = {
22
langString: "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString",
3-
type: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
3+
type: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
4+
first: "http://www.w3.org/1999/02/22-rdf-syntax-ns#first",
5+
rest: "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest",
6+
nil: "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil",
47
} as const

0 commit comments

Comments
 (0)