@longsightgroup/qti3-core 0.10.6 → 0.12.0
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 +126 -0
- package/dist/adaptive-turn-materializer.js +4 -5
- package/dist/adaptive-turn-materializer.js.map +1 -1
- package/dist/adaptive-turn.d.ts.map +1 -1
- package/dist/adaptive-turn.js +1 -0
- package/dist/adaptive-turn.js.map +1 -1
- package/dist/attempt-state.d.ts.map +1 -1
- package/dist/attempt-state.js +41 -1
- package/dist/attempt-state.js.map +1 -1
- package/dist/binary-choice.d.ts +9 -0
- package/dist/binary-choice.d.ts.map +1 -0
- package/dist/binary-choice.js +81 -0
- package/dist/binary-choice.js.map +1 -0
- package/dist/diagnostics.d.ts +13 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +28 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +19 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/item-submission-materialization.d.ts.map +1 -1
- package/dist/item-submission-materialization.js +2 -0
- package/dist/item-submission-materialization.js.map +1 -1
- package/dist/parser-processing.d.ts.map +1 -1
- package/dist/parser-processing.js +8 -18
- package/dist/parser-processing.js.map +1 -1
- package/dist/parser-values.d.ts.map +1 -1
- package/dist/parser-values.js +1 -3
- package/dist/parser-values.js.map +1 -1
- package/dist/parser.js +32 -0
- package/dist/parser.js.map +1 -1
- package/dist/processing-equality.d.ts +10 -0
- package/dist/processing-equality.d.ts.map +1 -0
- package/dist/processing-equality.js +78 -0
- package/dist/processing-equality.js.map +1 -0
- package/dist/processing-evaluator-comparison.d.ts.map +1 -1
- package/dist/processing-evaluator-comparison.js +14 -9
- package/dist/processing-evaluator-comparison.js.map +1 -1
- package/dist/processing-evaluator-numeric.d.ts.map +1 -1
- package/dist/processing-evaluator-numeric.js +2 -1
- package/dist/processing-evaluator-numeric.js.map +1 -1
- package/dist/processing-evaluator-string.d.ts.map +1 -1
- package/dist/processing-evaluator-string.js +26 -10
- package/dist/processing-evaluator-string.js.map +1 -1
- package/dist/processing-evaluator-variable.d.ts.map +1 -1
- package/dist/processing-evaluator-variable.js +9 -3
- package/dist/processing-evaluator-variable.js.map +1 -1
- package/dist/processing-evaluator.d.ts +5 -1
- package/dist/processing-evaluator.d.ts.map +1 -1
- package/dist/processing-evaluator.js +13 -4
- package/dist/processing-evaluator.js.map +1 -1
- package/dist/processing-expression-children.d.ts.map +1 -1
- package/dist/processing-expression-children.js +2 -3
- package/dist/processing-expression-children.js.map +1 -1
- package/dist/processing-expression-tags.d.ts +2 -1
- package/dist/processing-expression-tags.d.ts.map +1 -1
- package/dist/processing-expression-tags.js +2 -1
- package/dist/processing-expression-tags.js.map +1 -1
- package/dist/processing-mapping.d.ts.map +1 -1
- package/dist/processing-mapping.js +26 -16
- package/dist/processing-mapping.js.map +1 -1
- package/dist/processing-state.d.ts +2 -2
- package/dist/processing-state.d.ts.map +1 -1
- package/dist/processing-state.js +5 -2
- package/dist/processing-state.js.map +1 -1
- package/dist/processing-values.d.ts +1 -2
- package/dist/processing-values.d.ts.map +1 -1
- package/dist/processing-values.js +1 -3
- package/dist/processing-values.js.map +1 -1
- package/dist/processing-variables.d.ts.map +1 -1
- package/dist/processing-variables.js +10 -5
- package/dist/processing-variables.js.map +1 -1
- package/dist/qti-identifier.d.ts +3 -0
- package/dist/qti-identifier.d.ts.map +1 -0
- package/dist/qti-identifier.js +5 -0
- package/dist/qti-identifier.js.map +1 -0
- package/dist/qti-package-assets.d.ts.map +1 -1
- package/dist/qti-package-assets.js +16 -10
- package/dist/qti-package-assets.js.map +1 -1
- package/dist/qti-package-manifest.d.ts +1 -0
- package/dist/qti-package-manifest.d.ts.map +1 -1
- package/dist/qti-package-manifest.js +1 -0
- package/dist/qti-package-manifest.js.map +1 -1
- package/dist/qti-package-metadata.d.ts.map +1 -1
- package/dist/qti-package-metadata.js +11 -0
- package/dist/qti-package-metadata.js.map +1 -1
- package/dist/qti-package-paths.d.ts +2 -0
- package/dist/qti-package-paths.d.ts.map +1 -1
- package/dist/qti-package-paths.js +6 -0
- package/dist/qti-package-paths.js.map +1 -1
- package/dist/qti-package-plan.d.ts +1 -1
- package/dist/qti-package-plan.d.ts.map +1 -1
- package/dist/qti-package-types.d.ts +9 -0
- package/dist/qti-package-types.d.ts.map +1 -1
- package/dist/qti-package-xml.d.ts +6 -1
- package/dist/qti-package-xml.d.ts.map +1 -1
- package/dist/qti-package-xml.js +11 -6
- package/dist/qti-package-xml.js.map +1 -1
- package/dist/qti-package-zip.d.ts +13 -1
- package/dist/qti-package-zip.d.ts.map +1 -1
- package/dist/qti-package-zip.js +185 -54
- package/dist/qti-package-zip.js.map +1 -1
- package/dist/qti-package.d.ts +12 -5
- package/dist/qti-package.d.ts.map +1 -1
- package/dist/qti-package.js +63 -8
- package/dist/qti-package.js.map +1 -1
- package/dist/response-validation-policy.d.ts.map +1 -1
- package/dist/response-validation-policy.js +3 -1
- package/dist/response-validation-policy.js.map +1 -1
- package/dist/response-validation.d.ts.map +1 -1
- package/dist/response-validation.js +2 -1
- package/dist/response-validation.js.map +1 -1
- package/dist/serializer-processing-expressions.d.ts.map +1 -1
- package/dist/serializer-processing-expressions.js +2 -10
- package/dist/serializer-processing-expressions.js.map +1 -1
- package/dist/server-scoring.d.ts.map +1 -1
- package/dist/server-scoring.js +1 -0
- package/dist/server-scoring.js.map +1 -1
- package/dist/session-builtins.d.ts +16 -0
- package/dist/session-builtins.d.ts.map +1 -0
- package/dist/session-builtins.js +58 -0
- package/dist/session-builtins.js.map +1 -0
- package/dist/session.d.ts +5 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +63 -11
- package/dist/session.js.map +1 -1
- package/dist/support-evidence.d.ts.map +1 -1
- package/dist/support-evidence.js +2 -0
- package/dist/support-evidence.js.map +1 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +66 -23
- package/dist/support.js.map +1 -1
- package/dist/test-expression-parser.d.ts +6 -0
- package/dist/test-expression-parser.d.ts.map +1 -0
- package/dist/test-expression-parser.js +76 -0
- package/dist/test-expression-parser.js.map +1 -0
- package/dist/test-expression-serializer.d.ts +5 -0
- package/dist/test-expression-serializer.d.ts.map +1 -0
- package/dist/test-expression-serializer.js +54 -0
- package/dist/test-expression-serializer.js.map +1 -0
- package/dist/test-expression-validation.d.ts +14 -0
- package/dist/test-expression-validation.d.ts.map +1 -0
- package/dist/test-expression-validation.js +101 -0
- package/dist/test-expression-validation.js.map +1 -0
- package/dist/test-expression.d.ts +99 -0
- package/dist/test-expression.d.ts.map +1 -0
- package/dist/test-expression.js +28 -0
- package/dist/test-expression.js.map +1 -0
- package/dist/test-model.d.ts +62 -0
- package/dist/test-model.d.ts.map +1 -0
- package/dist/test-model.js +5 -0
- package/dist/test-model.js.map +1 -0
- package/dist/test-parser.d.ts +13 -0
- package/dist/test-parser.d.ts.map +1 -0
- package/dist/test-parser.js +144 -0
- package/dist/test-parser.js.map +1 -0
- package/dist/test-processing.d.ts +22 -0
- package/dist/test-processing.d.ts.map +1 -0
- package/dist/test-processing.js +84 -0
- package/dist/test-processing.js.map +1 -0
- package/dist/test-session.d.ts +31 -0
- package/dist/test-session.d.ts.map +1 -0
- package/dist/test-session.js +102 -0
- package/dist/test-session.js.map +1 -0
- package/dist/test-support.d.ts +4 -0
- package/dist/test-support.d.ts.map +1 -0
- package/dist/test-support.js +30 -0
- package/dist/test-support.js.map +1 -0
- package/dist/test-validation.d.ts +4 -0
- package/dist/test-validation.d.ts.map +1 -0
- package/dist/test-validation.js +133 -0
- package/dist/test-validation.js.map +1 -0
- package/dist/test-xml.d.ts +7 -0
- package/dist/test-xml.d.ts.map +1 -0
- package/dist/test-xml.js +23 -0
- package/dist/test-xml.js.map +1 -0
- package/dist/trusted-item-session.d.ts +4 -1
- package/dist/trusted-item-session.d.ts.map +1 -1
- package/dist/trusted-item-session.js +8 -3
- package/dist/trusted-item-session.js.map +1 -1
- package/dist/types.d.ts +26 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/validation-declaration-entries.d.ts.map +1 -1
- package/dist/validation-declaration-entries.js +11 -1
- package/dist/validation-declaration-entries.js.map +1 -1
- package/dist/validation-item-children.d.ts +4 -0
- package/dist/validation-item-children.d.ts.map +1 -0
- package/dist/validation-item-children.js +45 -0
- package/dist/validation-item-children.js.map +1 -0
- package/dist/validation-processing.d.ts.map +1 -1
- package/dist/validation-processing.js +45 -12
- package/dist/validation-processing.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +12 -0
- package/dist/validation.js.map +1 -1
- package/dist/value-guards.d.ts +4 -0
- package/dist/value-guards.d.ts.map +1 -0
- package/dist/value-guards.js +5 -0
- package/dist/value-guards.js.map +1 -0
- package/dist/xml-schema-regex-data.d.ts +48 -0
- package/dist/xml-schema-regex-data.d.ts.map +1 -0
- package/dist/xml-schema-regex-data.js +403 -0
- package/dist/xml-schema-regex-data.js.map +1 -0
- package/dist/xml-schema-regex-parser.d.ts +31 -0
- package/dist/xml-schema-regex-parser.d.ts.map +1 -0
- package/dist/xml-schema-regex-parser.js +245 -0
- package/dist/xml-schema-regex-parser.js.map +1 -0
- package/dist/xml-schema-regex.d.ts +14 -0
- package/dist/xml-schema-regex.d.ts.map +1 -0
- package/dist/xml-schema-regex.js +114 -0
- package/dist/xml-schema-regex.js.map +1 -0
- package/package.json +1 -1
- package/src/adaptive-turn-materializer.ts +4 -3
- package/src/adaptive-turn.ts +1 -0
- package/src/attempt-state.ts +54 -1
- package/src/binary-choice.ts +96 -0
- package/src/diagnostics.ts +29 -0
- package/src/index.ts +38 -0
- package/src/item-submission-materialization.ts +2 -0
- package/src/parser-processing.ts +15 -18
- package/src/parser-values.ts +1 -5
- package/src/parser.ts +34 -0
- package/src/processing-equality.ts +104 -0
- package/src/processing-evaluator-comparison.ts +19 -14
- package/src/processing-evaluator-numeric.ts +2 -6
- package/src/processing-evaluator-string.ts +25 -18
- package/src/processing-evaluator-variable.ts +8 -3
- package/src/processing-evaluator.ts +20 -7
- package/src/processing-expression-children.ts +2 -3
- package/src/processing-expression-tags.ts +2 -1
- package/src/processing-mapping.ts +31 -25
- package/src/processing-state.ts +6 -1
- package/src/processing-values.ts +2 -11
- package/src/processing-variables.ts +9 -7
- package/src/qti-identifier.ts +4 -0
- package/src/qti-package-assets.ts +20 -13
- package/src/qti-package-manifest.ts +1 -0
- package/src/qti-package-metadata.ts +11 -0
- package/src/qti-package-paths.ts +9 -0
- package/src/qti-package-plan.ts +1 -1
- package/src/qti-package-types.ts +10 -0
- package/src/qti-package-xml.ts +11 -6
- package/src/qti-package-zip.ts +330 -97
- package/src/qti-package.ts +106 -7
- package/src/response-validation-policy.ts +5 -1
- package/src/response-validation.ts +2 -1
- package/src/serializer-processing-expressions.ts +8 -25
- package/src/server-scoring.ts +1 -0
- package/src/session-builtins.ts +75 -0
- package/src/session.ts +64 -8
- package/src/support-evidence.ts +2 -0
- package/src/support.ts +86 -23
- package/src/test-expression-parser.ts +89 -0
- package/src/test-expression-serializer.ts +71 -0
- package/src/test-expression-validation.ts +139 -0
- package/src/test-expression.ts +66 -0
- package/src/test-model.ts +68 -0
- package/src/test-parser.ts +201 -0
- package/src/test-processing.ts +107 -0
- package/src/test-session.ts +160 -0
- package/src/test-support.ts +31 -0
- package/src/test-validation.ts +190 -0
- package/src/test-xml.ts +37 -0
- package/src/trusted-item-session.ts +13 -2
- package/src/types.ts +36 -4
- package/src/validation-declaration-entries.ts +11 -1
- package/src/validation-item-children.ts +48 -0
- package/src/validation-processing.ts +47 -10
- package/src/validation.ts +14 -0
- package/src/value-guards.ts +6 -0
- package/src/xml-schema-regex-data.ts +404 -0
- package/src/xml-schema-regex-parser.ts +234 -0
- package/src/xml-schema-regex.ts +115 -0
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export {
|
|
|
5
5
|
type QtiAdaptiveTurnResult,
|
|
6
6
|
} from "./adaptive-turn.js";
|
|
7
7
|
export { assertNever } from "./assert-never.js";
|
|
8
|
+
export { diagnosticKey, uniqueDiagnostics } from "./diagnostics.js";
|
|
8
9
|
export {
|
|
9
10
|
createCatalogSupportResolution,
|
|
10
11
|
type QtiCatalogSupportResolution,
|
|
@@ -57,9 +58,13 @@ export {
|
|
|
57
58
|
discoverQtiPackageContentAssets,
|
|
58
59
|
normalizePackagePath,
|
|
59
60
|
QTI_ITEM_RESOURCE_TYPE,
|
|
61
|
+
isQtiItemResource,
|
|
60
62
|
QTI_PACKAGE_MANIFEST_PATH,
|
|
61
63
|
parseQtiPackage,
|
|
64
|
+
parseQtiPackageFromEntries,
|
|
62
65
|
readQtiPackageZipEntries,
|
|
66
|
+
readQtiPackageZipEntriesAsync,
|
|
67
|
+
scopeDiagnosticToPackagePath,
|
|
63
68
|
type QtiAssessmentSectionPackageModel,
|
|
64
69
|
type QtiAssessmentTestItemRef,
|
|
65
70
|
type QtiAssessmentTestPackageModel,
|
|
@@ -71,11 +76,14 @@ export {
|
|
|
71
76
|
type QtiPackageEntry,
|
|
72
77
|
type QtiPackageInflateContext,
|
|
73
78
|
type QtiPackageInflateRaw,
|
|
79
|
+
type QtiPackageInflateRawAsync,
|
|
80
|
+
type QtiPackageZipAsyncOptions,
|
|
74
81
|
type QtiItemSessionControl,
|
|
75
82
|
type QtiPackageItem,
|
|
76
83
|
type QtiPackageItemSource,
|
|
77
84
|
type QtiPackageParseOptions,
|
|
78
85
|
type QtiPackageParseResult,
|
|
86
|
+
type QtiPackageXmlFileSummary,
|
|
79
87
|
type QtiPackageResourceLimits,
|
|
80
88
|
type QtiPackageShape,
|
|
81
89
|
type QtiStandardAlignment,
|
|
@@ -184,6 +192,7 @@ export {
|
|
|
184
192
|
type QtiCustomOperatorRegistry,
|
|
185
193
|
type QtiItemSession,
|
|
186
194
|
type QtiItemSessionOptions,
|
|
195
|
+
type QtiSessionEnvironment,
|
|
187
196
|
} from "./session.js";
|
|
188
197
|
export {
|
|
189
198
|
deprecatedInteractionSupport,
|
|
@@ -383,3 +392,32 @@ export {
|
|
|
383
392
|
formatQtiTextResponse,
|
|
384
393
|
qtiTextResponseString,
|
|
385
394
|
} from "./text-response.js";
|
|
395
|
+
export type {
|
|
396
|
+
QtiExecutableTest,
|
|
397
|
+
QtiTestDefinition,
|
|
398
|
+
QtiTestSection,
|
|
399
|
+
QtiTestItemRef,
|
|
400
|
+
QtiTestBranch,
|
|
401
|
+
QtiTestResult,
|
|
402
|
+
QtiTestOutcomeRule,
|
|
403
|
+
} from "./test-model.js";
|
|
404
|
+
export { validateQtiTest } from "./test-validation.js";
|
|
405
|
+
export { parseQtiTest } from "./test-parser.js";
|
|
406
|
+
export { parseQtiTestExecution } from "./test-parser.js";
|
|
407
|
+
export type { QtiTestExecution } from "./test-parser.js";
|
|
408
|
+
export {
|
|
409
|
+
startQtiTest,
|
|
410
|
+
submitQtiTestAnswer,
|
|
411
|
+
snapshotQtiTestSession,
|
|
412
|
+
restoreQtiTestSession,
|
|
413
|
+
} from "./test-session.js";
|
|
414
|
+
export type { QtiTestSession, QtiTestSessionSnapshot } from "./test-session.js";
|
|
415
|
+
export type { QtiTestSubmission } from "./test-processing.js";
|
|
416
|
+
export type { QtiTestExpression, QtiTestBaseType } from "./test-expression.js";
|
|
417
|
+
export { serializeTestExpression } from "./test-expression-serializer.js";
|
|
418
|
+
export { isQtiIdentifier } from "./qti-identifier.js";
|
|
419
|
+
export { isQtiPackageItemHref } from "./qti-package-paths.js";
|
|
420
|
+
export { testFailure } from "./test-model.js";
|
|
421
|
+
export { testExecutionSupport } from "./test-support.js";
|
|
422
|
+
export { inspectQtiBinaryChoice } from "./binary-choice.js";
|
|
423
|
+
export type { QtiBinaryChoicePolicy } from "./binary-choice.js";
|
|
@@ -65,6 +65,7 @@ export function materializeQtiItemSubmission(
|
|
|
65
65
|
parsedItem: parsedResult.parsed,
|
|
66
66
|
diagnosticPrefix: "itemSubmission",
|
|
67
67
|
allowedUndeclaredResponseIdentifiers: input.allowedUndeclaredResponseIdentifiers,
|
|
68
|
+
sessionEnvironment: input.sessionEnvironment,
|
|
68
69
|
priorState: priorState.state,
|
|
69
70
|
submission: {
|
|
70
71
|
trustedResponses: input.trustedResponses,
|
|
@@ -98,6 +99,7 @@ function materializeAdaptiveSubmission(
|
|
|
98
99
|
parsedItem,
|
|
99
100
|
diagnosticPrefix: "itemSubmission",
|
|
100
101
|
allowedUndeclaredResponseIdentifiers: input.allowedUndeclaredResponseIdentifiers,
|
|
102
|
+
sessionEnvironment: input.sessionEnvironment,
|
|
101
103
|
priorState: input.existingState,
|
|
102
104
|
trustedResponses: input.trustedResponses,
|
|
103
105
|
trustedInteractionStates: input.trustedInteractionStates,
|
package/src/parser-processing.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
coerceValue,
|
|
3
|
+
parseCoords,
|
|
4
|
+
parseInteger,
|
|
5
|
+
parseShape,
|
|
6
|
+
parseXmlBoolean,
|
|
7
|
+
} from "./parser-values.js";
|
|
2
8
|
import { responseConditionsFromRules } from "./processing-rules.js";
|
|
3
9
|
import type {
|
|
4
10
|
QtiLookupOutcomeValue,
|
|
@@ -215,12 +221,8 @@ function parseExpression(node: XmlNode): QtiProcessingExpression | undefined {
|
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
if (node.localName === "qti-is-null") {
|
|
218
|
-
const
|
|
219
|
-
return {
|
|
220
|
-
type: "isNull",
|
|
221
|
-
identifier: variable?.attributes.identifier ?? "",
|
|
222
|
-
source: node.source,
|
|
223
|
-
};
|
|
224
|
+
const expression = parseFirstExpression(node);
|
|
225
|
+
return expression ? { type: "isNull", expression, source: node.source } : undefined;
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
if (node.localName === "qti-map-response") {
|
|
@@ -281,14 +283,8 @@ function parseExpression(node: XmlNode): QtiProcessingExpression | undefined {
|
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
if (node.localName === "qti-random") {
|
|
284
|
-
const
|
|
285
|
-
return {
|
|
286
|
-
type: "random",
|
|
287
|
-
values: childElements(multiple ?? node)
|
|
288
|
-
.map(parseExpression)
|
|
289
|
-
.filter((expression): expression is QtiProcessingExpression => expression !== undefined),
|
|
290
|
-
source: node.source,
|
|
291
|
-
};
|
|
286
|
+
const expression = parseFirstExpression(node);
|
|
287
|
+
return expression ? { type: "random", expression, source: node.source } : undefined;
|
|
292
288
|
}
|
|
293
289
|
|
|
294
290
|
if (node.localName === "qti-multiple") {
|
|
@@ -473,15 +469,16 @@ function parseExpression(node: XmlNode): QtiProcessingExpression | undefined {
|
|
|
473
469
|
const [left, right] = childElements(node)
|
|
474
470
|
.map(parseExpression)
|
|
475
471
|
.filter((expression): expression is QtiProcessingExpression => expression !== undefined);
|
|
476
|
-
if (left && right)
|
|
472
|
+
if (left && right)
|
|
473
|
+
return { type: "equal", left, right, attributes: node.attributes, source: node.source };
|
|
477
474
|
}
|
|
478
475
|
|
|
479
476
|
if (node.localName === "qti-equal-rounded") {
|
|
480
477
|
const [left, right] = childElements(node)
|
|
481
478
|
.map(parseExpression)
|
|
482
479
|
.filter((expression): expression is QtiProcessingExpression => expression !== undefined);
|
|
483
|
-
const roundingMode = node.attributes["rounding-mode"] ?? "";
|
|
484
|
-
const figures =
|
|
480
|
+
const roundingMode = node.attributes["rounding-mode"] ?? "significantFigures";
|
|
481
|
+
const figures = parseInteger(node.attributes.figures) ?? node.attributes.figures ?? "";
|
|
485
482
|
if (left && right) {
|
|
486
483
|
return { type: "equalRounded", left, right, roundingMode, figures, source: node.source };
|
|
487
484
|
}
|
package/src/parser-values.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
QtiBaseType,
|
|
3
3
|
QtiCardinality,
|
|
4
|
-
QtiRecordValue,
|
|
5
4
|
QtiResponseDeclaration,
|
|
6
5
|
QtiScalarValue,
|
|
7
6
|
QtiValue,
|
|
8
7
|
} from "./types.js";
|
|
8
|
+
import { isRecordValue } from "./value-guards.js";
|
|
9
9
|
|
|
10
10
|
const QTI_BASE_TYPES = new Set<string>([
|
|
11
11
|
"identifier",
|
|
@@ -127,7 +127,3 @@ export function parseCoords(value: string | undefined): number[] {
|
|
|
127
127
|
.map((part) => Number(part.trim()))
|
|
128
128
|
.filter((part) => Number.isFinite(part));
|
|
129
129
|
}
|
|
130
|
-
|
|
131
|
-
function isRecordValue(value: QtiValue): value is QtiRecordValue {
|
|
132
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
-
}
|
package/src/parser.ts
CHANGED
|
@@ -178,6 +178,10 @@ function parseAssessmentItem(node: XmlNode, diagnostics: QtiDiagnostic[]): QtiAs
|
|
|
178
178
|
|
|
179
179
|
return {
|
|
180
180
|
identifier,
|
|
181
|
+
sourceChildren: node.children.map((child) => ({
|
|
182
|
+
qtiName: child.localName,
|
|
183
|
+
source: child.source,
|
|
184
|
+
})),
|
|
181
185
|
title: node.attributes.title,
|
|
182
186
|
language: node.attributes["xml:lang"] ?? node.attributes.lang,
|
|
183
187
|
adaptive: parseXmlBoolean(node.attributes.adaptive) === true,
|
|
@@ -213,6 +217,36 @@ function diagnoseProcessingElements(
|
|
|
213
217
|
if (!processingNode) return;
|
|
214
218
|
for (const node of [processingNode, ...descendants(processingNode, () => true)]) {
|
|
215
219
|
if (!node.localName.startsWith("qti-")) continue;
|
|
220
|
+
if (
|
|
221
|
+
node.localName === "qti-pattern-match" &&
|
|
222
|
+
(node.attributes.pattern === undefined || childElements(node).length !== 1)
|
|
223
|
+
) {
|
|
224
|
+
diagnostics.push({
|
|
225
|
+
code: "processing.pattern.required",
|
|
226
|
+
severity: "error",
|
|
227
|
+
message: "qti-pattern-match requires a pattern attribute and exactly one expression.",
|
|
228
|
+
path: node.source.path,
|
|
229
|
+
source: node.source,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (node.localName === "qti-is-null" && childElements(node).length !== 1) {
|
|
233
|
+
diagnostics.push({
|
|
234
|
+
code: "processing.isNull.arity",
|
|
235
|
+
severity: "error",
|
|
236
|
+
message: "qti-is-null requires exactly one expression.",
|
|
237
|
+
path: node.source.path,
|
|
238
|
+
source: node.source,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (node.localName === "qti-random" && childElements(node).length !== 1) {
|
|
242
|
+
diagnostics.push({
|
|
243
|
+
code: "processing.random.arity",
|
|
244
|
+
severity: "error",
|
|
245
|
+
message: "qti-random requires exactly one container expression.",
|
|
246
|
+
path: node.source.path,
|
|
247
|
+
source: node.source,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
216
250
|
if (
|
|
217
251
|
processingNode.localName === "qti-response-processing" &&
|
|
218
252
|
responseProcessingForbiddenNames.has(node.localName)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { parseFiniteNumber, parseXmlBoolean } from "./parser-values.js";
|
|
2
|
+
import type { QtiDiagnostic, QtiProcessingExpression, QtiValue } from "./types.js";
|
|
3
|
+
|
|
4
|
+
type EqualExpression = Extract<QtiProcessingExpression, { type: "equal" }>;
|
|
5
|
+
type EqualityTolerance =
|
|
6
|
+
| { mode: "exact" }
|
|
7
|
+
| {
|
|
8
|
+
mode: "absolute" | "relative";
|
|
9
|
+
lower: string;
|
|
10
|
+
upper: string;
|
|
11
|
+
includeLower: boolean;
|
|
12
|
+
includeUpper: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function parseTolerance(
|
|
16
|
+
attributes: Record<string, string> = {},
|
|
17
|
+
): { ok: true; value: EqualityTolerance } | { ok: false; message: string } {
|
|
18
|
+
const mode = attributes["tolerance-mode"] ?? "exact";
|
|
19
|
+
if (mode !== "exact" && mode !== "absolute" && mode !== "relative") {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
message: "qti-equal requires exact, absolute, or relative tolerance-mode.",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const lowerAttribute = attributes["include-lower-bound"];
|
|
26
|
+
const upperAttribute = attributes["include-upper-bound"];
|
|
27
|
+
const includeLower = lowerAttribute === undefined ? true : parseXmlBoolean(lowerAttribute);
|
|
28
|
+
const includeUpper = upperAttribute === undefined ? true : parseXmlBoolean(upperAttribute);
|
|
29
|
+
if (includeLower === undefined || includeUpper === undefined) {
|
|
30
|
+
return { ok: false, message: "qti-equal boundary flags must be boolean." };
|
|
31
|
+
}
|
|
32
|
+
if (mode === "exact") return { ok: true, value: { mode } };
|
|
33
|
+
const tolerances = attributes.tolerance?.trim().split(/\s+/) ?? [];
|
|
34
|
+
const [lower, upper = lower] = tolerances;
|
|
35
|
+
if (!lower || !upper || tolerances.length > 2) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
message: "qti-equal requires one or two tolerance values in absolute or relative mode.",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return { ok: true, value: { mode, lower, upper, includeLower, includeUpper } };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Validate tolerance attributes and numeric literal or declared-variable operands. */
|
|
45
|
+
export function validateEqualTolerance(
|
|
46
|
+
expression: EqualExpression,
|
|
47
|
+
variables: ReadonlySet<string>,
|
|
48
|
+
diagnostics: QtiDiagnostic[],
|
|
49
|
+
): void {
|
|
50
|
+
const parsed = parseTolerance(expression.attributes);
|
|
51
|
+
const messages: string[] = [];
|
|
52
|
+
if (!parsed.ok) messages.push(parsed.message);
|
|
53
|
+
else if (parsed.value.mode !== "exact") {
|
|
54
|
+
for (const token of [parsed.value.lower, parsed.value.upper]) {
|
|
55
|
+
const number = parseFiniteNumber(token);
|
|
56
|
+
if (number === undefined ? !variables.has(token) : number < 0) {
|
|
57
|
+
messages.push(
|
|
58
|
+
"qti-equal tolerance must be nonnegative numeric values or declared variable references.",
|
|
59
|
+
);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const message of messages) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
code: "processing.equal.tolerance",
|
|
67
|
+
severity: "error",
|
|
68
|
+
message,
|
|
69
|
+
source: expression.source,
|
|
70
|
+
path: expression.source?.path,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Compare single numeric operands using QTI absolute/relative tolerance and boundary rules. */
|
|
76
|
+
export function equalWithTolerance(
|
|
77
|
+
expression: EqualExpression,
|
|
78
|
+
left: QtiValue,
|
|
79
|
+
right: QtiValue,
|
|
80
|
+
resolve: (identifier: string) => QtiValue,
|
|
81
|
+
): boolean | null {
|
|
82
|
+
if (typeof left !== "number" || typeof right !== "number") return null;
|
|
83
|
+
const parsed = parseTolerance(expression.attributes);
|
|
84
|
+
if (!parsed.ok) return null;
|
|
85
|
+
const tolerance = parsed.value;
|
|
86
|
+
if (tolerance.mode === "exact") return left === right;
|
|
87
|
+
const lower = parseFiniteNumber(tolerance.lower) ?? resolve(tolerance.lower);
|
|
88
|
+
const upper = parseFiniteNumber(tolerance.upper) ?? resolve(tolerance.upper);
|
|
89
|
+
if (
|
|
90
|
+
typeof lower !== "number" ||
|
|
91
|
+
typeof upper !== "number" ||
|
|
92
|
+
!Number.isFinite(lower) ||
|
|
93
|
+
!Number.isFinite(upper) ||
|
|
94
|
+
lower < 0 ||
|
|
95
|
+
upper < 0
|
|
96
|
+
)
|
|
97
|
+
return null;
|
|
98
|
+
const minimum = tolerance.mode === "absolute" ? left - lower : left * (1 - lower / 100);
|
|
99
|
+
const maximum = tolerance.mode === "absolute" ? left + upper : left * (1 + upper / 100);
|
|
100
|
+
return (
|
|
101
|
+
(tolerance.includeLower ? right >= minimum : right > minimum) &&
|
|
102
|
+
(tolerance.includeUpper ? right <= maximum : right < maximum)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { QtiProcessingExpression, QtiValue } from "./types.js";
|
|
2
|
+
import { equalWithTolerance } from "./processing-equality.js";
|
|
2
3
|
import { assertNever } from "./assert-never.js";
|
|
3
4
|
import type { EvaluationContext } from "./processing-evaluator.js";
|
|
4
5
|
import {
|
|
@@ -47,22 +48,21 @@ export function evaluateComparisonExpression(
|
|
|
47
48
|
case "equal": {
|
|
48
49
|
const left = context.evaluate(expression.left);
|
|
49
50
|
const right = context.evaluate(expression.right);
|
|
50
|
-
return left
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
left,
|
|
54
|
-
right,
|
|
55
|
-
false,
|
|
56
|
-
expressionBaseType(expression.left, context.document) ??
|
|
57
|
-
expressionBaseType(expression.right, context.document),
|
|
58
|
-
);
|
|
51
|
+
return equalWithTolerance(expression, left, right, (identifier) =>
|
|
52
|
+
context.evaluate({ type: "variable", identifier }),
|
|
53
|
+
);
|
|
59
54
|
}
|
|
60
55
|
case "equalRounded": {
|
|
56
|
+
const figures =
|
|
57
|
+
typeof expression.figures === "number"
|
|
58
|
+
? expression.figures
|
|
59
|
+
: context.evaluate({ type: "variable", identifier: expression.figures });
|
|
61
60
|
const validRounding =
|
|
62
|
-
|
|
61
|
+
typeof figures === "number" &&
|
|
62
|
+
Number.isInteger(figures) &&
|
|
63
63
|
(expression.roundingMode === "decimalPlaces"
|
|
64
|
-
?
|
|
65
|
-
: expression.roundingMode === "significantFigures" &&
|
|
64
|
+
? figures >= 0
|
|
65
|
+
: expression.roundingMode === "significantFigures" && figures > 0);
|
|
66
66
|
if (!validRounding) return null;
|
|
67
67
|
const left = context.evaluate(expression.left);
|
|
68
68
|
const right = context.evaluate(expression.right);
|
|
@@ -70,8 +70,8 @@ export function evaluateComparisonExpression(
|
|
|
70
70
|
const leftNumber = numericValueOrNull(left);
|
|
71
71
|
const rightNumber = numericValueOrNull(right);
|
|
72
72
|
if (leftNumber === null || rightNumber === null) return null;
|
|
73
|
-
const roundedLeft = roundWithMode(leftNumber, expression.roundingMode,
|
|
74
|
-
const roundedRight = roundWithMode(rightNumber, expression.roundingMode,
|
|
73
|
+
const roundedLeft = roundWithMode(leftNumber, expression.roundingMode, figures);
|
|
74
|
+
const roundedRight = roundWithMode(rightNumber, expression.roundingMode, figures);
|
|
75
75
|
return roundedLeft === null || roundedRight === null ? null : roundedLeft === roundedRight;
|
|
76
76
|
}
|
|
77
77
|
case "numericCompare": {
|
|
@@ -123,6 +123,11 @@ export function evaluateComparisonExpression(
|
|
|
123
123
|
const baseType =
|
|
124
124
|
expressionBaseType(expression.collection, context.document) ??
|
|
125
125
|
expressionBaseType(expression.values, context.document);
|
|
126
|
+
if (expressionIsOrdered(expression.collection, context.document)) {
|
|
127
|
+
return collection.some((_, offset) =>
|
|
128
|
+
valuesEqual(collection.slice(offset, offset + values.length), values, true, baseType),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
126
131
|
return containsValues(collection, values, baseType);
|
|
127
132
|
}
|
|
128
133
|
default:
|
|
@@ -9,12 +9,8 @@ import {
|
|
|
9
9
|
roundToSignificantFigures,
|
|
10
10
|
statsOperatorValue,
|
|
11
11
|
} from "./processing-operators.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
numericValue,
|
|
15
|
-
numericValueOrNull,
|
|
16
|
-
valueContainer,
|
|
17
|
-
} from "./processing-values.js";
|
|
12
|
+
import { numericValue, numericValueOrNull, valueContainer } from "./processing-values.js";
|
|
13
|
+
import { isRecordValue } from "./value-guards.js";
|
|
18
14
|
|
|
19
15
|
type NumericExpression = Extract<
|
|
20
16
|
QtiProcessingExpression,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { QtiProcessingExpression, QtiValue } from "./types.js";
|
|
2
2
|
import { assertNever } from "./assert-never.js";
|
|
3
3
|
import type { EvaluationContext } from "./processing-evaluator.js";
|
|
4
|
-
import {
|
|
5
|
-
import { isRecordValue
|
|
6
|
-
import {
|
|
4
|
+
import { stringMatch } from "./processing-values.js";
|
|
5
|
+
import { isRecordValue } from "./value-guards.js";
|
|
6
|
+
import { matchXmlSchemaRegex, patternVariableIdentifier } from "./xml-schema-regex.js";
|
|
7
7
|
|
|
8
8
|
type StringExpression = Extract<
|
|
9
9
|
QtiProcessingExpression,
|
|
@@ -32,23 +32,30 @@ export function evaluateStringExpression(
|
|
|
32
32
|
case "patternMatch": {
|
|
33
33
|
const value = context.evaluate(expression.expression);
|
|
34
34
|
if (value === null) return null;
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expression.pattern
|
|
39
|
-
context.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
).test(qtiValueToString(value));
|
|
49
|
-
} catch {
|
|
35
|
+
const reference = patternVariableIdentifier(expression.pattern);
|
|
36
|
+
const pattern =
|
|
37
|
+
reference === undefined
|
|
38
|
+
? expression.pattern
|
|
39
|
+
: context.evaluate({ type: "variable", identifier: reference });
|
|
40
|
+
if (pattern === null) return null;
|
|
41
|
+
if (typeof value !== "string" || typeof pattern !== "string") {
|
|
42
|
+
context.diagnostics.push({
|
|
43
|
+
code: "processing.pattern.type",
|
|
44
|
+
severity: "error",
|
|
45
|
+
message: "Pattern matching requires a single string pattern and operand.",
|
|
46
|
+
source: expression.source,
|
|
47
|
+
});
|
|
50
48
|
return null;
|
|
51
49
|
}
|
|
50
|
+
const result = matchXmlSchemaRegex(pattern, value);
|
|
51
|
+
if (result.ok) return result.matches;
|
|
52
|
+
context.diagnostics.push({
|
|
53
|
+
code: `processing.pattern.${result.problem.code}`,
|
|
54
|
+
severity: "error",
|
|
55
|
+
message: result.problem.message,
|
|
56
|
+
source: expression.source,
|
|
57
|
+
});
|
|
58
|
+
return null;
|
|
52
59
|
}
|
|
53
60
|
case "fieldValue": {
|
|
54
61
|
const value = context.evaluate(expression.expression);
|
|
@@ -29,7 +29,7 @@ export function evaluateVariableExpression(
|
|
|
29
29
|
): QtiValue {
|
|
30
30
|
switch (expression.type) {
|
|
31
31
|
case "isNull":
|
|
32
|
-
return isNullResponse(context.
|
|
32
|
+
return isNullResponse(context.evaluate(expression.expression));
|
|
33
33
|
case "matchCorrect": {
|
|
34
34
|
const declaration = getResponseDeclaration(context.document, expression.correctIdentifier);
|
|
35
35
|
return declaration
|
|
@@ -63,8 +63,12 @@ export function evaluateVariableExpression(
|
|
|
63
63
|
case "correct":
|
|
64
64
|
return context.correctResponses[expression.identifier] ?? null;
|
|
65
65
|
case "default":
|
|
66
|
-
return
|
|
67
|
-
|
|
66
|
+
return Object.hasOwn(context.defaultValues, expression.identifier)
|
|
67
|
+
? (context.defaultValues[expression.identifier] ?? null)
|
|
68
|
+
: defaultValueForIdentifier(context.document, expression.identifier);
|
|
69
|
+
case "variable": {
|
|
70
|
+
const builtIn = context.builtInVariable(expression.identifier);
|
|
71
|
+
if (builtIn !== undefined) return builtIn;
|
|
68
72
|
return (
|
|
69
73
|
resolveVariableValue(
|
|
70
74
|
context.document,
|
|
@@ -76,6 +80,7 @@ export function evaluateVariableExpression(
|
|
|
76
80
|
context.undeclaredResponseValue(expression.identifier) ??
|
|
77
81
|
null
|
|
78
82
|
);
|
|
83
|
+
}
|
|
79
84
|
default:
|
|
80
85
|
return assertNever(expression);
|
|
81
86
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QtiDocument, QtiProcessingExpression, QtiValue } from "./types.js";
|
|
1
|
+
import type { QtiDiagnostic, QtiDocument, QtiProcessingExpression, QtiValue } from "./types.js";
|
|
2
2
|
import { assertNever } from "./assert-never.js";
|
|
3
3
|
import type { QtiCustomOperatorRegistry } from "./custom-operators.js";
|
|
4
4
|
import { evaluateBooleanExpression } from "./processing-evaluator-boolean.js";
|
|
@@ -12,7 +12,8 @@ import { evaluateGeometryExpression } from "./processing-evaluator-geometry.js";
|
|
|
12
12
|
import { evaluateNumericExpression } from "./processing-evaluator-numeric.js";
|
|
13
13
|
import { evaluateStringExpression } from "./processing-evaluator-string.js";
|
|
14
14
|
import { evaluateVariableExpression } from "./processing-evaluator-variable.js";
|
|
15
|
-
import {
|
|
15
|
+
import { numericValueOrNull } from "./processing-values.js";
|
|
16
|
+
import { isRecordValue } from "./value-guards.js";
|
|
16
17
|
|
|
17
18
|
export interface EvaluationContext {
|
|
18
19
|
document: QtiDocument;
|
|
@@ -20,6 +21,9 @@ export interface EvaluationContext {
|
|
|
20
21
|
outcomes: Record<string, QtiValue>;
|
|
21
22
|
templateValues: Record<string, QtiValue>;
|
|
22
23
|
correctResponses: Record<string, QtiValue>;
|
|
24
|
+
defaultValues: Record<string, QtiValue>;
|
|
25
|
+
diagnostics: QtiDiagnostic[];
|
|
26
|
+
builtInVariable(identifier: string): QtiValue | undefined;
|
|
23
27
|
allowedUndeclaredResponseIdentifiers: ReadonlySet<string>;
|
|
24
28
|
random: () => number;
|
|
25
29
|
customOperators: QtiCustomOperatorRegistry;
|
|
@@ -30,6 +34,7 @@ export interface EvaluationContext {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export interface EvaluationOptions {
|
|
37
|
+
builtInVariable?: ((identifier: string) => QtiValue | undefined) | undefined;
|
|
33
38
|
allowedUndeclaredResponseIdentifiers?: ReadonlySet<string> | readonly string[] | undefined;
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -53,6 +58,14 @@ export function createEvaluationContext(
|
|
|
53
58
|
outcomes,
|
|
54
59
|
templateValues,
|
|
55
60
|
correctResponses,
|
|
61
|
+
defaultValues: {},
|
|
62
|
+
diagnostics: [],
|
|
63
|
+
builtInVariable:
|
|
64
|
+
options.builtInVariable ??
|
|
65
|
+
((identifier) =>
|
|
66
|
+
identifier === "completionStatus"
|
|
67
|
+
? (outcomes.completionStatus ?? "not_attempted")
|
|
68
|
+
: undefined),
|
|
56
69
|
allowedUndeclaredResponseIdentifiers,
|
|
57
70
|
random,
|
|
58
71
|
customOperators,
|
|
@@ -105,11 +118,11 @@ export function evaluateProcessingExpression(
|
|
|
105
118
|
}
|
|
106
119
|
case "randomFloat":
|
|
107
120
|
return expression.min + context.random() * (expression.max - expression.min);
|
|
108
|
-
case "random":
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
case "random": {
|
|
122
|
+
const values = context.evaluate(expression.expression);
|
|
123
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
124
|
+
return values[Math.floor(context.random() * values.length)] ?? null;
|
|
125
|
+
}
|
|
113
126
|
case "isNull":
|
|
114
127
|
case "matchCorrect":
|
|
115
128
|
case "mapResponse":
|
|
@@ -3,8 +3,6 @@ import { assertNever } from "./assert-never.js";
|
|
|
3
3
|
|
|
4
4
|
export function expressionChildren(expression: QtiProcessingExpression): QtiProcessingExpression[] {
|
|
5
5
|
switch (expression.type) {
|
|
6
|
-
case "random":
|
|
7
|
-
return expression.values;
|
|
8
6
|
case "multiple":
|
|
9
7
|
case "ordered":
|
|
10
8
|
case "sum":
|
|
@@ -33,6 +31,8 @@ export function expressionChildren(expression: QtiProcessingExpression): QtiProc
|
|
|
33
31
|
case "stringMatch":
|
|
34
32
|
case "substring":
|
|
35
33
|
return [expression.left, expression.right];
|
|
34
|
+
case "isNull":
|
|
35
|
+
case "random":
|
|
36
36
|
case "not":
|
|
37
37
|
case "round":
|
|
38
38
|
case "roundTo":
|
|
@@ -54,7 +54,6 @@ export function expressionChildren(expression: QtiProcessingExpression): QtiProc
|
|
|
54
54
|
case "baseValue":
|
|
55
55
|
case "correct":
|
|
56
56
|
case "default":
|
|
57
|
-
case "isNull":
|
|
58
57
|
case "mapResponse":
|
|
59
58
|
case "mapResponsePoint":
|
|
60
59
|
case "matchCorrect":
|
|
@@ -17,6 +17,8 @@ export const identifierExpressionTags = {
|
|
|
17
17
|
export type IdentifierExpressionType = keyof typeof identifierExpressionTags;
|
|
18
18
|
|
|
19
19
|
export const unaryExpressionTags = {
|
|
20
|
+
isNull: "qti-is-null",
|
|
21
|
+
random: "qti-random",
|
|
20
22
|
containerSize: "qti-container-size",
|
|
21
23
|
round: "qti-round",
|
|
22
24
|
truncate: "qti-truncate",
|
|
@@ -47,7 +49,6 @@ export const leftRightExpressionTags = {
|
|
|
47
49
|
power: "qti-power",
|
|
48
50
|
integerDivide: "qti-integer-divide",
|
|
49
51
|
integerModulus: "qti-integer-modulus",
|
|
50
|
-
equal: "qti-equal",
|
|
51
52
|
} as const;
|
|
52
53
|
|
|
53
54
|
export type LeftRightExpressionType = keyof typeof leftRightExpressionTags;
|