@sprig-and-prose/sprig-universe 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +30 -0
- package/src/universe/graph.js +1619 -0
- package/src/universe/parser.js +1751 -0
- package/src/universe/scanner.js +240 -0
- package/src/universe/scene-manifest.js +856 -0
- package/src/universe/test-graph.js +157 -0
- package/src/universe/test-parser.js +61 -0
- package/src/universe/test-scanner.js +37 -0
- package/src/universe/universe.prose +169 -0
- package/src/universe/validator.js +862 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { scan } from './scanner.js';
|
|
5
|
+
import { parseUniverse, debugAst } from './parser.js';
|
|
6
|
+
import { buildGraph } from './graph.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// Load the universe.prose file
|
|
12
|
+
const prosePath = join(__dirname, 'universe.prose');
|
|
13
|
+
const proseContent = readFileSync(prosePath, 'utf-8');
|
|
14
|
+
|
|
15
|
+
console.log('=== Scanning universe.prose ===\n');
|
|
16
|
+
console.log(`File: ${prosePath}`);
|
|
17
|
+
console.log(`Content length: ${proseContent.length} characters\n`);
|
|
18
|
+
|
|
19
|
+
// Run the scanner
|
|
20
|
+
const tokens = scan(proseContent, prosePath);
|
|
21
|
+
|
|
22
|
+
// Print token summary
|
|
23
|
+
console.log(`Found ${tokens.length} tokens\n`);
|
|
24
|
+
|
|
25
|
+
// Run the parser
|
|
26
|
+
console.log('=== Parsing tokens ===\n');
|
|
27
|
+
const { ast, diags } = parseUniverse({
|
|
28
|
+
tokens,
|
|
29
|
+
sourceText: proseContent,
|
|
30
|
+
filePath: prosePath,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Print parser diagnostics
|
|
34
|
+
if (diags.length > 0) {
|
|
35
|
+
console.log(`Found ${diags.length} parser diagnostic(s):\n`);
|
|
36
|
+
diags.forEach((diag, index) => {
|
|
37
|
+
const { severity, message, source } = diag;
|
|
38
|
+
const loc = source
|
|
39
|
+
? `${source.file}:${source.start.line}:${source.start.col}`
|
|
40
|
+
: 'unknown location';
|
|
41
|
+
console.log(` ${index + 1}. [${severity}] ${message}`);
|
|
42
|
+
console.log(` at ${loc}\n`);
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
console.log('No parser diagnostics (parsing succeeded)\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Print AST summary
|
|
49
|
+
console.log('=== AST Summary ===\n');
|
|
50
|
+
console.log(`AST kind: ${ast.kind}`);
|
|
51
|
+
console.log(`Top-level declarations: ${ast.decls.length}`);
|
|
52
|
+
if (ast.source) {
|
|
53
|
+
console.log(`Source span: ${ast.source.start.line}:${ast.source.start.col} - ${ast.source.end.line}:${ast.source.end.col}`);
|
|
54
|
+
}
|
|
55
|
+
console.log('');
|
|
56
|
+
|
|
57
|
+
// Print AST tree using debugAst
|
|
58
|
+
console.log('=== AST Tree ===\n');
|
|
59
|
+
debugAst(ast);
|
|
60
|
+
console.log('');
|
|
61
|
+
|
|
62
|
+
// Run the graph builder
|
|
63
|
+
console.log('=== Building graph ===\n');
|
|
64
|
+
const fileAST = {
|
|
65
|
+
...ast,
|
|
66
|
+
sourceText: proseContent,
|
|
67
|
+
};
|
|
68
|
+
const graph = buildGraph(fileAST);
|
|
69
|
+
|
|
70
|
+
// Print graph diagnostics
|
|
71
|
+
if (graph.diagnostics.length > 0) {
|
|
72
|
+
console.log(`Found ${graph.diagnostics.length} graph diagnostic(s):\n`);
|
|
73
|
+
graph.diagnostics.forEach((diag, index) => {
|
|
74
|
+
const { severity, message, source } = diag;
|
|
75
|
+
const loc = source
|
|
76
|
+
? `${source.file}:${source.start.line}:${source.start.col}`
|
|
77
|
+
: 'unknown location';
|
|
78
|
+
console.log(` ${index + 1}. [${severity}] ${message}`);
|
|
79
|
+
console.log(` at ${loc}\n`);
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
console.log('No graph diagnostics (graph building succeeded)\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Print graph summary
|
|
86
|
+
console.log('=== Graph Summary ===\n');
|
|
87
|
+
console.log(`Version: ${graph.version}`);
|
|
88
|
+
console.log(`Universes: ${Object.keys(graph.universes).length}`);
|
|
89
|
+
if (Object.keys(graph.universes).length > 0) {
|
|
90
|
+
for (const [name, universe] of Object.entries(graph.universes)) {
|
|
91
|
+
console.log(` - ${name} (root: ${universe.root})`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.log(`Nodes: ${Object.keys(graph.nodes).length}`);
|
|
95
|
+
console.log(`Edges: ${graph.edges ? graph.edges.length : 0}`);
|
|
96
|
+
console.log(`Asserted edges: ${graph.edgesAsserted ? graph.edgesAsserted.length : 0}`);
|
|
97
|
+
console.log(`Repositories: ${graph.repositories ? Object.keys(graph.repositories).length : 0}`);
|
|
98
|
+
console.log(`References: ${graph.references ? Object.keys(graph.references).length : 0}`);
|
|
99
|
+
console.log(`Relationship declarations: ${graph.relationshipDecls ? Object.keys(graph.relationshipDecls).reduce((sum, univ) => sum + Object.keys(graph.relationshipDecls[univ]).length, 0) : 0}`);
|
|
100
|
+
console.log('');
|
|
101
|
+
|
|
102
|
+
// Print node summary
|
|
103
|
+
console.log('=== Node Summary ===\n');
|
|
104
|
+
const nodesByKind = {};
|
|
105
|
+
for (const [id, node] of Object.entries(graph.nodes)) {
|
|
106
|
+
const kind = node.kind || 'unknown';
|
|
107
|
+
if (!nodesByKind[kind]) {
|
|
108
|
+
nodesByKind[kind] = [];
|
|
109
|
+
}
|
|
110
|
+
nodesByKind[kind].push(node.name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const [kind, names] of Object.entries(nodesByKind)) {
|
|
114
|
+
console.log(`${kind}: ${names.length}`);
|
|
115
|
+
if (names.length <= 10) {
|
|
116
|
+
names.forEach(name => console.log(` - ${name}`));
|
|
117
|
+
} else {
|
|
118
|
+
names.slice(0, 10).forEach(name => console.log(` - ${name}`));
|
|
119
|
+
console.log(` ... and ${names.length - 10} more`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
console.log('');
|
|
123
|
+
|
|
124
|
+
// Print edge summary
|
|
125
|
+
if (graph.edges && graph.edges.length > 0) {
|
|
126
|
+
console.log('=== Edge Summary ===\n');
|
|
127
|
+
const edgesByVia = {};
|
|
128
|
+
for (const edge of graph.edges) {
|
|
129
|
+
const via = edge.via || 'unknown';
|
|
130
|
+
if (!edgesByVia[via]) {
|
|
131
|
+
edgesByVia[via] = 0;
|
|
132
|
+
}
|
|
133
|
+
edgesByVia[via]++;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const [via, count] of Object.entries(edgesByVia)) {
|
|
137
|
+
console.log(`${via}: ${count} edge(s)`);
|
|
138
|
+
}
|
|
139
|
+
console.log('');
|
|
140
|
+
|
|
141
|
+
// Print first few edges
|
|
142
|
+
console.log('=== Sample Edges (first 10) ===\n');
|
|
143
|
+
graph.edges.slice(0, 10).forEach((edge, index) => {
|
|
144
|
+
const fromNode = graph.nodes[edge.from];
|
|
145
|
+
const toNode = graph.nodes[edge.to];
|
|
146
|
+
const fromName = fromNode ? fromNode.name : edge.from;
|
|
147
|
+
const toName = toNode ? toNode.name : edge.to;
|
|
148
|
+
console.log(` ${index + 1}. ${fromName} --[${edge.via}]--> ${toName}${edge.asserted ? '' : ' (inferred)'}`);
|
|
149
|
+
});
|
|
150
|
+
if (graph.edges.length > 10) {
|
|
151
|
+
console.log(` ... and ${graph.edges.length - 10} more edges`);
|
|
152
|
+
}
|
|
153
|
+
console.log('');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log('=== Graph build complete ===');
|
|
157
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { scan } from './scanner.js';
|
|
5
|
+
import { parseUniverse, debugAst } from './parser.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Load the universe.prose file
|
|
11
|
+
const prosePath = join(__dirname, 'universe.prose');
|
|
12
|
+
const proseContent = readFileSync(prosePath, 'utf-8');
|
|
13
|
+
|
|
14
|
+
console.log('=== Scanning universe.prose ===\n');
|
|
15
|
+
console.log(`File: ${prosePath}`);
|
|
16
|
+
console.log(`Content length: ${proseContent.length} characters\n`);
|
|
17
|
+
|
|
18
|
+
// Run the scanner
|
|
19
|
+
const tokens = scan(proseContent, prosePath);
|
|
20
|
+
|
|
21
|
+
// Print token summary
|
|
22
|
+
console.log(`Found ${tokens.length} tokens\n`);
|
|
23
|
+
|
|
24
|
+
// Run the parser
|
|
25
|
+
console.log('=== Parsing tokens ===\n');
|
|
26
|
+
const { ast, diags } = parseUniverse({
|
|
27
|
+
tokens,
|
|
28
|
+
sourceText: proseContent,
|
|
29
|
+
filePath: prosePath,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Print diagnostics
|
|
33
|
+
if (diags.length > 0) {
|
|
34
|
+
console.log(`Found ${diags.length} diagnostic(s):\n`);
|
|
35
|
+
diags.forEach((diag, index) => {
|
|
36
|
+
const { severity, message, source } = diag;
|
|
37
|
+
const loc = source
|
|
38
|
+
? `${source.file}:${source.start.line}:${source.start.col}`
|
|
39
|
+
: 'unknown location';
|
|
40
|
+
console.log(` ${index + 1}. [${severity}] ${message}`);
|
|
41
|
+
console.log(` at ${loc}\n`);
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
console.log('No diagnostics (parsing succeeded)\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Print AST summary
|
|
48
|
+
console.log('=== AST Summary ===\n');
|
|
49
|
+
console.log(`AST kind: ${ast.kind}`);
|
|
50
|
+
console.log(`Top-level declarations: ${ast.decls.length}`);
|
|
51
|
+
if (ast.source) {
|
|
52
|
+
console.log(`Source span: ${ast.source.start.line}:${ast.source.start.col} - ${ast.source.end.line}:${ast.source.end.col}`);
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
|
|
56
|
+
// Print AST tree using debugAst
|
|
57
|
+
console.log('=== AST Tree ===\n');
|
|
58
|
+
debugAst(ast);
|
|
59
|
+
|
|
60
|
+
console.log('\n=== Parse complete ===');
|
|
61
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { scan } from './scanner.js';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// Load the universe.prose file
|
|
10
|
+
const prosePath = join(__dirname, 'universe.prose');
|
|
11
|
+
const proseContent = readFileSync(prosePath, 'utf-8');
|
|
12
|
+
|
|
13
|
+
console.log('=== Scanning universe.prose ===\n');
|
|
14
|
+
console.log(`File: ${prosePath}`);
|
|
15
|
+
console.log(`Content length: ${proseContent.length} characters\n`);
|
|
16
|
+
|
|
17
|
+
// Run the scanner
|
|
18
|
+
const tokens = scan(proseContent, prosePath);
|
|
19
|
+
|
|
20
|
+
// Print the tokens
|
|
21
|
+
console.log(`Found ${tokens.length} tokens:\n`);
|
|
22
|
+
tokens.forEach((token, index) => {
|
|
23
|
+
const { type, value, span } = token;
|
|
24
|
+
const { start, end } = span;
|
|
25
|
+
|
|
26
|
+
// Format the value for display (truncate long strings)
|
|
27
|
+
let displayValue = value;
|
|
28
|
+
if (type === 'STRING' && value.length > 50) {
|
|
29
|
+
displayValue = `"${value.substring(0, 47)}..."`;
|
|
30
|
+
} else if (value.length > 30) {
|
|
31
|
+
displayValue = value.substring(0, 27) + '...';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`${index.toString().padStart(4)}: ${type.padEnd(12)} | ${displayValue.padEnd(30)} | ${start.line}:${start.col}-${end.line}:${end.col}`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log('\n=== Scan complete ===');
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
universe CanonicalUniverse {
|
|
2
|
+
note {
|
|
3
|
+
This universe contains every possible feature for testing the parser and
|
|
4
|
+
compiler.
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
anthology NestedAnthology {
|
|
8
|
+
series NestedSeries {
|
|
9
|
+
book NestedBook {
|
|
10
|
+
chapter NestedChapter {
|
|
11
|
+
concept NestedConcept { }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
relates NestedConcept and ExternalConcept {
|
|
18
|
+
describe {
|
|
19
|
+
A relationship between a nested concept and an external concept.
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
from NestedConcept {
|
|
23
|
+
relationships { 'relates to' }
|
|
24
|
+
describe {
|
|
25
|
+
A nested concept that relates to an external concept.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
from ExternalConcept {
|
|
30
|
+
relationships { 'is related to' }
|
|
31
|
+
describe {
|
|
32
|
+
An external concept that is related to a nested concept.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
anthology AnthologyWithRelationships in CanonicalUniverse {
|
|
39
|
+
relationship singleton {
|
|
40
|
+
describe {
|
|
41
|
+
A singleton relationship.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
relationship owns and ownedBy {
|
|
46
|
+
describe {
|
|
47
|
+
A bidirectional relationship between two entities.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
from ownedBy {
|
|
51
|
+
label { 'is owned by' }
|
|
52
|
+
describe {
|
|
53
|
+
An entity that is owned by another entity.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
alias other { book }
|
|
59
|
+
|
|
60
|
+
other OtherConcept {
|
|
61
|
+
describe {
|
|
62
|
+
This book is masquerading as `other`.
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
chapter OtherChapter {
|
|
66
|
+
describe {
|
|
67
|
+
A chapter can still be nested under an aliased book.
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
owns {
|
|
71
|
+
ExternalOtherChapter,
|
|
72
|
+
ExternalBook,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
singleton {
|
|
76
|
+
ExternalAnthology
|
|
77
|
+
ExternalSeries
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
chapter AnotherExample {
|
|
82
|
+
relationships {
|
|
83
|
+
ownedBy {
|
|
84
|
+
ExternalChapter
|
|
85
|
+
ExternalBook {
|
|
86
|
+
describe {
|
|
87
|
+
An example where an array element is a block.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
chapter ExternalOtherChapter in OtherConcept {
|
|
96
|
+
describe {
|
|
97
|
+
A chapter can still reference an external aliased book.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
repository InternalRepository {
|
|
102
|
+
title { GitHub }
|
|
103
|
+
url { 'https://github.com/owner/repository/tree/main' }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
reference NamedReference1 {
|
|
107
|
+
url { 'https://github.com/owner/repository/tree/main' }
|
|
108
|
+
describe {
|
|
109
|
+
References can have describe blocks.
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
reference NamedReference2 {
|
|
114
|
+
url { 'https://github.com/owner/repository/tree/main' }
|
|
115
|
+
describe {
|
|
116
|
+
References can have describe blocks.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
reference NamedReferenceInRepository in InternalRepository {
|
|
121
|
+
url { 'https://github.com/owner/repository/tree/main' }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
concept ConceptWithDirectReference {
|
|
125
|
+
reference {
|
|
126
|
+
url { 'https://github.com/owner/repository/tree/main' }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
concept ConceptWithReferences {
|
|
131
|
+
references { NamedReference1 NamedReference2 }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
concept ConceptWithReferenceInRepository {
|
|
135
|
+
reference in ExternalRepository {
|
|
136
|
+
kind { 'documentation' }
|
|
137
|
+
paths {
|
|
138
|
+
'path/to/file1.md'
|
|
139
|
+
'path/to/file2.md'
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
anthology ExternalAnthology in CanonicalUniverse {
|
|
146
|
+
aliases {
|
|
147
|
+
bookAlias { book }
|
|
148
|
+
chapterAlias { chapter }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
bookAlias BookAlias {
|
|
152
|
+
chapterAlias NestedChapterAlias { }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
chapterAlias ExternalChapterAlias in BookAlias { }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
series ExternalSeries in ExternalAnthology { }
|
|
159
|
+
|
|
160
|
+
book ExternalBook in ExternalSeries { }
|
|
161
|
+
|
|
162
|
+
chapter ExternalChapter in ExternalBook { }
|
|
163
|
+
|
|
164
|
+
concept ExternalConcept in ExternalChapter { }
|
|
165
|
+
|
|
166
|
+
repository ExternalRepository in CanonicalUniverse {
|
|
167
|
+
title { GitHub }
|
|
168
|
+
url { 'https://github.com/owner/repository/tree/main' }
|
|
169
|
+
}
|