cactus-react-native 0.2.2 → 0.2.4
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/README.md +1 -1
- package/android/src/main/java/com/cactus/Cactus.java +35 -0
- package/android/src/main/java/com/cactus/LlamaContext.java +18 -1
- package/android/src/main/jni.cpp +11 -0
- package/android/src/main/jniLibs/arm64-v8a/libcactus.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libcactus_v8.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_dotprod.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_dotprod_i8mm.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_i8mm.so +0 -0
- package/android/src/newarch/java/com/cactus/CactusModule.java +5 -0
- package/android/src/oldarch/java/com/cactus/CactusModule.java +5 -0
- package/ios/Cactus.mm +21 -0
- package/ios/CactusContext.h +1 -0
- package/ios/CactusContext.mm +4 -0
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_ffi.h +0 -12
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
- package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/Headers/cactus_ffi.h +0 -12
- package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
- package/ios/cactus.xcframework/tvos-arm64/cactus.framework/Headers/cactus_ffi.h +0 -12
- package/ios/cactus.xcframework/tvos-arm64/cactus.framework/cactus +0 -0
- package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/Headers/cactus_ffi.h +0 -12
- package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
- package/lib/commonjs/NativeCactus.js +0 -1
- package/lib/commonjs/NativeCactus.js.map +1 -1
- package/lib/commonjs/chat.js +33 -0
- package/lib/commonjs/chat.js.map +1 -1
- package/lib/commonjs/index.js +0 -23
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lm.js +69 -32
- package/lib/commonjs/lm.js.map +1 -1
- package/lib/commonjs/tools.js +0 -7
- package/lib/commonjs/tools.js.map +1 -1
- package/lib/commonjs/tts.js +1 -4
- package/lib/commonjs/tts.js.map +1 -1
- package/lib/commonjs/vlm.js +25 -7
- package/lib/commonjs/vlm.js.map +1 -1
- package/lib/module/NativeCactus.js +0 -3
- package/lib/module/NativeCactus.js.map +1 -1
- package/lib/module/chat.js +31 -0
- package/lib/module/chat.js.map +1 -1
- package/lib/module/index.js +1 -10
- package/lib/module/index.js.map +1 -1
- package/lib/module/lm.js +70 -32
- package/lib/module/lm.js.map +1 -1
- package/lib/module/tools.js +0 -7
- package/lib/module/tools.js.map +1 -1
- package/lib/module/tts.js +1 -4
- package/lib/module/tts.js.map +1 -1
- package/lib/module/vlm.js +25 -7
- package/lib/module/vlm.js.map +1 -1
- package/lib/typescript/NativeCactus.d.ts +1 -142
- package/lib/typescript/NativeCactus.d.ts.map +1 -1
- package/lib/typescript/chat.d.ts +10 -0
- package/lib/typescript/chat.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/lm.d.ts +13 -7
- package/lib/typescript/lm.d.ts.map +1 -1
- package/lib/typescript/tools.d.ts.map +1 -1
- package/lib/typescript/tts.d.ts.map +1 -1
- package/lib/typescript/vlm.d.ts +3 -1
- package/lib/typescript/vlm.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeCactus.ts +6 -175
- package/src/chat.ts +42 -1
- package/src/index.ts +6 -17
- package/src/lm.ts +81 -26
- package/src/tools.ts +0 -5
- package/src/tts.ts +1 -4
- package/src/vlm.ts +35 -13
- package/android/src/main/jniLibs/x86_64/libcactus.so +0 -0
- package/android/src/main/jniLibs/x86_64/libcactus_x86_64.so +0 -0
- package/lib/commonjs/grammar.js +0 -560
- package/lib/commonjs/grammar.js.map +0 -1
- package/lib/module/grammar.js +0 -553
- package/lib/module/grammar.js.map +0 -1
- package/lib/typescript/grammar.d.ts +0 -37
- package/lib/typescript/grammar.d.ts.map +0 -1
- package/src/grammar.ts +0 -854
package/src/vlm.ts
CHANGED
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
initMultimodal,
|
|
4
4
|
multimodalCompletion,
|
|
5
5
|
LlamaContext,
|
|
6
|
+
type ContextParams,
|
|
7
|
+
type CompletionParams,
|
|
8
|
+
type CactusOAICompatibleMessage,
|
|
9
|
+
type NativeCompletionResult,
|
|
6
10
|
} from './index'
|
|
7
|
-
|
|
8
|
-
ContextParams,
|
|
9
|
-
CompletionParams,
|
|
10
|
-
CactusOAICompatibleMessage,
|
|
11
|
-
NativeCompletionResult,
|
|
12
|
-
} from './index'
|
|
11
|
+
|
|
13
12
|
import { Telemetry } from './telemetry'
|
|
14
13
|
import { setCactusToken, getTextCompletion, getVisionCompletion } from './remote'
|
|
14
|
+
import { ConversationHistoryManager } from './chat'
|
|
15
15
|
|
|
16
16
|
interface CactusVLMReturn {
|
|
17
17
|
vlm: CactusVLM | null
|
|
@@ -29,9 +29,11 @@ export type VLMCompletionParams = Omit<CompletionParams, 'prompt'> & {
|
|
|
29
29
|
|
|
30
30
|
export class CactusVLM {
|
|
31
31
|
private context: LlamaContext
|
|
32
|
-
|
|
32
|
+
protected conversationHistoryManager: ConversationHistoryManager
|
|
33
|
+
|
|
33
34
|
private constructor(context: LlamaContext) {
|
|
34
35
|
this.context = context
|
|
36
|
+
this.conversationHistoryManager = new ConversationHistoryManager()
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
static async init(
|
|
@@ -111,26 +113,47 @@ export class CactusVLM {
|
|
|
111
113
|
return result;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
private async
|
|
116
|
+
private _handleLocalCompletion = async(
|
|
115
117
|
messages: CactusOAICompatibleMessage[],
|
|
116
118
|
params: VLMCompletionParams,
|
|
117
119
|
callback?: (data: any) => void,
|
|
118
|
-
): Promise<NativeCompletionResult> {
|
|
120
|
+
): Promise<NativeCompletionResult> => {
|
|
121
|
+
const { newMessages, requiresReset } =
|
|
122
|
+
this.conversationHistoryManager.processNewMessages(messages);
|
|
123
|
+
|
|
124
|
+
if (requiresReset) {
|
|
125
|
+
this.context?.rewind();
|
|
126
|
+
this.conversationHistoryManager.reset();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (newMessages.length === 0) {
|
|
130
|
+
console.warn('No messages to complete!');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let result: NativeCompletionResult;
|
|
134
|
+
|
|
119
135
|
if (params.images && params.images.length > 0) {
|
|
120
|
-
const formattedPrompt = await this.context.getFormattedChat(
|
|
136
|
+
const formattedPrompt = await this.context.getFormattedChat(newMessages)
|
|
121
137
|
const prompt =
|
|
122
138
|
typeof formattedPrompt === 'string'
|
|
123
139
|
? formattedPrompt
|
|
124
140
|
: formattedPrompt.prompt
|
|
125
|
-
|
|
141
|
+
result = await multimodalCompletion(
|
|
126
142
|
this.context.id,
|
|
127
143
|
prompt,
|
|
128
144
|
params.images,
|
|
129
145
|
{ ...params, prompt, emit_partial_completion: !!callback },
|
|
130
146
|
)
|
|
131
147
|
} else {
|
|
132
|
-
|
|
148
|
+
result = await this.context.completion({ messages: newMessages, ...params }, callback)
|
|
133
149
|
}
|
|
150
|
+
|
|
151
|
+
this.conversationHistoryManager.update(newMessages, {
|
|
152
|
+
role: 'assistant',
|
|
153
|
+
content: result.content || result.text,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return result;
|
|
134
157
|
}
|
|
135
158
|
|
|
136
159
|
private async _handleRemoteCompletion(
|
|
@@ -181,7 +204,6 @@ export class CactusVLM {
|
|
|
181
204
|
}
|
|
182
205
|
|
|
183
206
|
async rewind(): Promise<void> {
|
|
184
|
-
// @ts-ignore
|
|
185
207
|
return this.context?.rewind()
|
|
186
208
|
}
|
|
187
209
|
|
|
Binary file
|
|
Binary file
|
package/lib/commonjs/grammar.js
DELETED
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.convertJsonSchemaToGrammar = exports.SchemaGrammarConverterBuiltinRule = exports.SchemaGrammarConverter = void 0;
|
|
7
|
-
/* eslint-disable no-restricted-syntax */
|
|
8
|
-
/* eslint-disable no-underscore-dangle */
|
|
9
|
-
|
|
10
|
-
// NOTE: Deprecated, please use tools or response_format with json_schema instead
|
|
11
|
-
|
|
12
|
-
const SPACE_RULE = '" "?';
|
|
13
|
-
function buildRepetition(itemRule, minItems, maxItems, opts = {}) {
|
|
14
|
-
const separatorRule = opts.separatorRule ?? '';
|
|
15
|
-
const itemRuleIsLiteral = opts.itemRuleIsLiteral ?? false;
|
|
16
|
-
if (separatorRule === '') {
|
|
17
|
-
if (minItems === 0 && maxItems === 1) {
|
|
18
|
-
return `${itemRule}?`;
|
|
19
|
-
} else if (minItems === 1 && maxItems === undefined) {
|
|
20
|
-
return `${itemRule}+`;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
let result = '';
|
|
24
|
-
if (minItems > 0) {
|
|
25
|
-
if (itemRuleIsLiteral && separatorRule === '') {
|
|
26
|
-
result = `"${itemRule.slice(1, -1).repeat(minItems)}"`;
|
|
27
|
-
} else {
|
|
28
|
-
result = Array.from({
|
|
29
|
-
length: minItems
|
|
30
|
-
}, () => itemRule).join(separatorRule !== '' ? ` ${separatorRule} ` : ' ');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const optRepetitions = (upToN, prefixWithSep = false) => {
|
|
34
|
-
const content = separatorRule !== '' && prefixWithSep ? `${separatorRule} ${itemRule}` : itemRule;
|
|
35
|
-
if (upToN === 0) {
|
|
36
|
-
return '';
|
|
37
|
-
} else if (upToN === 1) {
|
|
38
|
-
return `(${content})?`;
|
|
39
|
-
} else if (separatorRule !== '' && !prefixWithSep) {
|
|
40
|
-
return `(${content} ${optRepetitions(upToN - 1, true)})?`;
|
|
41
|
-
} else {
|
|
42
|
-
return Array.from({
|
|
43
|
-
length: upToN
|
|
44
|
-
}, () => `(${content}`).join(' ').trim() + Array.from({
|
|
45
|
-
length: upToN
|
|
46
|
-
}, () => ')?').join('');
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
if (minItems > 0 && maxItems !== minItems) {
|
|
50
|
-
result += ' ';
|
|
51
|
-
}
|
|
52
|
-
if (maxItems !== undefined) {
|
|
53
|
-
result += optRepetitions(maxItems - minItems, minItems > 0);
|
|
54
|
-
} else {
|
|
55
|
-
const itemOperator = `(${separatorRule !== '' ? `${separatorRule} ` : ''}${itemRule})`;
|
|
56
|
-
if (minItems === 0 && separatorRule !== '') {
|
|
57
|
-
result = `(${itemRule} ${itemOperator}*)?`;
|
|
58
|
-
} else {
|
|
59
|
-
result += `${itemOperator}*`;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
class SchemaGrammarConverterBuiltinRule {
|
|
65
|
-
constructor(content, deps) {
|
|
66
|
-
this.content = content;
|
|
67
|
-
this.deps = deps || [];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
exports.SchemaGrammarConverterBuiltinRule = SchemaGrammarConverterBuiltinRule;
|
|
71
|
-
const BuiltinRule = SchemaGrammarConverterBuiltinRule;
|
|
72
|
-
const UP_TO_15_DIGITS = buildRepetition('[0-9]', 0, 15);
|
|
73
|
-
const PRIMITIVE_RULES = {
|
|
74
|
-
boolean: new BuiltinRule('("true" | "false") space', []),
|
|
75
|
-
'decimal-part': new BuiltinRule(`[0-9] ${UP_TO_15_DIGITS}`, []),
|
|
76
|
-
'integral-part': new BuiltinRule(`[0-9] | [1-9] ${UP_TO_15_DIGITS}`, []),
|
|
77
|
-
number: new BuiltinRule('("-"? integral-part) ("." decimal-part)? ([eE] [-+]? integral-part)? space', ['integral-part', 'decimal-part']),
|
|
78
|
-
integer: new BuiltinRule('("-"? integral-part) space', ['integral-part']),
|
|
79
|
-
value: new BuiltinRule('object | array | string | number | boolean | null', ['object', 'array', 'string', 'number', 'boolean', 'null']),
|
|
80
|
-
object: new BuiltinRule('"{" space ( string ":" space value ("," space string ":" space value)* )? "}" space', ['string', 'value']),
|
|
81
|
-
array: new BuiltinRule('"[" space ( value ("," space value)* )? "]" space', ['value']),
|
|
82
|
-
uuid: new BuiltinRule(`"\\"" ${[8, 4, 4, 4, 12].map(n => [...new Array(n)].map(_ => '[0-9a-fA-F]').join('')).join(' "-" ')} "\\"" space`, []),
|
|
83
|
-
char: new BuiltinRule(`[^"\\\\] | "\\\\" (["\\\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])`, []),
|
|
84
|
-
string: new BuiltinRule(`"\\"" char* "\\"" space`, ['char']),
|
|
85
|
-
null: new BuiltinRule('"null" space', [])
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// TODO: support "uri", "email" string formats
|
|
89
|
-
const STRING_FORMAT_RULES = {
|
|
90
|
-
date: new BuiltinRule('[0-9] [0-9] [0-9] [0-9] "-" ( "0" [1-9] | "1" [0-2] ) "-" ( "0" [1-9] | [1-2] [0-9] | "3" [0-1] )', []),
|
|
91
|
-
time: new BuiltinRule('([01] [0-9] | "2" [0-3]) ":" [0-5] [0-9] ":" [0-5] [0-9] ( "." [0-9] [0-9] [0-9] )? ( "Z" | ( "+" | "-" ) ( [01] [0-9] | "2" [0-3] ) ":" [0-5] [0-9] )', []),
|
|
92
|
-
'date-time': new BuiltinRule('date "T" time', ['date', 'time']),
|
|
93
|
-
'date-string': new BuiltinRule('"\\"" date "\\"" space', ['date']),
|
|
94
|
-
'time-string': new BuiltinRule('"\\"" time "\\"" space', ['time']),
|
|
95
|
-
'date-time-string': new BuiltinRule('"\\"" date-time "\\"" space', ['date-time'])
|
|
96
|
-
};
|
|
97
|
-
const RESERVED_NAMES = {
|
|
98
|
-
root: true,
|
|
99
|
-
...PRIMITIVE_RULES,
|
|
100
|
-
...STRING_FORMAT_RULES
|
|
101
|
-
};
|
|
102
|
-
const INVALID_RULE_CHARS_RE = /[^\dA-Za-z-]+/g;
|
|
103
|
-
const GRAMMAR_LITERAL_ESCAPE_RE = /[\n\r"]/g;
|
|
104
|
-
const GRAMMAR_LITERAL_ESCAPES = {
|
|
105
|
-
'\r': '\\r',
|
|
106
|
-
'\n': '\\n',
|
|
107
|
-
'"': '\\"',
|
|
108
|
-
'-': '\\-',
|
|
109
|
-
']': '\\]'
|
|
110
|
-
};
|
|
111
|
-
const NON_LITERAL_SET = new Set('|.()[]{}*+?');
|
|
112
|
-
const ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS = new Set('[]()|{}*+?');
|
|
113
|
-
const formatLiteral = literal => {
|
|
114
|
-
const escaped = literal.replace(GRAMMAR_LITERAL_ESCAPE_RE, m => GRAMMAR_LITERAL_ESCAPES[m] || '');
|
|
115
|
-
return `"${escaped}"`;
|
|
116
|
-
};
|
|
117
|
-
const generateConstantRule = value => formatLiteral(JSON.stringify(value));
|
|
118
|
-
// Helper function to group elements by a key function
|
|
119
|
-
function* groupBy(iterable, keyFn) {
|
|
120
|
-
let lastKey = null;
|
|
121
|
-
let group = [];
|
|
122
|
-
for (const element of iterable) {
|
|
123
|
-
const key = keyFn(element);
|
|
124
|
-
if (lastKey !== null && key !== lastKey) {
|
|
125
|
-
yield [lastKey, group];
|
|
126
|
-
group = [];
|
|
127
|
-
}
|
|
128
|
-
group.push(element);
|
|
129
|
-
lastKey = key;
|
|
130
|
-
}
|
|
131
|
-
if (group.length > 0) {
|
|
132
|
-
yield [lastKey, group];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
class SchemaGrammarConverter {
|
|
136
|
-
constructor(options) {
|
|
137
|
-
this._propOrder = options.prop_order || {};
|
|
138
|
-
this._allowFetch = options.allow_fetch || false;
|
|
139
|
-
this._dotall = options.dotall || false;
|
|
140
|
-
this._rules = {
|
|
141
|
-
space: SPACE_RULE
|
|
142
|
-
};
|
|
143
|
-
this._refs = {};
|
|
144
|
-
this._refsBeingResolved = new Set();
|
|
145
|
-
}
|
|
146
|
-
_addRule(name, rule) {
|
|
147
|
-
const escName = name.replace(INVALID_RULE_CHARS_RE, '-');
|
|
148
|
-
let key = escName;
|
|
149
|
-
if (escName in this._rules) {
|
|
150
|
-
if (this._rules[escName] === rule) {
|
|
151
|
-
return key;
|
|
152
|
-
}
|
|
153
|
-
let i = 0;
|
|
154
|
-
while (`${escName}${i}` in this._rules && this._rules[`${escName}${i}`] !== rule) {
|
|
155
|
-
i += 1;
|
|
156
|
-
}
|
|
157
|
-
key = `${escName}${i}`;
|
|
158
|
-
}
|
|
159
|
-
this._rules[key] = rule;
|
|
160
|
-
return key;
|
|
161
|
-
}
|
|
162
|
-
async resolveRefs(schema, url) {
|
|
163
|
-
const visit = async n => {
|
|
164
|
-
if (Array.isArray(n)) {
|
|
165
|
-
return Promise.all(n.map(visit));
|
|
166
|
-
} else if (typeof n === 'object' && n !== null) {
|
|
167
|
-
let ref = n.$ref;
|
|
168
|
-
let target;
|
|
169
|
-
if (ref !== undefined && !this._refs[ref]) {
|
|
170
|
-
if (ref.startsWith('https://')) {
|
|
171
|
-
if (!this._allowFetch) {
|
|
172
|
-
throw new Error('Fetching remote schemas is not allowed (use --allow-fetch for force)');
|
|
173
|
-
}
|
|
174
|
-
const fragSplit = ref.split('#');
|
|
175
|
-
const baseUrl = fragSplit[0];
|
|
176
|
-
target = this._refs[baseUrl];
|
|
177
|
-
if (!target) {
|
|
178
|
-
target = await this.resolveRefs(await fetch(ref).then(res => res.json()), baseUrl);
|
|
179
|
-
this._refs[baseUrl] = target;
|
|
180
|
-
}
|
|
181
|
-
if (fragSplit.length === 1 || fragSplit[fragSplit.length - 1] === '') {
|
|
182
|
-
return target;
|
|
183
|
-
}
|
|
184
|
-
} else if (ref.startsWith('#/')) {
|
|
185
|
-
target = schema;
|
|
186
|
-
ref = `${url}${ref}`;
|
|
187
|
-
n.$ref = ref;
|
|
188
|
-
} else {
|
|
189
|
-
throw new Error(`Unsupported ref ${ref}`);
|
|
190
|
-
}
|
|
191
|
-
const selectors = ref.split('#')[1].split('/').slice(1);
|
|
192
|
-
for (const sel of selectors) {
|
|
193
|
-
if (!target || !(sel in target)) {
|
|
194
|
-
throw new Error(`Error resolving ref ${ref}: ${sel} not in ${JSON.stringify(target)}`);
|
|
195
|
-
}
|
|
196
|
-
target = target[sel];
|
|
197
|
-
}
|
|
198
|
-
this._refs[ref] = target;
|
|
199
|
-
} else {
|
|
200
|
-
await Promise.all(Object.values(n).map(visit));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return n;
|
|
204
|
-
};
|
|
205
|
-
return visit(schema);
|
|
206
|
-
}
|
|
207
|
-
_generateUnionRule(name, altSchemas) {
|
|
208
|
-
return altSchemas.map((altSchema, i) => this.visit(altSchema, `${name ?? ''}${name ? '-' : 'alternative-'}${i}`)).join(' | ');
|
|
209
|
-
}
|
|
210
|
-
_visitPattern(pattern, name) {
|
|
211
|
-
if (!pattern.startsWith('^') || !pattern.endsWith('$')) {
|
|
212
|
-
throw new Error('Pattern must start with "^" and end with "$"');
|
|
213
|
-
}
|
|
214
|
-
pattern = pattern.slice(1, -1);
|
|
215
|
-
const subRuleIds = {};
|
|
216
|
-
let i = 0;
|
|
217
|
-
const {
|
|
218
|
-
length
|
|
219
|
-
} = pattern;
|
|
220
|
-
const getDot = () => {
|
|
221
|
-
let rule;
|
|
222
|
-
if (this._dotall) {
|
|
223
|
-
rule = '[\\U00000000-\\U0010FFFF]';
|
|
224
|
-
} else {
|
|
225
|
-
// Accept any character... except \n and \r line break chars (\x0A and \xOD)
|
|
226
|
-
rule = '[^\\x0A\\x0D]';
|
|
227
|
-
}
|
|
228
|
-
return this._addRule('dot', rule);
|
|
229
|
-
};
|
|
230
|
-
const toRule = ([s, isLiteral]) => isLiteral ? `"${s}"` : s;
|
|
231
|
-
const transform = () => {
|
|
232
|
-
const start = i;
|
|
233
|
-
// For each component of this sequence, store its string representation and whether it's a literal.
|
|
234
|
-
// We only need a flat structure here to apply repetition operators to the last item, and
|
|
235
|
-
// to merge literals at the and (we're parsing grouped ( sequences ) recursively and don't treat '|' specially
|
|
236
|
-
// (GBNF's syntax is luckily very close to regular expressions!)
|
|
237
|
-
const seq = [];
|
|
238
|
-
const joinSeq = () => {
|
|
239
|
-
const ret = [];
|
|
240
|
-
for (const [isLiteral, g] of groupBy(seq, x => x[1])) {
|
|
241
|
-
if (isLiteral) {
|
|
242
|
-
ret.push([[...g].map(x => x[0]).join(''), true]);
|
|
243
|
-
} else {
|
|
244
|
-
ret.push(...g);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
if (ret.length === 1) {
|
|
248
|
-
return ret[0];
|
|
249
|
-
}
|
|
250
|
-
return [ret.map(x => toRule(x)).join(' '), false];
|
|
251
|
-
};
|
|
252
|
-
while (i < length) {
|
|
253
|
-
const c = pattern[i];
|
|
254
|
-
if (c === '.') {
|
|
255
|
-
seq.push([getDot(), false]);
|
|
256
|
-
i += 1;
|
|
257
|
-
} else if (c === '(') {
|
|
258
|
-
i += 1;
|
|
259
|
-
if (i < length) {
|
|
260
|
-
if (pattern[i] === '?') {
|
|
261
|
-
throw new Error(`Unsupported pattern syntax "${pattern[i]}" at index ${i} of /${pattern}/`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
seq.push([`(${toRule(transform())})`, false]);
|
|
265
|
-
} else if (c === ')') {
|
|
266
|
-
i += 1;
|
|
267
|
-
if (start <= 0 || pattern[start - 1] !== '(') {
|
|
268
|
-
throw new Error(`Unbalanced parentheses; start = ${start}, i = ${i}, pattern = ${pattern}`);
|
|
269
|
-
}
|
|
270
|
-
return joinSeq();
|
|
271
|
-
} else if (c === '[') {
|
|
272
|
-
let squareBrackets = c;
|
|
273
|
-
i += 1;
|
|
274
|
-
while (i < length && pattern[i] !== ']') {
|
|
275
|
-
if (pattern[i] === '\\') {
|
|
276
|
-
squareBrackets += pattern.slice(i, i + 2);
|
|
277
|
-
i += 2;
|
|
278
|
-
} else {
|
|
279
|
-
squareBrackets += pattern[i];
|
|
280
|
-
i += 1;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (i >= length) {
|
|
284
|
-
throw new Error(`Unbalanced square brackets; start = ${start}, i = ${i}, pattern = ${pattern}`);
|
|
285
|
-
}
|
|
286
|
-
squareBrackets += ']';
|
|
287
|
-
i += 1;
|
|
288
|
-
seq.push([squareBrackets, false]);
|
|
289
|
-
} else if (c === '|') {
|
|
290
|
-
seq.push(['|', false]);
|
|
291
|
-
i += 1;
|
|
292
|
-
} else if (c === '*' || c === '+' || c === '?') {
|
|
293
|
-
seq[seq.length - 1] = [toRule(seq[seq.length - 1] || ['', false]) + c, false];
|
|
294
|
-
i += 1;
|
|
295
|
-
} else if (c === '{') {
|
|
296
|
-
let curlyBrackets = c;
|
|
297
|
-
i += 1;
|
|
298
|
-
while (i < length && pattern[i] !== '}') {
|
|
299
|
-
curlyBrackets += pattern[i];
|
|
300
|
-
i += 1;
|
|
301
|
-
}
|
|
302
|
-
if (i >= length) {
|
|
303
|
-
throw new Error(`Unbalanced curly brackets; start = ${start}, i = ${i}, pattern = ${pattern}`);
|
|
304
|
-
}
|
|
305
|
-
curlyBrackets += '}';
|
|
306
|
-
i += 1;
|
|
307
|
-
const nums = curlyBrackets.slice(1, -1).split(',').map(s => s.trim());
|
|
308
|
-
let minTimes;
|
|
309
|
-
let maxTimes;
|
|
310
|
-
if (nums.length === 1) {
|
|
311
|
-
minTimes = parseInt(nums[0], 10);
|
|
312
|
-
maxTimes = minTimes;
|
|
313
|
-
} else {
|
|
314
|
-
if (nums.length !== 2) {
|
|
315
|
-
throw new Error(`Invalid quantifier ${curlyBrackets}`);
|
|
316
|
-
}
|
|
317
|
-
minTimes = nums[0] ? parseInt(nums[0], 10) : 0;
|
|
318
|
-
maxTimes = nums[1] ? parseInt(nums[1], 10) : Infinity;
|
|
319
|
-
}
|
|
320
|
-
let [sub] = seq[seq.length - 1] || ['', false];
|
|
321
|
-
const [, subIsLiteral] = seq[seq.length - 1] || ['', false];
|
|
322
|
-
if (!subIsLiteral) {
|
|
323
|
-
let id = subRuleIds[sub];
|
|
324
|
-
if (id === undefined) {
|
|
325
|
-
id = this._addRule(`${name}-${Object.keys(subRuleIds).length + 1}`, sub);
|
|
326
|
-
subRuleIds[sub] = id;
|
|
327
|
-
}
|
|
328
|
-
sub = id;
|
|
329
|
-
}
|
|
330
|
-
seq[seq.length - 1] = [buildRepetition(subIsLiteral ? `"${sub}"` : sub, minTimes, maxTimes, {
|
|
331
|
-
itemRuleIsLiteral: subIsLiteral
|
|
332
|
-
}), false];
|
|
333
|
-
} else {
|
|
334
|
-
let literal = '';
|
|
335
|
-
while (i < length) {
|
|
336
|
-
if (pattern[i] === '\\' && i < length - 1) {
|
|
337
|
-
const next = pattern[i + 1];
|
|
338
|
-
if (ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS.has(next || '')) {
|
|
339
|
-
i += 1;
|
|
340
|
-
literal += pattern[i];
|
|
341
|
-
i += 1;
|
|
342
|
-
} else {
|
|
343
|
-
literal += pattern.slice(i, i + 2);
|
|
344
|
-
i += 2;
|
|
345
|
-
}
|
|
346
|
-
} else if (pattern[i] === '"') {
|
|
347
|
-
literal += '\\"';
|
|
348
|
-
i += 1;
|
|
349
|
-
} else if (!NON_LITERAL_SET.has(pattern[i] || '') && (i === length - 1 || literal === '' || pattern[i + 1] === '.' || !NON_LITERAL_SET.has(pattern[i + 1] || ''))) {
|
|
350
|
-
literal += pattern[i];
|
|
351
|
-
i += 1;
|
|
352
|
-
} else {
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (literal !== '') {
|
|
357
|
-
seq.push([literal, true]);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return joinSeq();
|
|
362
|
-
};
|
|
363
|
-
return this._addRule(name, `"\\"" ${toRule(transform())} "\\"" space`);
|
|
364
|
-
}
|
|
365
|
-
_resolveRef(ref) {
|
|
366
|
-
let refName = ref.split('/').pop() || '';
|
|
367
|
-
if (!(refName in this._rules) && !this._refsBeingResolved.has(ref)) {
|
|
368
|
-
this._refsBeingResolved.add(ref);
|
|
369
|
-
const resolved = this._refs[ref];
|
|
370
|
-
refName = this.visit(resolved, refName);
|
|
371
|
-
this._refsBeingResolved.delete(ref);
|
|
372
|
-
}
|
|
373
|
-
return refName;
|
|
374
|
-
}
|
|
375
|
-
visit(schema, name) {
|
|
376
|
-
const schemaType = schema.type;
|
|
377
|
-
const schemaFormat = schema.format;
|
|
378
|
-
const isRoot = name in RESERVED_NAMES ? `${name}-` : name == '';
|
|
379
|
-
const ruleName = isRoot ? 'root' : name;
|
|
380
|
-
const ref = schema.$ref;
|
|
381
|
-
if (ref !== undefined) {
|
|
382
|
-
return this._addRule(ruleName, this._resolveRef(ref));
|
|
383
|
-
} else if (schema.oneOf || schema.anyOf) {
|
|
384
|
-
return this._addRule(ruleName, this._generateUnionRule(name, schema.oneOf || schema.anyOf));
|
|
385
|
-
} else if (Array.isArray(schemaType)) {
|
|
386
|
-
return this._addRule(ruleName, this._generateUnionRule(name, schemaType.map(t => ({
|
|
387
|
-
type: t
|
|
388
|
-
}))));
|
|
389
|
-
} else if ('const' in schema) {
|
|
390
|
-
return this._addRule(ruleName, generateConstantRule(schema.const));
|
|
391
|
-
} else if ('enum' in schema) {
|
|
392
|
-
const rule = schema.enum.map(v => generateConstantRule(v)).join(' | ');
|
|
393
|
-
return this._addRule(ruleName, rule);
|
|
394
|
-
} else if ((schemaType === undefined || schemaType === 'object') && ('properties' in schema || 'additionalProperties' in schema && schema.additionalProperties !== true)) {
|
|
395
|
-
const required = new Set(schema.required || []);
|
|
396
|
-
const properties = Object.entries(schema.properties ?? {});
|
|
397
|
-
return this._addRule(ruleName, this._buildObjectRule(properties, required, name, schema.additionalProperties));
|
|
398
|
-
} else if ((schemaType === undefined || schemaType === 'object') && 'allOf' in schema) {
|
|
399
|
-
const required = new Set();
|
|
400
|
-
const properties = [];
|
|
401
|
-
const addComponent = (compSchema, isRequired) => {
|
|
402
|
-
if (compSchema.$ref !== undefined) {
|
|
403
|
-
compSchema = this._refs[compSchema.$ref];
|
|
404
|
-
}
|
|
405
|
-
if ('properties' in compSchema) {
|
|
406
|
-
for (const [propName, propSchema] of Object.entries(compSchema.properties)) {
|
|
407
|
-
properties.push([propName, propSchema]);
|
|
408
|
-
if (isRequired) {
|
|
409
|
-
required.add(propName);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
for (const t of schema.allOf) {
|
|
415
|
-
if ('anyOf' in t) {
|
|
416
|
-
for (const tt of t.anyOf) {
|
|
417
|
-
addComponent(tt, false);
|
|
418
|
-
}
|
|
419
|
-
} else {
|
|
420
|
-
addComponent(t, true);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return this._addRule(ruleName, this._buildObjectRule(properties, required, name, /* additionalProperties= */false));
|
|
424
|
-
} else if ((schemaType === undefined || schemaType === 'array') && ('items' in schema || 'prefixItems' in schema)) {
|
|
425
|
-
const items = schema.items ?? schema.prefixItems;
|
|
426
|
-
if (Array.isArray(items)) {
|
|
427
|
-
const rules = items.map((item, i) => this.visit(item, `${name ?? ''}${name ? '-' : ''}tuple-${i}`)).join(' "," space ');
|
|
428
|
-
return this._addRule(ruleName, `"[" space ${rules} "]" space`);
|
|
429
|
-
} else {
|
|
430
|
-
const itemRuleName = this.visit(items, `${name ?? ''}${name ? '-' : ''}item`);
|
|
431
|
-
const minItems = schema.minItems || 0;
|
|
432
|
-
const {
|
|
433
|
-
maxItems
|
|
434
|
-
} = schema;
|
|
435
|
-
return this._addRule(ruleName, `"[" space ${buildRepetition(itemRuleName, minItems, maxItems, {
|
|
436
|
-
separatorRule: '"," space'
|
|
437
|
-
})} "]" space`);
|
|
438
|
-
}
|
|
439
|
-
} else if ((schemaType === undefined || schemaType === 'string') && 'pattern' in schema) {
|
|
440
|
-
return this._visitPattern(schema.pattern, ruleName);
|
|
441
|
-
} else if ((schemaType === undefined || schemaType === 'string') && /^uuid[1-5]?$/.test(schema.format || '')) {
|
|
442
|
-
return this._addPrimitive(ruleName === 'root' ? 'root' : schemaFormat, PRIMITIVE_RULES['uuid']);
|
|
443
|
-
} else if ((schemaType === undefined || schemaType === 'string') && `${schema.format}-string` in STRING_FORMAT_RULES) {
|
|
444
|
-
const primName = `${schema.format}-string`;
|
|
445
|
-
return this._addRule(ruleName, this._addPrimitive(primName, STRING_FORMAT_RULES[primName]));
|
|
446
|
-
} else if (schemaType === 'string' && ('minLength' in schema || 'maxLength' in schema)) {
|
|
447
|
-
const charRuleName = this._addPrimitive('char', PRIMITIVE_RULES['char']);
|
|
448
|
-
const minLen = schema.minLength || 0;
|
|
449
|
-
const maxLen = schema.maxLength;
|
|
450
|
-
return this._addRule(ruleName, `"\\"" ${buildRepetition(charRuleName, minLen, maxLen)} "\\"" space`);
|
|
451
|
-
} else if (schemaType === 'object' || Object.keys(schema).length === 0) {
|
|
452
|
-
return this._addRule(ruleName, this._addPrimitive('object', PRIMITIVE_RULES['object']));
|
|
453
|
-
} else {
|
|
454
|
-
if (!(schemaType in PRIMITIVE_RULES)) {
|
|
455
|
-
throw new Error(`Unrecognized schema: ${JSON.stringify(schema)}`);
|
|
456
|
-
}
|
|
457
|
-
// TODO: support minimum, maximum, exclusiveMinimum, exclusiveMaximum at least for zero
|
|
458
|
-
return this._addPrimitive(ruleName === 'root' ? 'root' : schemaType, PRIMITIVE_RULES[schemaType]);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
_addPrimitive(name, rule) {
|
|
462
|
-
if (!rule) {
|
|
463
|
-
throw new Error(`Rule ${name} not known`);
|
|
464
|
-
}
|
|
465
|
-
const n = this._addRule(name, rule.content);
|
|
466
|
-
for (const dep of rule.deps) {
|
|
467
|
-
const depRule = PRIMITIVE_RULES[dep] || STRING_FORMAT_RULES[dep];
|
|
468
|
-
if (!depRule) {
|
|
469
|
-
throw new Error(`Rule ${dep} not known`);
|
|
470
|
-
}
|
|
471
|
-
if (!(dep in this._rules)) {
|
|
472
|
-
this._addPrimitive(dep, depRule);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
return n;
|
|
476
|
-
}
|
|
477
|
-
_buildObjectRule(properties, required, name, additionalProperties) {
|
|
478
|
-
const propOrder = this._propOrder;
|
|
479
|
-
// sort by position in prop_order (if specified) then by original order
|
|
480
|
-
const sortedProps = properties.map(([k]) => k).sort((a, b) => {
|
|
481
|
-
const orderA = propOrder[a] || Infinity;
|
|
482
|
-
const orderB = propOrder[b] || Infinity;
|
|
483
|
-
return orderA - orderB || properties.findIndex(([k]) => k === a) - properties.findIndex(([k]) => k === b);
|
|
484
|
-
});
|
|
485
|
-
const propKvRuleNames = {};
|
|
486
|
-
for (const [propName, propSchema] of properties) {
|
|
487
|
-
const propRuleName = this.visit(propSchema, `${name ?? ''}${name ? '-' : ''}${propName}`);
|
|
488
|
-
propKvRuleNames[propName] = this._addRule(`${name ?? ''}${name ? '-' : ''}${propName}-kv`, `${formatLiteral(JSON.stringify(propName))} space ":" space ${propRuleName}`);
|
|
489
|
-
}
|
|
490
|
-
const requiredProps = sortedProps.filter(k => required.has(k));
|
|
491
|
-
const optionalProps = sortedProps.filter(k => !required.has(k));
|
|
492
|
-
if (typeof additionalProperties === 'object' || additionalProperties === true) {
|
|
493
|
-
const subName = `${name ?? ''}${name ? '-' : ''}additional`;
|
|
494
|
-
const valueRule = this.visit(additionalProperties === true ? {} : additionalProperties, `${subName}-value`);
|
|
495
|
-
propKvRuleNames['*'] = this._addRule(`${subName}-kv`, `${this._addPrimitive('string', PRIMITIVE_RULES['string'])} ":" space ${valueRule}`);
|
|
496
|
-
optionalProps.push('*');
|
|
497
|
-
}
|
|
498
|
-
let rule = '"{" space ';
|
|
499
|
-
rule += requiredProps.map(k => propKvRuleNames[k]).join(' "," space ');
|
|
500
|
-
if (optionalProps.length > 0) {
|
|
501
|
-
rule += ' (';
|
|
502
|
-
if (requiredProps.length > 0) {
|
|
503
|
-
rule += ' "," space ( ';
|
|
504
|
-
}
|
|
505
|
-
const getRecursiveRefs = (ks, firstIsOptional) => {
|
|
506
|
-
const [k, ...rest] = ks;
|
|
507
|
-
const kvRuleName = propKvRuleNames[k];
|
|
508
|
-
let res;
|
|
509
|
-
if (k === '*') {
|
|
510
|
-
res = this._addRule(`${name ?? ''}${name ? '-' : ''}additional-kvs`, `${kvRuleName} ( "," space ${kvRuleName} )*`);
|
|
511
|
-
} else if (firstIsOptional) {
|
|
512
|
-
res = `( "," space ${kvRuleName} )?`;
|
|
513
|
-
} else {
|
|
514
|
-
res = kvRuleName;
|
|
515
|
-
}
|
|
516
|
-
if (rest.length > 0) {
|
|
517
|
-
res += ` ${this._addRule(`${name ?? ''}${name ? '-' : ''}${k}-rest`, getRecursiveRefs(rest, true) || '')}`;
|
|
518
|
-
}
|
|
519
|
-
return res;
|
|
520
|
-
};
|
|
521
|
-
rule += optionalProps.map((_, i) => getRecursiveRefs(optionalProps.slice(i), false)).join(' | ');
|
|
522
|
-
if (requiredProps.length > 0) {
|
|
523
|
-
rule += ' )';
|
|
524
|
-
}
|
|
525
|
-
rule += ' )?';
|
|
526
|
-
}
|
|
527
|
-
rule += ' "}" space';
|
|
528
|
-
return rule;
|
|
529
|
-
}
|
|
530
|
-
formatGrammar() {
|
|
531
|
-
let grammar = '';
|
|
532
|
-
for (const [name, rule] of Object.entries(this._rules).sort(([a], [b]) => a.localeCompare(b))) {
|
|
533
|
-
grammar += `${name} ::= ${rule}\n`;
|
|
534
|
-
}
|
|
535
|
-
return grammar;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
exports.SchemaGrammarConverter = SchemaGrammarConverter;
|
|
539
|
-
const convertJsonSchemaToGrammar = ({
|
|
540
|
-
schema,
|
|
541
|
-
propOrder,
|
|
542
|
-
dotall,
|
|
543
|
-
allowFetch
|
|
544
|
-
}) => {
|
|
545
|
-
const converter = new SchemaGrammarConverter({
|
|
546
|
-
prop_order: propOrder,
|
|
547
|
-
dotall,
|
|
548
|
-
allow_fetch: allowFetch
|
|
549
|
-
});
|
|
550
|
-
if (allowFetch) {
|
|
551
|
-
return converter.resolveRefs(schema, '').then(() => {
|
|
552
|
-
converter.visit(schema, '');
|
|
553
|
-
return converter.formatGrammar();
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
converter.visit(schema, '');
|
|
557
|
-
return converter.formatGrammar();
|
|
558
|
-
};
|
|
559
|
-
exports.convertJsonSchemaToGrammar = convertJsonSchemaToGrammar;
|
|
560
|
-
//# sourceMappingURL=grammar.js.map
|