Introduce repository-backed capability workspaces
- define and validate the capability and resource-lock languages - provision workspace source repositories through Central Gitea - build package runtimes from pinned standalone sources - replace legacy schema compilation with workspace persistence plans - add stable optimistic registers and Automerge CRDT documents - modernize TypeScript/Nix package builds and runtime activation readiness
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { parseQuixosLock } from "./parser.js";
|
||||
|
||||
const fileName = process.argv[2];
|
||||
if (!fileName || process.argv.length !== 3) {
|
||||
console.error("Usage: quixos-lock-check <quixos.lock>");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const result = parseQuixosLock(await readFile(fileName, "utf8"), fileName);
|
||||
if (!result.ok) {
|
||||
for (const diagnostic of result.diagnostics) {
|
||||
console.error(
|
||||
`${diagnostic.fileName}:${diagnostic.line}:${diagnostic.column + 1}: ${diagnostic.phase}/${diagnostic.code}: ${diagnostic.message}`,
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(JSON.stringify(result.lock, null, 2));
|
||||
@@ -0,0 +1,52 @@
|
||||
token literal names:
|
||||
null
|
||||
'quixos-lock'
|
||||
'version'
|
||||
'quixos'
|
||||
'source'
|
||||
'interface'
|
||||
'package'
|
||||
'repository'
|
||||
'commit'
|
||||
'{'
|
||||
'}'
|
||||
';'
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
QUIXOS_LOCK
|
||||
VERSION
|
||||
QUIXOS
|
||||
SOURCE
|
||||
INTERFACE
|
||||
PACKAGE
|
||||
REPOSITORY
|
||||
COMMIT
|
||||
LBRACE
|
||||
RBRACE
|
||||
SEMI
|
||||
INTEGER
|
||||
IDENTIFIER
|
||||
STRING_LITERAL
|
||||
LINE_COMMENT
|
||||
BLOCK_COMMENT
|
||||
WS
|
||||
|
||||
rule names:
|
||||
document
|
||||
quixosEntry
|
||||
resourceEntry
|
||||
resourceKind
|
||||
sourceBlock
|
||||
identifier
|
||||
stringLiteral
|
||||
|
||||
|
||||
atn:
|
||||
[4, 1, 17, 53, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 21, 8, 0, 10, 0, 12, 0, 24, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, 0, 1, 1, 0, 5, 6, 46, 0, 14, 1, 0, 0, 0, 2, 28, 1, 0, 0, 0, 4, 32, 1, 0, 0, 0, 6, 37, 1, 0, 0, 0, 8, 39, 1, 0, 0, 0, 10, 48, 1, 0, 0, 0, 12, 50, 1, 0, 0, 0, 14, 15, 5, 1, 0, 0, 15, 16, 5, 2, 0, 0, 16, 17, 5, 12, 0, 0, 17, 18, 5, 9, 0, 0, 18, 22, 3, 2, 1, 0, 19, 21, 3, 4, 2, 0, 20, 19, 1, 0, 0, 0, 21, 24, 1, 0, 0, 0, 22, 20, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 25, 1, 0, 0, 0, 24, 22, 1, 0, 0, 0, 25, 26, 5, 10, 0, 0, 26, 27, 5, 0, 0, 1, 27, 1, 1, 0, 0, 0, 28, 29, 5, 3, 0, 0, 29, 30, 5, 4, 0, 0, 30, 31, 3, 8, 4, 0, 31, 3, 1, 0, 0, 0, 32, 33, 3, 6, 3, 0, 33, 34, 3, 10, 5, 0, 34, 35, 5, 4, 0, 0, 35, 36, 3, 8, 4, 0, 36, 5, 1, 0, 0, 0, 37, 38, 7, 0, 0, 0, 38, 7, 1, 0, 0, 0, 39, 40, 5, 9, 0, 0, 40, 41, 5, 7, 0, 0, 41, 42, 3, 12, 6, 0, 42, 43, 5, 11, 0, 0, 43, 44, 5, 8, 0, 0, 44, 45, 3, 12, 6, 0, 45, 46, 5, 11, 0, 0, 46, 47, 5, 10, 0, 0, 47, 9, 1, 0, 0, 0, 48, 49, 5, 13, 0, 0, 49, 11, 1, 0, 0, 0, 50, 51, 5, 14, 0, 0, 51, 13, 1, 0, 0, 0, 1, 22]
|
||||
@@ -0,0 +1,28 @@
|
||||
QUIXOS_LOCK=1
|
||||
VERSION=2
|
||||
QUIXOS=3
|
||||
SOURCE=4
|
||||
INTERFACE=5
|
||||
PACKAGE=6
|
||||
REPOSITORY=7
|
||||
COMMIT=8
|
||||
LBRACE=9
|
||||
RBRACE=10
|
||||
SEMI=11
|
||||
INTEGER=12
|
||||
IDENTIFIER=13
|
||||
STRING_LITERAL=14
|
||||
LINE_COMMENT=15
|
||||
BLOCK_COMMENT=16
|
||||
WS=17
|
||||
'quixos-lock'=1
|
||||
'version'=2
|
||||
'quixos'=3
|
||||
'source'=4
|
||||
'interface'=5
|
||||
'package'=6
|
||||
'repository'=7
|
||||
'commit'=8
|
||||
'{'=9
|
||||
'}'=10
|
||||
';'=11
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,28 @@
|
||||
QUIXOS_LOCK=1
|
||||
VERSION=2
|
||||
QUIXOS=3
|
||||
SOURCE=4
|
||||
INTERFACE=5
|
||||
PACKAGE=6
|
||||
REPOSITORY=7
|
||||
COMMIT=8
|
||||
LBRACE=9
|
||||
RBRACE=10
|
||||
SEMI=11
|
||||
INTEGER=12
|
||||
IDENTIFIER=13
|
||||
STRING_LITERAL=14
|
||||
LINE_COMMENT=15
|
||||
BLOCK_COMMENT=16
|
||||
WS=17
|
||||
'quixos-lock'=1
|
||||
'version'=2
|
||||
'quixos'=3
|
||||
'source'=4
|
||||
'interface'=5
|
||||
'package'=6
|
||||
'repository'=7
|
||||
'commit'=8
|
||||
'{'=9
|
||||
'}'=10
|
||||
';'=11
|
||||
@@ -0,0 +1,155 @@
|
||||
|
||||
import * as antlr from "antlr4ng";
|
||||
import { Token } from "antlr4ng";
|
||||
|
||||
|
||||
export class QuixosLockLexer extends antlr.Lexer {
|
||||
public static readonly QUIXOS_LOCK = 1;
|
||||
public static readonly VERSION = 2;
|
||||
public static readonly QUIXOS = 3;
|
||||
public static readonly SOURCE = 4;
|
||||
public static readonly INTERFACE = 5;
|
||||
public static readonly PACKAGE = 6;
|
||||
public static readonly REPOSITORY = 7;
|
||||
public static readonly COMMIT = 8;
|
||||
public static readonly LBRACE = 9;
|
||||
public static readonly RBRACE = 10;
|
||||
public static readonly SEMI = 11;
|
||||
public static readonly INTEGER = 12;
|
||||
public static readonly IDENTIFIER = 13;
|
||||
public static readonly STRING_LITERAL = 14;
|
||||
public static readonly LINE_COMMENT = 15;
|
||||
public static readonly BLOCK_COMMENT = 16;
|
||||
public static readonly WS = 17;
|
||||
|
||||
public static readonly channelNames = [
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
|
||||
];
|
||||
|
||||
public static readonly literalNames = [
|
||||
null, "'quixos-lock'", "'version'", "'quixos'", "'source'", "'interface'",
|
||||
"'package'", "'repository'", "'commit'", "'{'", "'}'", "';'"
|
||||
];
|
||||
|
||||
public static readonly symbolicNames = [
|
||||
null, "QUIXOS_LOCK", "VERSION", "QUIXOS", "SOURCE", "INTERFACE",
|
||||
"PACKAGE", "REPOSITORY", "COMMIT", "LBRACE", "RBRACE", "SEMI", "INTEGER",
|
||||
"IDENTIFIER", "STRING_LITERAL", "LINE_COMMENT", "BLOCK_COMMENT",
|
||||
"WS"
|
||||
];
|
||||
|
||||
public static readonly modeNames = [
|
||||
"DEFAULT_MODE",
|
||||
];
|
||||
|
||||
public static readonly ruleNames = [
|
||||
"QUIXOS_LOCK", "VERSION", "QUIXOS", "SOURCE", "INTERFACE", "PACKAGE",
|
||||
"REPOSITORY", "COMMIT", "LBRACE", "RBRACE", "SEMI", "INTEGER", "IDENTIFIER",
|
||||
"STRING_LITERAL", "ESC", "HEX", "LINE_COMMENT", "BLOCK_COMMENT",
|
||||
"WS",
|
||||
];
|
||||
|
||||
|
||||
public constructor(input: antlr.CharStream) {
|
||||
super(input);
|
||||
this.interpreter = new antlr.LexerATNSimulator(this, QuixosLockLexer._ATN, QuixosLockLexer.decisionsToDFA, new antlr.PredictionContextCache());
|
||||
}
|
||||
|
||||
public get grammarFileName(): string { return "QuixosLock.g4"; }
|
||||
|
||||
public get literalNames(): (string | null)[] { return QuixosLockLexer.literalNames; }
|
||||
public get symbolicNames(): (string | null)[] { return QuixosLockLexer.symbolicNames; }
|
||||
public get ruleNames(): string[] { return QuixosLockLexer.ruleNames; }
|
||||
|
||||
public get serializedATN(): number[] { return QuixosLockLexer._serializedATN; }
|
||||
|
||||
public get channelNames(): string[] { return QuixosLockLexer.channelNames; }
|
||||
|
||||
public get modeNames(): string[] { return QuixosLockLexer.modeNames; }
|
||||
|
||||
public static readonly _serializedATN: number[] = [
|
||||
4,0,17,181,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
|
||||
2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
|
||||
13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,1,0,1,
|
||||
0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,3,1,3,1,
|
||||
3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,
|
||||
5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,
|
||||
7,1,7,1,7,1,7,1,7,1,8,1,8,1,9,1,9,1,10,1,10,1,11,4,11,117,8,11,11,
|
||||
11,12,11,118,1,12,1,12,5,12,123,8,12,10,12,12,12,126,9,12,1,13,1,
|
||||
13,1,13,5,13,131,8,13,10,13,12,13,134,9,13,1,13,1,13,1,14,1,14,1,
|
||||
14,1,14,1,14,1,14,1,14,1,14,3,14,146,8,14,1,15,1,15,1,16,1,16,1,
|
||||
16,1,16,5,16,154,8,16,10,16,12,16,157,9,16,1,16,1,16,1,17,1,17,1,
|
||||
17,1,17,5,17,165,8,17,10,17,12,17,168,9,17,1,17,1,17,1,17,1,17,1,
|
||||
17,1,18,4,18,176,8,18,11,18,12,18,177,1,18,1,18,1,166,0,19,1,1,3,
|
||||
2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,
|
||||
29,0,31,0,33,15,35,16,37,17,1,0,8,1,0,48,57,3,0,65,90,95,95,97,122,
|
||||
4,0,48,57,65,90,95,95,97,122,4,0,10,10,13,13,34,34,92,92,8,0,34,
|
||||
34,47,47,92,92,98,98,102,102,110,110,114,114,116,116,3,0,48,57,65,
|
||||
70,97,102,2,0,10,10,13,13,3,0,9,10,13,13,32,32,186,0,1,1,0,0,0,0,
|
||||
3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,
|
||||
1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,
|
||||
1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,
|
||||
1,0,0,0,1,39,1,0,0,0,3,51,1,0,0,0,5,59,1,0,0,0,7,66,1,0,0,0,9,73,
|
||||
1,0,0,0,11,83,1,0,0,0,13,91,1,0,0,0,15,102,1,0,0,0,17,109,1,0,0,
|
||||
0,19,111,1,0,0,0,21,113,1,0,0,0,23,116,1,0,0,0,25,120,1,0,0,0,27,
|
||||
127,1,0,0,0,29,137,1,0,0,0,31,147,1,0,0,0,33,149,1,0,0,0,35,160,
|
||||
1,0,0,0,37,175,1,0,0,0,39,40,5,113,0,0,40,41,5,117,0,0,41,42,5,105,
|
||||
0,0,42,43,5,120,0,0,43,44,5,111,0,0,44,45,5,115,0,0,45,46,5,45,0,
|
||||
0,46,47,5,108,0,0,47,48,5,111,0,0,48,49,5,99,0,0,49,50,5,107,0,0,
|
||||
50,2,1,0,0,0,51,52,5,118,0,0,52,53,5,101,0,0,53,54,5,114,0,0,54,
|
||||
55,5,115,0,0,55,56,5,105,0,0,56,57,5,111,0,0,57,58,5,110,0,0,58,
|
||||
4,1,0,0,0,59,60,5,113,0,0,60,61,5,117,0,0,61,62,5,105,0,0,62,63,
|
||||
5,120,0,0,63,64,5,111,0,0,64,65,5,115,0,0,65,6,1,0,0,0,66,67,5,115,
|
||||
0,0,67,68,5,111,0,0,68,69,5,117,0,0,69,70,5,114,0,0,70,71,5,99,0,
|
||||
0,71,72,5,101,0,0,72,8,1,0,0,0,73,74,5,105,0,0,74,75,5,110,0,0,75,
|
||||
76,5,116,0,0,76,77,5,101,0,0,77,78,5,114,0,0,78,79,5,102,0,0,79,
|
||||
80,5,97,0,0,80,81,5,99,0,0,81,82,5,101,0,0,82,10,1,0,0,0,83,84,5,
|
||||
112,0,0,84,85,5,97,0,0,85,86,5,99,0,0,86,87,5,107,0,0,87,88,5,97,
|
||||
0,0,88,89,5,103,0,0,89,90,5,101,0,0,90,12,1,0,0,0,91,92,5,114,0,
|
||||
0,92,93,5,101,0,0,93,94,5,112,0,0,94,95,5,111,0,0,95,96,5,115,0,
|
||||
0,96,97,5,105,0,0,97,98,5,116,0,0,98,99,5,111,0,0,99,100,5,114,0,
|
||||
0,100,101,5,121,0,0,101,14,1,0,0,0,102,103,5,99,0,0,103,104,5,111,
|
||||
0,0,104,105,5,109,0,0,105,106,5,109,0,0,106,107,5,105,0,0,107,108,
|
||||
5,116,0,0,108,16,1,0,0,0,109,110,5,123,0,0,110,18,1,0,0,0,111,112,
|
||||
5,125,0,0,112,20,1,0,0,0,113,114,5,59,0,0,114,22,1,0,0,0,115,117,
|
||||
7,0,0,0,116,115,1,0,0,0,117,118,1,0,0,0,118,116,1,0,0,0,118,119,
|
||||
1,0,0,0,119,24,1,0,0,0,120,124,7,1,0,0,121,123,7,2,0,0,122,121,1,
|
||||
0,0,0,123,126,1,0,0,0,124,122,1,0,0,0,124,125,1,0,0,0,125,26,1,0,
|
||||
0,0,126,124,1,0,0,0,127,132,5,34,0,0,128,131,3,29,14,0,129,131,8,
|
||||
3,0,0,130,128,1,0,0,0,130,129,1,0,0,0,131,134,1,0,0,0,132,130,1,
|
||||
0,0,0,132,133,1,0,0,0,133,135,1,0,0,0,134,132,1,0,0,0,135,136,5,
|
||||
34,0,0,136,28,1,0,0,0,137,145,5,92,0,0,138,146,7,4,0,0,139,140,5,
|
||||
117,0,0,140,141,3,31,15,0,141,142,3,31,15,0,142,143,3,31,15,0,143,
|
||||
144,3,31,15,0,144,146,1,0,0,0,145,138,1,0,0,0,145,139,1,0,0,0,146,
|
||||
30,1,0,0,0,147,148,7,5,0,0,148,32,1,0,0,0,149,150,5,47,0,0,150,151,
|
||||
5,47,0,0,151,155,1,0,0,0,152,154,8,6,0,0,153,152,1,0,0,0,154,157,
|
||||
1,0,0,0,155,153,1,0,0,0,155,156,1,0,0,0,156,158,1,0,0,0,157,155,
|
||||
1,0,0,0,158,159,6,16,0,0,159,34,1,0,0,0,160,161,5,47,0,0,161,162,
|
||||
5,42,0,0,162,166,1,0,0,0,163,165,9,0,0,0,164,163,1,0,0,0,165,168,
|
||||
1,0,0,0,166,167,1,0,0,0,166,164,1,0,0,0,167,169,1,0,0,0,168,166,
|
||||
1,0,0,0,169,170,5,42,0,0,170,171,5,47,0,0,171,172,1,0,0,0,172,173,
|
||||
6,17,0,0,173,36,1,0,0,0,174,176,7,7,0,0,175,174,1,0,0,0,176,177,
|
||||
1,0,0,0,177,175,1,0,0,0,177,178,1,0,0,0,178,179,1,0,0,0,179,180,
|
||||
6,18,0,0,180,38,1,0,0,0,9,0,118,124,130,132,145,155,166,177,1,6,
|
||||
0,0
|
||||
];
|
||||
|
||||
private static __ATN: antlr.ATN;
|
||||
public static get _ATN(): antlr.ATN {
|
||||
if (!QuixosLockLexer.__ATN) {
|
||||
QuixosLockLexer.__ATN = new antlr.ATNDeserializer().deserialize(QuixosLockLexer._serializedATN);
|
||||
}
|
||||
|
||||
return QuixosLockLexer.__ATN;
|
||||
}
|
||||
|
||||
|
||||
private static readonly vocabulary = new antlr.Vocabulary(QuixosLockLexer.literalNames, QuixosLockLexer.symbolicNames, []);
|
||||
|
||||
public override get vocabulary(): antlr.Vocabulary {
|
||||
return QuixosLockLexer.vocabulary;
|
||||
}
|
||||
|
||||
private static readonly decisionsToDFA = QuixosLockLexer._ATN.decisionToState.map( (ds: antlr.DecisionState, index: number) => new antlr.DFA(ds, index) );
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
|
||||
import * as antlr from "antlr4ng";
|
||||
import { Token } from "antlr4ng";
|
||||
|
||||
import { QuixosLockVisitor } from "./QuixosLockVisitor.js";
|
||||
|
||||
// for running tests with parameters, TODO: discuss strategy for typed parameters in CI
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
type int = number;
|
||||
|
||||
|
||||
export class QuixosLockParser extends antlr.Parser {
|
||||
public static readonly QUIXOS_LOCK = 1;
|
||||
public static readonly VERSION = 2;
|
||||
public static readonly QUIXOS = 3;
|
||||
public static readonly SOURCE = 4;
|
||||
public static readonly INTERFACE = 5;
|
||||
public static readonly PACKAGE = 6;
|
||||
public static readonly REPOSITORY = 7;
|
||||
public static readonly COMMIT = 8;
|
||||
public static readonly LBRACE = 9;
|
||||
public static readonly RBRACE = 10;
|
||||
public static readonly SEMI = 11;
|
||||
public static readonly INTEGER = 12;
|
||||
public static readonly IDENTIFIER = 13;
|
||||
public static readonly STRING_LITERAL = 14;
|
||||
public static readonly LINE_COMMENT = 15;
|
||||
public static readonly BLOCK_COMMENT = 16;
|
||||
public static readonly WS = 17;
|
||||
public static readonly RULE_document = 0;
|
||||
public static readonly RULE_quixosEntry = 1;
|
||||
public static readonly RULE_resourceEntry = 2;
|
||||
public static readonly RULE_resourceKind = 3;
|
||||
public static readonly RULE_sourceBlock = 4;
|
||||
public static readonly RULE_identifier = 5;
|
||||
public static readonly RULE_stringLiteral = 6;
|
||||
|
||||
public static readonly literalNames = [
|
||||
null, "'quixos-lock'", "'version'", "'quixos'", "'source'", "'interface'",
|
||||
"'package'", "'repository'", "'commit'", "'{'", "'}'", "';'"
|
||||
];
|
||||
|
||||
public static readonly symbolicNames = [
|
||||
null, "QUIXOS_LOCK", "VERSION", "QUIXOS", "SOURCE", "INTERFACE",
|
||||
"PACKAGE", "REPOSITORY", "COMMIT", "LBRACE", "RBRACE", "SEMI", "INTEGER",
|
||||
"IDENTIFIER", "STRING_LITERAL", "LINE_COMMENT", "BLOCK_COMMENT",
|
||||
"WS"
|
||||
];
|
||||
public static readonly ruleNames = [
|
||||
"document", "quixosEntry", "resourceEntry", "resourceKind", "sourceBlock",
|
||||
"identifier", "stringLiteral",
|
||||
];
|
||||
|
||||
public get grammarFileName(): string { return "QuixosLock.g4"; }
|
||||
public get literalNames(): (string | null)[] { return QuixosLockParser.literalNames; }
|
||||
public get symbolicNames(): (string | null)[] { return QuixosLockParser.symbolicNames; }
|
||||
public get ruleNames(): string[] { return QuixosLockParser.ruleNames; }
|
||||
public get serializedATN(): number[] { return QuixosLockParser._serializedATN; }
|
||||
|
||||
protected createFailedPredicateException(predicate?: string, message?: string): antlr.FailedPredicateException {
|
||||
return new antlr.FailedPredicateException(this, predicate, message);
|
||||
}
|
||||
|
||||
public constructor(input: antlr.TokenStream) {
|
||||
super(input);
|
||||
this.interpreter = new antlr.ParserATNSimulator(this, QuixosLockParser._ATN, QuixosLockParser.decisionsToDFA, new antlr.PredictionContextCache());
|
||||
}
|
||||
public document(): DocumentContext {
|
||||
let localContext = new DocumentContext(this.context, this.state);
|
||||
this.enterRule(localContext, 0, QuixosLockParser.RULE_document);
|
||||
let _la: number;
|
||||
try {
|
||||
this.enterOuterAlt(localContext, 1);
|
||||
{
|
||||
this.state = 14;
|
||||
this.match(QuixosLockParser.QUIXOS_LOCK);
|
||||
this.state = 15;
|
||||
this.match(QuixosLockParser.VERSION);
|
||||
this.state = 16;
|
||||
this.match(QuixosLockParser.INTEGER);
|
||||
this.state = 17;
|
||||
this.match(QuixosLockParser.LBRACE);
|
||||
this.state = 18;
|
||||
this.quixosEntry();
|
||||
this.state = 22;
|
||||
this.errorHandler.sync(this);
|
||||
_la = this.tokenStream.LA(1);
|
||||
while (_la === 5 || _la === 6) {
|
||||
{
|
||||
{
|
||||
this.state = 19;
|
||||
this.resourceEntry();
|
||||
}
|
||||
}
|
||||
this.state = 24;
|
||||
this.errorHandler.sync(this);
|
||||
_la = this.tokenStream.LA(1);
|
||||
}
|
||||
this.state = 25;
|
||||
this.match(QuixosLockParser.RBRACE);
|
||||
this.state = 26;
|
||||
this.match(QuixosLockParser.EOF);
|
||||
}
|
||||
}
|
||||
catch (re) {
|
||||
if (re instanceof antlr.RecognitionException) {
|
||||
this.errorHandler.reportError(this, re);
|
||||
this.errorHandler.recover(this, re);
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.exitRule();
|
||||
}
|
||||
return localContext;
|
||||
}
|
||||
public quixosEntry(): QuixosEntryContext {
|
||||
let localContext = new QuixosEntryContext(this.context, this.state);
|
||||
this.enterRule(localContext, 2, QuixosLockParser.RULE_quixosEntry);
|
||||
try {
|
||||
this.enterOuterAlt(localContext, 1);
|
||||
{
|
||||
this.state = 28;
|
||||
this.match(QuixosLockParser.QUIXOS);
|
||||
this.state = 29;
|
||||
this.match(QuixosLockParser.SOURCE);
|
||||
this.state = 30;
|
||||
this.sourceBlock();
|
||||
}
|
||||
}
|
||||
catch (re) {
|
||||
if (re instanceof antlr.RecognitionException) {
|
||||
this.errorHandler.reportError(this, re);
|
||||
this.errorHandler.recover(this, re);
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.exitRule();
|
||||
}
|
||||
return localContext;
|
||||
}
|
||||
public resourceEntry(): ResourceEntryContext {
|
||||
let localContext = new ResourceEntryContext(this.context, this.state);
|
||||
this.enterRule(localContext, 4, QuixosLockParser.RULE_resourceEntry);
|
||||
try {
|
||||
this.enterOuterAlt(localContext, 1);
|
||||
{
|
||||
this.state = 32;
|
||||
this.resourceKind();
|
||||
this.state = 33;
|
||||
this.identifier();
|
||||
this.state = 34;
|
||||
this.match(QuixosLockParser.SOURCE);
|
||||
this.state = 35;
|
||||
this.sourceBlock();
|
||||
}
|
||||
}
|
||||
catch (re) {
|
||||
if (re instanceof antlr.RecognitionException) {
|
||||
this.errorHandler.reportError(this, re);
|
||||
this.errorHandler.recover(this, re);
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.exitRule();
|
||||
}
|
||||
return localContext;
|
||||
}
|
||||
public resourceKind(): ResourceKindContext {
|
||||
let localContext = new ResourceKindContext(this.context, this.state);
|
||||
this.enterRule(localContext, 6, QuixosLockParser.RULE_resourceKind);
|
||||
let _la: number;
|
||||
try {
|
||||
this.enterOuterAlt(localContext, 1);
|
||||
{
|
||||
this.state = 37;
|
||||
_la = this.tokenStream.LA(1);
|
||||
if(!(_la === 5 || _la === 6)) {
|
||||
this.errorHandler.recoverInline(this);
|
||||
}
|
||||
else {
|
||||
this.errorHandler.reportMatch(this);
|
||||
this.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (re) {
|
||||
if (re instanceof antlr.RecognitionException) {
|
||||
this.errorHandler.reportError(this, re);
|
||||
this.errorHandler.recover(this, re);
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.exitRule();
|
||||
}
|
||||
return localContext;
|
||||
}
|
||||
public sourceBlock(): SourceBlockContext {
|
||||
let localContext = new SourceBlockContext(this.context, this.state);
|
||||
this.enterRule(localContext, 8, QuixosLockParser.RULE_sourceBlock);
|
||||
try {
|
||||
this.enterOuterAlt(localContext, 1);
|
||||
{
|
||||
this.state = 39;
|
||||
this.match(QuixosLockParser.LBRACE);
|
||||
this.state = 40;
|
||||
this.match(QuixosLockParser.REPOSITORY);
|
||||
this.state = 41;
|
||||
this.stringLiteral();
|
||||
this.state = 42;
|
||||
this.match(QuixosLockParser.SEMI);
|
||||
this.state = 43;
|
||||
this.match(QuixosLockParser.COMMIT);
|
||||
this.state = 44;
|
||||
this.stringLiteral();
|
||||
this.state = 45;
|
||||
this.match(QuixosLockParser.SEMI);
|
||||
this.state = 46;
|
||||
this.match(QuixosLockParser.RBRACE);
|
||||
}
|
||||
}
|
||||
catch (re) {
|
||||
if (re instanceof antlr.RecognitionException) {
|
||||
this.errorHandler.reportError(this, re);
|
||||
this.errorHandler.recover(this, re);
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.exitRule();
|
||||
}
|
||||
return localContext;
|
||||
}
|
||||
public identifier(): IdentifierContext {
|
||||
let localContext = new IdentifierContext(this.context, this.state);
|
||||
this.enterRule(localContext, 10, QuixosLockParser.RULE_identifier);
|
||||
try {
|
||||
this.enterOuterAlt(localContext, 1);
|
||||
{
|
||||
this.state = 48;
|
||||
this.match(QuixosLockParser.IDENTIFIER);
|
||||
}
|
||||
}
|
||||
catch (re) {
|
||||
if (re instanceof antlr.RecognitionException) {
|
||||
this.errorHandler.reportError(this, re);
|
||||
this.errorHandler.recover(this, re);
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.exitRule();
|
||||
}
|
||||
return localContext;
|
||||
}
|
||||
public stringLiteral(): StringLiteralContext {
|
||||
let localContext = new StringLiteralContext(this.context, this.state);
|
||||
this.enterRule(localContext, 12, QuixosLockParser.RULE_stringLiteral);
|
||||
try {
|
||||
this.enterOuterAlt(localContext, 1);
|
||||
{
|
||||
this.state = 50;
|
||||
this.match(QuixosLockParser.STRING_LITERAL);
|
||||
}
|
||||
}
|
||||
catch (re) {
|
||||
if (re instanceof antlr.RecognitionException) {
|
||||
this.errorHandler.reportError(this, re);
|
||||
this.errorHandler.recover(this, re);
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.exitRule();
|
||||
}
|
||||
return localContext;
|
||||
}
|
||||
|
||||
public static readonly _serializedATN: number[] = [
|
||||
4,1,17,53,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,
|
||||
6,1,0,1,0,1,0,1,0,1,0,1,0,5,0,21,8,0,10,0,12,0,24,9,0,1,0,1,0,1,
|
||||
0,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,4,1,4,1,4,1,4,1,
|
||||
4,1,4,1,4,1,4,1,4,1,5,1,5,1,6,1,6,1,6,0,0,7,0,2,4,6,8,10,12,0,1,
|
||||
1,0,5,6,46,0,14,1,0,0,0,2,28,1,0,0,0,4,32,1,0,0,0,6,37,1,0,0,0,8,
|
||||
39,1,0,0,0,10,48,1,0,0,0,12,50,1,0,0,0,14,15,5,1,0,0,15,16,5,2,0,
|
||||
0,16,17,5,12,0,0,17,18,5,9,0,0,18,22,3,2,1,0,19,21,3,4,2,0,20,19,
|
||||
1,0,0,0,21,24,1,0,0,0,22,20,1,0,0,0,22,23,1,0,0,0,23,25,1,0,0,0,
|
||||
24,22,1,0,0,0,25,26,5,10,0,0,26,27,5,0,0,1,27,1,1,0,0,0,28,29,5,
|
||||
3,0,0,29,30,5,4,0,0,30,31,3,8,4,0,31,3,1,0,0,0,32,33,3,6,3,0,33,
|
||||
34,3,10,5,0,34,35,5,4,0,0,35,36,3,8,4,0,36,5,1,0,0,0,37,38,7,0,0,
|
||||
0,38,7,1,0,0,0,39,40,5,9,0,0,40,41,5,7,0,0,41,42,3,12,6,0,42,43,
|
||||
5,11,0,0,43,44,5,8,0,0,44,45,3,12,6,0,45,46,5,11,0,0,46,47,5,10,
|
||||
0,0,47,9,1,0,0,0,48,49,5,13,0,0,49,11,1,0,0,0,50,51,5,14,0,0,51,
|
||||
13,1,0,0,0,1,22
|
||||
];
|
||||
|
||||
private static __ATN: antlr.ATN;
|
||||
public static get _ATN(): antlr.ATN {
|
||||
if (!QuixosLockParser.__ATN) {
|
||||
QuixosLockParser.__ATN = new antlr.ATNDeserializer().deserialize(QuixosLockParser._serializedATN);
|
||||
}
|
||||
|
||||
return QuixosLockParser.__ATN;
|
||||
}
|
||||
|
||||
|
||||
private static readonly vocabulary = new antlr.Vocabulary(QuixosLockParser.literalNames, QuixosLockParser.symbolicNames, []);
|
||||
|
||||
public override get vocabulary(): antlr.Vocabulary {
|
||||
return QuixosLockParser.vocabulary;
|
||||
}
|
||||
|
||||
private static readonly decisionsToDFA = QuixosLockParser._ATN.decisionToState.map( (ds: antlr.DecisionState, index: number) => new antlr.DFA(ds, index) );
|
||||
}
|
||||
|
||||
export class DocumentContext extends antlr.ParserRuleContext {
|
||||
public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
|
||||
super(parent, invokingState);
|
||||
}
|
||||
public QUIXOS_LOCK(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.QUIXOS_LOCK, 0)!;
|
||||
}
|
||||
public VERSION(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.VERSION, 0)!;
|
||||
}
|
||||
public INTEGER(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.INTEGER, 0)!;
|
||||
}
|
||||
public LBRACE(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.LBRACE, 0)!;
|
||||
}
|
||||
public quixosEntry(): QuixosEntryContext {
|
||||
return this.getRuleContext(0, QuixosEntryContext)!;
|
||||
}
|
||||
public RBRACE(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.RBRACE, 0)!;
|
||||
}
|
||||
public EOF(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.EOF, 0)!;
|
||||
}
|
||||
public resourceEntry(): ResourceEntryContext[];
|
||||
public resourceEntry(i: number): ResourceEntryContext | null;
|
||||
public resourceEntry(i?: number): ResourceEntryContext[] | ResourceEntryContext | null {
|
||||
if (i === undefined) {
|
||||
return this.getRuleContexts(ResourceEntryContext);
|
||||
}
|
||||
|
||||
return this.getRuleContext(i, ResourceEntryContext);
|
||||
}
|
||||
public override get ruleIndex(): number {
|
||||
return QuixosLockParser.RULE_document;
|
||||
}
|
||||
public override accept<Result>(visitor: QuixosLockVisitor<Result>): Result | null {
|
||||
if (visitor.visitDocument) {
|
||||
return visitor.visitDocument(this);
|
||||
} else {
|
||||
return visitor.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class QuixosEntryContext extends antlr.ParserRuleContext {
|
||||
public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
|
||||
super(parent, invokingState);
|
||||
}
|
||||
public QUIXOS(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.QUIXOS, 0)!;
|
||||
}
|
||||
public SOURCE(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.SOURCE, 0)!;
|
||||
}
|
||||
public sourceBlock(): SourceBlockContext {
|
||||
return this.getRuleContext(0, SourceBlockContext)!;
|
||||
}
|
||||
public override get ruleIndex(): number {
|
||||
return QuixosLockParser.RULE_quixosEntry;
|
||||
}
|
||||
public override accept<Result>(visitor: QuixosLockVisitor<Result>): Result | null {
|
||||
if (visitor.visitQuixosEntry) {
|
||||
return visitor.visitQuixosEntry(this);
|
||||
} else {
|
||||
return visitor.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ResourceEntryContext extends antlr.ParserRuleContext {
|
||||
public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
|
||||
super(parent, invokingState);
|
||||
}
|
||||
public resourceKind(): ResourceKindContext {
|
||||
return this.getRuleContext(0, ResourceKindContext)!;
|
||||
}
|
||||
public identifier(): IdentifierContext {
|
||||
return this.getRuleContext(0, IdentifierContext)!;
|
||||
}
|
||||
public SOURCE(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.SOURCE, 0)!;
|
||||
}
|
||||
public sourceBlock(): SourceBlockContext {
|
||||
return this.getRuleContext(0, SourceBlockContext)!;
|
||||
}
|
||||
public override get ruleIndex(): number {
|
||||
return QuixosLockParser.RULE_resourceEntry;
|
||||
}
|
||||
public override accept<Result>(visitor: QuixosLockVisitor<Result>): Result | null {
|
||||
if (visitor.visitResourceEntry) {
|
||||
return visitor.visitResourceEntry(this);
|
||||
} else {
|
||||
return visitor.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ResourceKindContext extends antlr.ParserRuleContext {
|
||||
public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
|
||||
super(parent, invokingState);
|
||||
}
|
||||
public INTERFACE(): antlr.TerminalNode | null {
|
||||
return this.getToken(QuixosLockParser.INTERFACE, 0);
|
||||
}
|
||||
public PACKAGE(): antlr.TerminalNode | null {
|
||||
return this.getToken(QuixosLockParser.PACKAGE, 0);
|
||||
}
|
||||
public override get ruleIndex(): number {
|
||||
return QuixosLockParser.RULE_resourceKind;
|
||||
}
|
||||
public override accept<Result>(visitor: QuixosLockVisitor<Result>): Result | null {
|
||||
if (visitor.visitResourceKind) {
|
||||
return visitor.visitResourceKind(this);
|
||||
} else {
|
||||
return visitor.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SourceBlockContext extends antlr.ParserRuleContext {
|
||||
public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
|
||||
super(parent, invokingState);
|
||||
}
|
||||
public LBRACE(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.LBRACE, 0)!;
|
||||
}
|
||||
public REPOSITORY(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.REPOSITORY, 0)!;
|
||||
}
|
||||
public stringLiteral(): StringLiteralContext[];
|
||||
public stringLiteral(i: number): StringLiteralContext | null;
|
||||
public stringLiteral(i?: number): StringLiteralContext[] | StringLiteralContext | null {
|
||||
if (i === undefined) {
|
||||
return this.getRuleContexts(StringLiteralContext);
|
||||
}
|
||||
|
||||
return this.getRuleContext(i, StringLiteralContext);
|
||||
}
|
||||
public SEMI(): antlr.TerminalNode[];
|
||||
public SEMI(i: number): antlr.TerminalNode | null;
|
||||
public SEMI(i?: number): antlr.TerminalNode | null | antlr.TerminalNode[] {
|
||||
if (i === undefined) {
|
||||
return this.getTokens(QuixosLockParser.SEMI);
|
||||
} else {
|
||||
return this.getToken(QuixosLockParser.SEMI, i);
|
||||
}
|
||||
}
|
||||
public COMMIT(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.COMMIT, 0)!;
|
||||
}
|
||||
public RBRACE(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.RBRACE, 0)!;
|
||||
}
|
||||
public override get ruleIndex(): number {
|
||||
return QuixosLockParser.RULE_sourceBlock;
|
||||
}
|
||||
public override accept<Result>(visitor: QuixosLockVisitor<Result>): Result | null {
|
||||
if (visitor.visitSourceBlock) {
|
||||
return visitor.visitSourceBlock(this);
|
||||
} else {
|
||||
return visitor.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class IdentifierContext extends antlr.ParserRuleContext {
|
||||
public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
|
||||
super(parent, invokingState);
|
||||
}
|
||||
public IDENTIFIER(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.IDENTIFIER, 0)!;
|
||||
}
|
||||
public override get ruleIndex(): number {
|
||||
return QuixosLockParser.RULE_identifier;
|
||||
}
|
||||
public override accept<Result>(visitor: QuixosLockVisitor<Result>): Result | null {
|
||||
if (visitor.visitIdentifier) {
|
||||
return visitor.visitIdentifier(this);
|
||||
} else {
|
||||
return visitor.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class StringLiteralContext extends antlr.ParserRuleContext {
|
||||
public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
|
||||
super(parent, invokingState);
|
||||
}
|
||||
public STRING_LITERAL(): antlr.TerminalNode {
|
||||
return this.getToken(QuixosLockParser.STRING_LITERAL, 0)!;
|
||||
}
|
||||
public override get ruleIndex(): number {
|
||||
return QuixosLockParser.RULE_stringLiteral;
|
||||
}
|
||||
public override accept<Result>(visitor: QuixosLockVisitor<Result>): Result | null {
|
||||
if (visitor.visitStringLiteral) {
|
||||
return visitor.visitStringLiteral(this);
|
||||
} else {
|
||||
return visitor.visitChildren(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
import { AbstractParseTreeVisitor } from "antlr4ng";
|
||||
|
||||
|
||||
import { DocumentContext } from "./QuixosLockParser.js";
|
||||
import { QuixosEntryContext } from "./QuixosLockParser.js";
|
||||
import { ResourceEntryContext } from "./QuixosLockParser.js";
|
||||
import { ResourceKindContext } from "./QuixosLockParser.js";
|
||||
import { SourceBlockContext } from "./QuixosLockParser.js";
|
||||
import { IdentifierContext } from "./QuixosLockParser.js";
|
||||
import { StringLiteralContext } from "./QuixosLockParser.js";
|
||||
|
||||
|
||||
/**
|
||||
* This interface defines a complete generic visitor for a parse tree produced
|
||||
* by `QuixosLockParser`.
|
||||
*
|
||||
* @param <Result> The return type of the visit operation. Use `void` for
|
||||
* operations with no return type.
|
||||
*/
|
||||
export class QuixosLockVisitor<Result> extends AbstractParseTreeVisitor<Result> {
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosLockParser.document`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitDocument?: (ctx: DocumentContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosLockParser.quixosEntry`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitQuixosEntry?: (ctx: QuixosEntryContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosLockParser.resourceEntry`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitResourceEntry?: (ctx: ResourceEntryContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosLockParser.resourceKind`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitResourceKind?: (ctx: ResourceKindContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosLockParser.sourceBlock`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitSourceBlock?: (ctx: SourceBlockContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosLockParser.identifier`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitIdentifier?: (ctx: IdentifierContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `QuixosLockParser.stringLiteral`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitStringLiteral?: (ctx: StringLiteralContext) => Result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./parser.js";
|
||||
export * from "./types.js";
|
||||
@@ -0,0 +1,227 @@
|
||||
import {
|
||||
BaseErrorListener,
|
||||
CharStream,
|
||||
CommonTokenStream,
|
||||
type ATNSimulator,
|
||||
type RecognitionException,
|
||||
type Recognizer,
|
||||
type Token,
|
||||
} from "antlr4ng";
|
||||
import { QuixosLockLexer } from "./generated/QuixosLockLexer.js";
|
||||
import {
|
||||
QuixosLockParser,
|
||||
type SourceBlockContext,
|
||||
} from "./generated/QuixosLockParser.js";
|
||||
import type {
|
||||
GitSource,
|
||||
LockedResource,
|
||||
QuixosRepositoryLock,
|
||||
} from "./types.js";
|
||||
|
||||
export type QuixosLockDiagnostic = {
|
||||
phase: "syntax" | "validation";
|
||||
code: string;
|
||||
message: string;
|
||||
fileName: string;
|
||||
line: number;
|
||||
column: number;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type QuixosLockParseResult =
|
||||
| { ok: true; lock: QuixosRepositoryLock; diagnostics: [] }
|
||||
| { ok: false; diagnostics: QuixosLockDiagnostic[] };
|
||||
|
||||
class SyntaxErrorListener extends BaseErrorListener {
|
||||
constructor(
|
||||
private readonly fileName: string,
|
||||
private readonly diagnostics: QuixosLockDiagnostic[],
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override syntaxError<S extends Token, T extends ATNSimulator>(
|
||||
_recognizer: Recognizer<T>,
|
||||
_offendingSymbol: S | null,
|
||||
line: number,
|
||||
column: number,
|
||||
message: string,
|
||||
_error: RecognitionException | null,
|
||||
): void {
|
||||
this.diagnostics.push({
|
||||
phase: "syntax",
|
||||
code: "syntax-error",
|
||||
message,
|
||||
fileName: this.fileName,
|
||||
line,
|
||||
column,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const stringValue = (context: { getText(): string }): string =>
|
||||
JSON.parse(context.getText()) as string;
|
||||
|
||||
const lowerSource = (context: SourceBlockContext): GitSource => ({
|
||||
resolver: "git",
|
||||
repository: stringValue(context.stringLiteral(0)!),
|
||||
commit: stringValue(context.stringLiteral(1)!).toLowerCase(),
|
||||
});
|
||||
|
||||
const issue = (
|
||||
diagnostics: QuixosLockDiagnostic[],
|
||||
fileName: string,
|
||||
code: string,
|
||||
message: string,
|
||||
path?: string,
|
||||
) => diagnostics.push({
|
||||
phase: "validation",
|
||||
code,
|
||||
message,
|
||||
fileName,
|
||||
line: 1,
|
||||
column: 0,
|
||||
path,
|
||||
});
|
||||
|
||||
const validateSource = (
|
||||
source: GitSource,
|
||||
path: string,
|
||||
fileName: string,
|
||||
diagnostics: QuixosLockDiagnostic[],
|
||||
) => {
|
||||
if (!/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(source.commit)) {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"invalid-git-commit",
|
||||
`${path}.commit must be a full 40- or 64-character Git object ID`,
|
||||
`${path}.commit`,
|
||||
);
|
||||
}
|
||||
let repository: URL;
|
||||
try {
|
||||
repository = new URL(source.repository);
|
||||
} catch {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"invalid-git-repository",
|
||||
`${path}.repository must be an absolute Git URL`,
|
||||
`${path}.repository`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!["https:", "ssh:"].includes(repository.protocol)) {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"unsupported-git-transport",
|
||||
`${path}.repository must use https:// or ssh://`,
|
||||
`${path}.repository`,
|
||||
);
|
||||
}
|
||||
if (repository.search || repository.hash) {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"decorated-git-repository",
|
||||
`${path}.repository must be a base repository URL without a query or fragment`,
|
||||
`${path}.repository`,
|
||||
);
|
||||
}
|
||||
if (repository.password || (repository.protocol === "https:" && repository.username)) {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"embedded-git-credential",
|
||||
`${path}.repository must not embed credentials`,
|
||||
`${path}.repository`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const parseQuixosLock = (
|
||||
source: string,
|
||||
fileName = "<memory>",
|
||||
): QuixosLockParseResult => {
|
||||
const diagnostics: QuixosLockDiagnostic[] = [];
|
||||
const listener = new SyntaxErrorListener(fileName, diagnostics);
|
||||
const lexer = new QuixosLockLexer(CharStream.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
lexer.addErrorListener(listener);
|
||||
const parser = new QuixosLockParser(new CommonTokenStream(lexer));
|
||||
parser.removeErrorListeners();
|
||||
parser.addErrorListener(listener);
|
||||
const tree = parser.document();
|
||||
if (diagnostics.length > 0) return { ok: false, diagnostics };
|
||||
|
||||
const formatVersion = Number.parseInt(tree.INTEGER().getText(), 10);
|
||||
if (formatVersion !== 1) {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"unsupported-lock-version",
|
||||
`Unsupported Quixos lock format version ${formatVersion}`,
|
||||
"formatVersion",
|
||||
);
|
||||
}
|
||||
|
||||
const quixos = lowerSource(tree.quixosEntry().sourceBlock());
|
||||
validateSource(quixos, "quixos", fileName, diagnostics);
|
||||
|
||||
const resources: LockedResource[] = [];
|
||||
const bindings = new Set<string>();
|
||||
for (const [index, context] of tree.resourceEntry().entries()) {
|
||||
const kind = context.resourceKind().INTERFACE() ? "interface" : "package";
|
||||
const binding = context.identifier().getText();
|
||||
const key = `${kind}\0${binding}`;
|
||||
if (bindings.has(key)) {
|
||||
issue(
|
||||
diagnostics,
|
||||
fileName,
|
||||
"duplicate-resource-binding",
|
||||
`Duplicate ${kind} binding ${binding}`,
|
||||
`resources[${index}].binding`,
|
||||
);
|
||||
}
|
||||
bindings.add(key);
|
||||
const resource = { kind, binding, source: lowerSource(context.sourceBlock()) } as LockedResource;
|
||||
validateSource(resource.source, `resources[${index}].source`, fileName, diagnostics);
|
||||
resources.push(resource);
|
||||
}
|
||||
|
||||
return diagnostics.length > 0
|
||||
? { ok: false, diagnostics }
|
||||
: {
|
||||
ok: true,
|
||||
lock: { formatVersion: 1, quixos, resources },
|
||||
diagnostics: [],
|
||||
};
|
||||
};
|
||||
|
||||
const quoted = (value: string) => JSON.stringify(value);
|
||||
|
||||
const sourceLines = (source: GitSource, indentation: string): string[] => [
|
||||
`${indentation}repository ${quoted(source.repository)};`,
|
||||
`${indentation}commit ${quoted(source.commit.toLowerCase())};`,
|
||||
];
|
||||
|
||||
export const formatQuixosLock = (lock: QuixosRepositoryLock): string => {
|
||||
const lines = [
|
||||
"quixos-lock version 1 {",
|
||||
" quixos source {",
|
||||
...sourceLines(lock.quixos, " "),
|
||||
" }",
|
||||
];
|
||||
for (const resource of lock.resources) {
|
||||
lines.push(
|
||||
"",
|
||||
` ${resource.kind} ${resource.binding} source {`,
|
||||
...sourceLines(resource.source, " "),
|
||||
" }",
|
||||
);
|
||||
}
|
||||
lines.push("}", "");
|
||||
return lines.join("\n");
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
export type GitSource = {
|
||||
resolver: "git";
|
||||
repository: string;
|
||||
commit: string;
|
||||
};
|
||||
|
||||
export type LockedResourceKind = "interface" | "package";
|
||||
|
||||
export type LockedResource = {
|
||||
kind: LockedResourceKind;
|
||||
binding: string;
|
||||
source: GitSource;
|
||||
};
|
||||
|
||||
export type QuixosRepositoryLock = {
|
||||
formatVersion: 1;
|
||||
quixos: GitSource;
|
||||
resources: LockedResource[];
|
||||
};
|
||||
|
||||
export type NixGitInput = {
|
||||
type: "git";
|
||||
url: string;
|
||||
ref: string;
|
||||
rev: string;
|
||||
};
|
||||
|
||||
export const RETENTION_TAG_PREFIX = "refs/tags/quixos-reachability/";
|
||||
|
||||
export const retentionTagForCommit = (commit: string): string =>
|
||||
`${RETENTION_TAG_PREFIX}${commit.toLowerCase()}`;
|
||||
|
||||
export const nixGitInput = (source: GitSource): NixGitInput => ({
|
||||
type: "git",
|
||||
url: source.repository,
|
||||
ref: retentionTagForCommit(source.commit),
|
||||
rev: source.commit.toLowerCase(),
|
||||
});
|
||||
Reference in New Issue
Block a user