diff --git a/.eslint/configuration/_index.mjs b/.eslint/configuration/_index.mjs new file mode 100644 index 0000000..b0e73ae --- /dev/null +++ b/.eslint/configuration/_index.mjs @@ -0,0 +1,19 @@ +import { configuration as disabled_configuration } from "./disabled/config.mjs"; +import { configuration as default_configuration } from "./global/default.mjs"; +import { configuration as ignores_configuration } from "./global/ignores.mjs"; +import { configuration as strict_configuration } from "./strict/config.mjs"; +import { configuration as style_configuration } from "./style/config.mjs"; +import { configuration as test_configuration } from "./test/config.mjs"; +import { configurations as file_specific_configurations } from "./file-specific/config.mjs"; + +const CONFIGURATIONS = [ + disabled_configuration, + default_configuration, + ignores_configuration, + strict_configuration, + style_configuration, + test_configuration, + ...file_specific_configurations, +]; + +export { CONFIGURATIONS as configurations }; diff --git a/.eslint/configuration/disabled/config.mjs b/.eslint/configuration/disabled/config.mjs new file mode 100644 index 0000000..de7428d --- /dev/null +++ b/.eslint/configuration/disabled/config.mjs @@ -0,0 +1,12 @@ +import { rules } from "./rules.mjs"; + +const CONFIGURATION = { + name: "disabled", + files: [ + ".disabled", + ], + ignores: [], + rules: rules, +}; + +export { CONFIGURATION as configuration }; diff --git a/.eslint/configuration/disabled/rules.mjs b/.eslint/configuration/disabled/rules.mjs new file mode 100644 index 0000000..3e33ea9 --- /dev/null +++ b/.eslint/configuration/disabled/rules.mjs @@ -0,0 +1,258 @@ +/* + * List disabled rules to help with configuration inspection + * It's easier to see the new rules in the unused rules list +**/ +const RULES = { + // Buggy rule + "@style/indent": "off", + // Buggy rule + "@style/indent-binary-ops": "off", + // We don't do JSX + "@style/jsx-child-element-spacing": "off", + // We don't do JSX + "@style/jsx-closing-bracket-location": "off", + // We don't do JSX + "@style/jsx-closing-tag-location": "off", + // We don't do JSX + "@style/jsx-curly-brace-presence": "off", + // We don't do JSX + "@style/jsx-curly-newline": "off", + // We don't do JSX + "@style/jsx-curly-spacing": "off", + // We don't do JSX + "@style/jsx-equals-spacing": "off", + // We don't do JSX + "@style/jsx-first-prop-new-line": "off", + // We don't do JSX + "@style/jsx-function-call-newline": "off", + // We don't do JSX + "@style/jsx-indent-props": "off", + // We don't do JSX + "@style/jsx-max-props-per-line": "off", + // We don't do JSX + "@style/jsx-newline": "off", + // We don't do JSX + "@style/jsx-one-expression-per-line": "off", + // We don't do JSX + "@style/jsx-pascal-case": "off", + // We don't do JSX + "@style/jsx-props-no-multi-spaces": "off", + // We don't do JSX + "@style/jsx-self-closing-comp": "off", + // We don't do JSX + "@style/jsx-sort-props": "off", + // We don't do JSX + "@style/jsx-tag-spacing": "off", + // We don't do JSX + "@style/jsx-wrap-multilines": "off", + // We don't do JSX + "@style/wrap-regex": "off", + // Free choice + "@style/lines-around-comment": "off", + // Case by case + "@style/multiline-comment-style": "off", + // The @ts/typedef rule make this rule obsolete + "@style/no-confusing-arrow": "off", + // Shouldn't be an issue with TypeScript + "@style/no-mixed-operators": "off", + // The curly rule make this rule obsolete + "@style/nonblock-statement-body-position": "off", + // The one-var rule make this rule obsolete + "@style/one-var-declaration-per-line": "off", + // The @style/comma-spacing rule make this rule obsolete + "@style/type-generic-spacing": "off", + // TypeScript prevent such confusion + "@style/wrap-regexp": "off", + // Handled by TypeScript option noImplicitReturns + "@ts/consistent-return": "off", + // Not formally defined + "@ts/naming-convention": "off", + // Handled by TypeScript + "@ts/no-dupe-class-members": "off", + // Incompatible with the @ts/typedef rule + "@ts/no-inferrable-types": "off", + // Handled by TypeScript option noImplicitThis + "@ts/no-invalid-this": "off", + // Mixed enums are good for holding configuration values + "@ts/no-mixed-enums": "off", + // Handled by TypeScript + "@ts/no-redeclare": "off", + // It was not necessary so far + "@ts/no-restricted-imports": "off", + // Not needed so far + "@ts/no-restricted-types": "off", + // The @ts/parameter-properties rule make this rule obsolete + "@ts/no-unnecessary-parameter-property-assignment": "off", + // Explicit qualifiers improve readability + "@ts/no-unnecessary-qualifier": "off", + // Being explicit is never bad + "@ts/no-unnecessary-type-arguments": "off", + // Being explicit is never bad + "@ts/no-unnecessary-type-parameters": "off", + // Enum comparison is necessary for comparing values with magic numbers or string constants + "@ts/no-unsafe-enum-comparison": "off", + // Type assertions are forbidden + "@ts/no-unsafe-type-assertion": "off", + // Traditional type assertion is more explicit + "@ts/non-nullable-type-assertion-style": "off", + // Destructuring is not always the best choice + "@ts/prefer-destructuring": "off", + // The @ts/no-namespace rule make this rule obsolete + "@ts/prefer-namespace-keyword": "off", + // Conflict with the no-param-reassign rule props option + "@ts/prefer-readonly-parameter-types": "off", + // The type is often implied by the callable return type + "@ts/prefer-reduce-type-parameter": "off", + // Distinct signatures can improve readability + "@ts/unified-signatures": "off", + // The no-var rule make this rule obsolete + "block-scoped-var": "off", + // Conflict with some dependencies and APIs + "camelcase": "off", + // Some comments may need to begin with a lowercase + "capitalized-comments": "off", + // Redefined as @ts/class-methods-use-this + "class-methods-use-this": "off", + // Redefined as @ts/consistent-return + "consistent-return": "off", + // The prefer-arrow-callback rule make this rule obsolete + "consistent-this": "off", + // Handled by TypeScript + "constructor-super": "off", + // Conflict with @ts/switch-exhaustiveness-check option allowDefaultCaseForExhaustiveSwitch + "default-case": "off", + // Redefined as @ts/default-param-last + "default-param-last": "off", + // Redefined as @ts/dot-notation + "dot-notation": "off", + // The prefer-arrow-callback rule make this rule obsolete + "func-name-matching": "off", + // Handled by TypeScript + "getter-return": "off", + // Handled by TypeScript + "guard-for-in": "off", + // The rules id-denylist and id-length make this rule obsolete + "id-match": "off", + // Redefined as @ts/init-declarations + "init-declarations": "off", + // Redefined as @ts/max-params + "max-params": "off", + // All variables follow the same naming convention + "new-cap": "off", + // The rule is only for a browser environment + "no-alert": "off", + // Redefined as @ts/no-array-constructor + "no-array-constructor": "off", + // It is not always desirable to perform multiple asynchronous operations in parallel + "no-await-in-loop": "off", + // Bitwise operations are useful in some cases + "no-bitwise": "off", + // Handled by TypeScript + "no-const-assign": "off", + // Early interruption improve readability + "no-continue": "off", + // Using ASCII or unicode code points in RegExp patterns is not bad + "no-control-regex": "off", + // TypeScript prevent such confusion + "no-div-regex": "off", + // Handled by TypeScript + "no-dupe-args": "off", + // Redefined as @ts/no-dupe-class-members + "no-dupe-class-members": "off", + // Handled by TypeScript + "no-dupe-keys": "off", + // Redefined as @ts/no-empty-function + "no-empty-function": "off", + // The eqeqeq rule make this rule obsolete + "no-eq-null": "off", + // The no-labels rule make this rule obsolete + "no-extra-label": "off", + // Handled by TypeScript + "no-func-assign": "off", + // Doesn't apply to modules + "no-implicit-globals": "off", + // Redefined as @ts/no-implied-eval + "no-implied-eval": "off", + // Handled by TypeScript + "no-import-assign": "off", + // Inline comments may improve readability in some cases + "no-inline-comments": "off", + // Not an issue in modules and strict mode + "no-inner-declarations": "off", + // Redefined as @ts/no-invalid-this + "no-invalid-this": "off", + // The no-labels rule make this rule obsolete + "no-label-var": "off", + // Not an issue in modules and strict mode + "no-lone-blocks": "off", + // Redefined as @ts/no-loop-func + "no-loop-func": "off", + // Redefined as @ts/no-magic-numbers + "no-magic-numbers": "off", + // Handled by TypeScript + "no-obj-calls": "off", + // Increment and decrement operators are good + "no-plusplus": "off", + // Redefined as @ts/no-redeclare + "no-redeclare": "off", + // The rules id-denylist and id-length are enough so far + "no-restricted-exports": "off", + // Not needed so far + "no-restricted-globals": "off", + // Redefined as @ts/no-restricted-imports + "no-restricted-imports": "off", + // Handled by TypeScript + "no-setter-return": "off", + // Redefined as @ts/no-shadow + "no-shadow": "off", + // Ternary can be useful in some cases + "no-ternary": "off", + // Handled by TypeScript + "no-this-before-super": "off", + // Redefined as @ts/only-throw-error + "no-throw-literal": "off", + // Handled by TypeScript + "no-undef": "off", + // Incompatible with @ts/init-declarations + "no-undef-init": "off", + // The no-shadow-restricted-names rule make this rule obsolete + "no-undefined": "off", + // Handled by TypeScript + "no-unreachable": "off", + // Handled by TypeScript + "no-unsafe-negation": "off", + // Redefined as @ts/no-unused-expressions + "no-unused-expressions": "off", + // The no-labels rule make this rule obsolete + "no-unused-labels": "off", + // Redefined as @ts/no-unused-vars + "no-unused-vars": "off", + // Redefined as @ts/no-use-before-define + "no-use-before-define": "off", + // Incompatible with @ts/init-declarations + "no-useless-assignment": "off", + // Redefined as @ts/no-useless-constructor + "no-useless-constructor": "off", + // Redefined as @ts/prefer-destructuring + "prefer-destructuring": "off", + // Math.pow() can improve readability + "prefer-exponentiation-operator": "off", + // Redefined as @ts/prefer-promise-reject-errors + "prefer-promise-reject-errors": "off", + // Redefined as @ts/require-await + "require-await": "off", + // Simple RegExp do not need the unicode flag + "require-unicode-regexp": "off", + // Grouping keys logically improve readability + "sort-keys": "off", + // The one-var rule make this rule obsolete + "sort-vars": "off", + // Handled by TypeScript + "use-isnan": "off", + // Handled by TypeScript + "valid-typeof": "off", + // The no-var rule make this rule obsolete + "vars-on-top": "off", +}; + +export { RULES as rules }; diff --git a/.eslint/configuration/file-specific/config.mjs b/.eslint/configuration/file-specific/config.mjs new file mode 100644 index 0000000..70ff8d2 --- /dev/null +++ b/.eslint/configuration/file-specific/config.mjs @@ -0,0 +1,14 @@ +import { rules } from "./rules.mjs"; + +const CONFIGURATIONS = [ + { + name: "ts-consistent-type-exports", + files: [ + "**/_index.mts", + ], + ignores: [], + rules: rules, + } +]; + +export { CONFIGURATIONS as configurations }; diff --git a/.eslint/configuration/file-specific/rules.mjs b/.eslint/configuration/file-specific/rules.mjs new file mode 100644 index 0000000..272bb0b --- /dev/null +++ b/.eslint/configuration/file-specific/rules.mjs @@ -0,0 +1,10 @@ +/* + * List disabled rules to help with configuration inspection + * It's easier to see the new rules in the unused rules list +**/ +const RULES = { + // Currently buggy for files purely exporting types. + "@ts/consistent-type-exports": "off", +}; + +export { RULES as rules }; diff --git a/.eslint/configuration/global/default.mjs b/.eslint/configuration/global/default.mjs new file mode 100644 index 0000000..a75e70a --- /dev/null +++ b/.eslint/configuration/global/default.mjs @@ -0,0 +1,27 @@ +import typescript_eslint from "typescript-eslint"; +import stylistic from "@stylistic/eslint-plugin"; + +/* Default configuration with plugins */ +const CONFIGURATION = { + name: "default", + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: {}, + parser: typescript_eslint.parser, + parserOptions: { + project: true, + }, + }, + linterOptions: { + noInlineConfig: false, + reportUnusedDisableDirectives: "warn", + }, + plugins: { + "@ts": typescript_eslint.plugin, + "@style": stylistic, + }, + settings: {}, +}; + +export { CONFIGURATION as configuration }; diff --git a/.eslint/configuration/global/ignores.mjs b/.eslint/configuration/global/ignores.mjs new file mode 100644 index 0000000..4d0542a --- /dev/null +++ b/.eslint/configuration/global/ignores.mjs @@ -0,0 +1,19 @@ +/* Ignored files for all configurations */ +const CONFIGURATION = { + ignores: [ + "**/*.js", + "**/*.cjs", + "**/*.mjs", + "**/*.d.ts", + "**/*.d.cts", + "**/*.d.mts", + "build", + "reports", + "coverage", + "tmp", + ".stryker-tmp", + "**/test.mts" + ], +}; + +export { CONFIGURATION as configuration }; diff --git a/.eslint/configuration/strict/config.mjs b/.eslint/configuration/strict/config.mjs new file mode 100644 index 0000000..68eb648 --- /dev/null +++ b/.eslint/configuration/strict/config.mjs @@ -0,0 +1,12 @@ +import { rules } from "./rules.mjs"; + +const CONFIGURATION = { + name: "strict", + files: [ + "**/*.mts", + ], + ignores: [], + rules: rules, +}; + +export { CONFIGURATION as configuration }; diff --git a/.eslint/configuration/strict/rules.mjs b/.eslint/configuration/strict/rules.mjs new file mode 100644 index 0000000..d6ab873 --- /dev/null +++ b/.eslint/configuration/strict/rules.mjs @@ -0,0 +1,871 @@ +const RULES = { + "@ts/array-type": [ + "error", + { + "default": "generic", + "readonly": "generic" + } + ], + "@ts/await-thenable": "error", + "@ts/ban-ts-comment": [ + "error", + { + "ts-expect-error": "allow-with-description", + "ts-ignore": false, + "ts-nocheck": false, + "ts-check": false, + "minimumDescriptionLength": 10 + } + ], + "@ts/ban-tslint-comment": "error", + "@ts/class-methods-use-this": [ + "error", + { + "enforceForClassFields": false, + "ignoreClassesThatImplementAnInterface": true, + "exceptMethods": [ + "execute" + ], + } + ], + "@ts/consistent-generic-constructors": [ + "error", + "type-annotation" + ], + "@ts/consistent-type-assertions": [ + "error", + { + "assertionStyle": "as", + "objectLiteralTypeAssertions": "never" + } + ], + "@ts/consistent-type-exports": [ + "error", + { + "fixMixedExportsWithInlineTypeSpecifier": true + } + ], + "@ts/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "fixStyle": "inline-type-imports", + "disallowTypeAnnotations": true + } + ], + "@ts/default-param-last": "error", + "@ts/explicit-function-return-type": [ + "error", + { + "allowExpressions": false, + "allowTypedFunctionExpressions": false, + "allowHigherOrderFunctions": true, + "allowDirectConstAssertionInArrowFunctions": false, + "allowConciseArrowFunctionExpressionsStartingWithVoid": false, + "allowFunctionsWithoutTypeParameters": false, + "allowedNames": [], + "allowIIFEs": false + } + ], + "@ts/explicit-member-accessibility": [ + "error", + { + "accessibility": "explicit", + "ignoredMethodNames": [], + "overrides": { + "accessors": "explicit", + "constructors": "explicit", + "methods": "explicit", + "parameterProperties": "explicit", + "properties": "explicit" + } + } + ], + "@ts/explicit-module-boundary-types": [ + "error", + { + "allowArgumentsExplicitlyTypedAsAny": false, + "allowDirectConstAssertionInArrowFunctions": false, + "allowHigherOrderFunctions": false, + "allowTypedFunctionExpressions": false, + "allowedNames": [] + } + ], + "@ts/init-declarations": [ + "error", + "always" + ], + "@ts/max-params": [ + "error", + { + "max": 3 + } + ], + "@ts/no-array-constructor": "error", + "@ts/no-array-delete": "error", + "@ts/no-base-to-string": "error", + "@ts/no-confusing-non-null-assertion": "error", + "@ts/no-confusing-void-expression": [ + "error", + { + "ignoreArrowShorthand": false, + "ignoreVoidOperator": false + } + ], + "@ts/no-deprecated": "error", + "@ts/no-duplicate-enum-values": "error", + "@ts/no-duplicate-type-constituents": [ + "error", + { + "ignoreIntersections": false, + "ignoreUnions": false + } + ], + "@ts/no-dynamic-delete": "error", + "@ts/no-empty-function": [ + "error", + { + "allow": [ + "private-constructors", + "protected-constructors", + "decoratedFunctions" + ] + } + ], + "@ts/no-empty-object-type": [ + "error", + { + "allowInterfaces": "with-single-extends", + "allowObjectTypes": "never" + } + ], + "@ts/no-explicit-any": [ + "error", + { + "fixToUnknown": true, + "ignoreRestArgs": false + } + ], + "@ts/no-extra-non-null-assertion": "error", + "@ts/no-extraneous-class": [ + "error", + { + "allowConstructorOnly": false, + "allowEmpty": false, + "allowStaticOnly": true, + "allowWithDecorator": false + } + ], + "@ts/no-floating-promises": [ + "error", + { + "ignoreIIFE": false, + "ignoreVoid": false + } + ], + "@ts/no-for-in-array": "error", + "@ts/no-implied-eval": "error", + "@ts/no-import-type-side-effects": "error", + "@ts/no-invalid-void-type": [ + "error", + { + "allowAsThisParameter": true, + "allowInGenericTypeArguments": [ + "Promise", + "PromiseWithResolvers", + "Generator" + ] + } + ], + "@ts/no-loop-func": "error", + "@ts/no-magic-numbers": [ + "error", + { + "enforceConst": true, + "detectObjects": true, + "ignoreArrayIndexes": false, + "ignoreDefaultValues": false, + "ignoreEnums": true, + "ignoreNumericLiteralTypes": false, + "ignoreReadonlyClassProperties": false, + "ignoreClassFieldInitialValues": false, + "ignoreTypeIndexes": false, + "ignore": [ + 0, + 1, + -1 + ] + } + ], + "@ts/no-meaningless-void-operator": [ + "error", + { + "checkNever": true + } + ], + "@ts/no-misused-new": "error", + "@ts/no-misused-promises": [ + "error", + { + "checksConditionals": true, + "checksVoidReturn": true, + "checksSpreads": true + } + ], + "@ts/no-namespace": [ + "error", + { + "allowDeclarations": false, + "allowDefinitionFiles": false + } + ], + "@ts/no-non-null-asserted-nullish-coalescing": "error", + "@ts/no-non-null-asserted-optional-chain": "error", + "@ts/no-non-null-assertion": "error", + "@ts/no-redundant-type-constituents": "error", + "@ts/no-require-imports": "error", + "@ts/no-shadow": [ + "error", + { + "allow": [], + "hoist": "all", + "builtinGlobals": true, + "ignoreOnInitialization": false, + "ignoreTypeValueShadow": false, + "ignoreFunctionTypeParameterNameValueShadow": false + } + ], + "@ts/no-this-alias": [ + "error", + { + "allowDestructuring": false, + "allowedNames": [] + } + ], + "@ts/no-unnecessary-boolean-literal-compare": [ + "error", + { + "allowComparingNullableBooleansToTrue": false, + "allowComparingNullableBooleansToFalse": false + } + ], + "@ts/no-unnecessary-condition": [ + "error", + { + "allowConstantLoopConditions": false, + "allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing": false + } + ], + "@ts/no-unnecessary-template-expression": "error", + "@ts/no-unnecessary-type-assertion": "error", + "@ts/no-unnecessary-type-constraint": "error", + "@ts/no-unsafe-argument": "error", + "@ts/no-unsafe-assignment": "error", + "@ts/no-unsafe-call": "error", + "@ts/no-unsafe-declaration-merging": "error", + "@ts/no-unsafe-function-type": "error", + "@ts/no-unsafe-member-access": "error", + "@ts/no-unsafe-return": "error", + "@ts/no-unsafe-unary-minus": "error", + "@ts/no-unused-expressions": [ + "error", + { + "allowShortCircuit": false, + "allowTernary": false, + "allowTaggedTemplates": false, + "enforceForJSX": true + } + ], + "@ts/no-unused-vars": [ + "error", + { + "vars": "all", + "args": "after-used", + "caughtErrors": "all", + "ignoreRestSiblings": false + /* "varsIgnorePattern": undefined, */ + /* "argsIgnorePattern": undefined, */ + } + ], + "@ts/no-use-before-define": [ + "error", + { + "functions": true, + "classes": true, + "variables": true, + "enums": true, + "typedefs": true, + "ignoreTypeReferences": false, + "allowNamedExports": false + } + ], + "@ts/no-useless-constructor": "error", + "@ts/no-useless-empty-export": "error", + "@ts/no-wrapper-object-types": "error", + "@ts/only-throw-error": [ + "error", + { + "allowThrowingAny": false, + "allowThrowingUnknown": false + } + ], + "@ts/parameter-properties": [ + "error", + { + "prefer": "class-property", + "allow": [] + } + ], + "@ts/prefer-as-const": "error", + "@ts/prefer-enum-initializers": "error", + "@ts/prefer-find": "error", + "@ts/prefer-includes": "error", + "@ts/prefer-literal-enum-member": [ + "error", + { + "allowBitwiseExpressions": false + } + ], + "@ts/prefer-nullish-coalescing": [ + "error", + { + "ignoreTernaryTests": false, + "ignoreIfStatements": true, + "ignoreConditionalTests": false, + "ignoreMixedLogicalExpressions": false, + "ignoreBooleanCoercion": false, + "ignorePrimitives": { + "bigint": false, + "boolean": false, + "number": false, + "string": false + } + } + ], + "@ts/prefer-optional-chain": [ + "error", + { + "checkAny": true, + "checkUnknown": true, + "checkString": true, + "checkNumber": true, + "checkBoolean": true, + "checkBigInt": true, + "requireNullish": true, + "allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing": false + } + ], + "@ts/prefer-promise-reject-errors": [ + "error", + { + "allowEmptyReject": false + } + ], + "@ts/prefer-readonly": [ + "error", + { + "onlyInlineLambdas": false + } + ], + "@ts/prefer-regexp-exec": "error", + "@ts/prefer-return-this-type": "error", + "@ts/prefer-string-starts-ends-with": "error", + "@ts/promise-function-async": [ + "error", + { + "allowAny": true, + "allowedPromiseNames": [], + "checkArrowFunctions": true, + "checkFunctionDeclarations": true, + "checkFunctionExpressions": true, + "checkMethodDeclarations": true + } + ], + "@ts/related-getter-setter-pairs": "error", + "@ts/require-array-sort-compare": [ + "error", + { + "ignoreStringArrays": false + } + ], + "@ts/require-await": "error", + "@ts/restrict-plus-operands": [ + "error", + { + "allowAny": false, + "allowBoolean": false, + "allowNullish": false, + "allowRegExp": false, + "allowNumberAndString": false, + "skipCompoundAssignments": false + } + ], + "@ts/restrict-template-expressions": [ + "error", + { + "allowAny": false, + "allowBoolean": false, + "allowNullish": false, + "allowNumber": false, + "allowRegExp": false + } + ], + "@ts/return-await": [ + "error", + "always" + ], + "@ts/strict-boolean-expressions": [ + "error", + { + "allowString": false, + "allowNumber": false, + "allowNullableObject": false, + "allowNullableBoolean": false, + "allowNullableString": false, + "allowNullableNumber": false, + "allowNullableEnum": false, + "allowAny": false, + "allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing": false + } + ], + "@ts/switch-exhaustiveness-check": [ + "error", + { + "allowDefaultCaseForExhaustiveSwitch": false, + "requireDefaultForNonUnion": false + } + ], + "@ts/triple-slash-reference": [ + "error", + { + "lib": "never", + "path": "never", + "types": "never" + } + ], + "@ts/unbound-method": [ + "error", + { + "ignoreStatic": false + } + ], + "@ts/use-unknown-in-catch-callback-variable": "error", + "accessor-pairs": [ + "warn", + { + "setWithoutGet": true, + "getWithoutSet": true, + "enforceForClassMembers": true + } + ], + "array-callback-return": [ + "error", + { + "allowImplicit": false, + "checkForEach": true, + "allowVoid": false + } + ], + "complexity": [ + "error", + { + "max": 30 + } + ], + "default-case-last": "error", + "eqeqeq": [ + "error", + "always", + { + "null": "ignore" + } + ], + "for-direction": "error", + "func-style": [ + "error", + "declaration", + { + "allowArrowFunctions": true + } + ], + "max-classes-per-file": [ + "error", + { + "max": 1, + "ignoreExpressions": false + } + ], + "max-depth": [ + "error", + { + "max": 7 + } + ], + "max-lines": [ + "error", + { + "max": 1000, + "skipBlankLines": true, + "skipComments": true + } + ], + "max-lines-per-function": [ + "error", + { + "max": 100, + "skipBlankLines": true, + "skipComments": true, + "IIFEs": true + } + ], + "max-nested-callbacks": [ + "error", + { + "max": 4 + } + ], + "max-statements": [ + "error", + 50, + { + "ignoreTopLevelFunctions": false + } + ], + "no-async-promise-executor": "error", + "no-caller": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": [ + "error", + "always" + ], + "no-console": "warn", + "no-constant-binary-expression": "error", + "no-constant-condition": [ + "error", + { + "checkLoops": true + } + ], + "no-constructor-return": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-else-if": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": [ + "error", + { + "includeExports": true + } + ], + "no-else-return": [ + "error", + { + "allowElseIf": false + } + ], + "no-empty": [ + "error", + { + "allowEmptyCatch": false + } + ], + "no-empty-character-class": "error", + "no-empty-pattern": [ + "error", + { + "allowObjectPatternsAsParameters": false + } + ], + "no-empty-static-block": "error", + "no-eval": [ + "error", + { + "allowIndirect": false + } + ], + "no-ex-assign": "error", + "no-extend-native": [ + "error", + { + "exceptions": [] + } + ], + "no-extra-bind": "error", + "no-extra-boolean-cast": [ + "error", + { + "enforceForLogicalOperands": true + } + ], + "no-fallthrough": [ + "error", + { + "allowEmptyCase": false, + "commentPattern": "no break|break.*?omitted|falls? ?through" + } + ], + "no-global-assign": [ + "error", + { + "exceptions": [] + } + ], + "no-implicit-coercion": [ + "error", + { + "boolean": true, + "number": true, + "string": true, + "disallowTemplateShorthand": true, + "allow": [] + } + ], + "no-invalid-regexp": [ + "error", + { + "allowConstructorFlags": [ + "d", + "g", + "i", + "m", + "s", + "u", + "v", + "y", + ] + } + ], + "no-irregular-whitespace": [ + "error", + { + "skipStrings": false, + "skipComments": false, + "skipRegExps": false, + "skipTemplates": false, + "skipJSXText": false + } + ], + "no-iterator": "error", + "no-labels": [ + "error", + { + "allowLoop": false, + "allowSwitch": false + } + ], + "no-lonely-if": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": [ + "error", + { + "allowEscape": false + } + ], + "no-multi-assign": [ + "error", + { + "ignoreNonDeclaration": false + } + ], + "no-multi-str": "error", + "no-negated-condition": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-native-nonconstructor": "error", + "no-new-wrappers": "error", + "no-nonoctal-decimal-escape": "error", + "no-object-constructor": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-param-reassign": [ + "error", + { + "props": false + } + ], + "no-promise-executor-return": [ + "error", + { + "allowVoid": false + } + ], + "no-proto": "error", + "no-prototype-builtins": "error", + "no-restricted-properties": [ + "error", + { + "property": "isPrototypeOf", + "message": "Use instanceof instead" + }, + { + "property": "propertyIsEnumerable", + "message": "Do not check property enumerability" + } + ], + "no-restricted-syntax": [ + "error", + { + "message": "IIFEs are not allowed", + "selector": "CallExpression[callee.type=FunctionExpression]" + }, + { + "message": "IIFEs are not allowed", + "selector": "CallExpression[callee.type=ArrowFunctionExpression]" + } + ], + "no-return-assign": [ + "error", + "always" + ], + "no-script-url": "error", + "no-self-assign": [ + "error", + { + "props": true + } + ], + "no-self-compare": "error", + "no-sequences": [ + "error", + { + "allowInParentheses": false + } + ], + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-template-curly-in-string": "error", + "no-underscore-dangle": [ + "error", + { + "allow": [], + "allowAfterThis": true, + "allowAfterSuper": false, + "allowAfterThisConstructor": true, + "enforceInMethodNames": true, + "enforceInClassFields": true, + "allowInArrayDestructuring": false, + "allowInObjectDestructuring": false, + "allowFunctionParams": false + } + ], + "no-unexpected-multiline": "error", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": [ + "error", + { + "defaultAssignment": false + } + ], + "no-unreachable-loop": [ + "error", + { + "ignore": [] + } + ], + "no-unsafe-finally": "error", + "no-unsafe-optional-chaining": [ + "error", + { + "disallowArithmeticOperators": true + } + ], + "no-unused-private-class-members": "error", + "no-useless-backreference": "error", + "no-useless-call": "error", + "no-useless-catch": "error", + "no-useless-computed-key": [ + "error", + { + "enforceForClassMembers": true + } + ], + "no-useless-concat": "error", + "no-useless-escape": "error", + "no-useless-rename": [ + "error", + { + "ignoreDestructuring": false, + "ignoreImport": false, + "ignoreExport": false + } + ], + "no-useless-return": "error", + "no-var": "error", + "no-void": [ + "error", + { + "allowAsStatement": false + } + ], + "no-warning-comments": [ + "warn", + { + "terms": [ + "TODO", + "FIXME" + ], + "location": "anywhere" + } + ], + "no-with": "error", + "object-shorthand": [ + "error", + "consistent" + ], + "one-var": [ + "error", + "never" + ], + "prefer-arrow-callback": [ + "error", + { + "allowNamedFunctions": false, + "allowUnboundThis": false + } + ], + "prefer-const": [ + "error", + { + "destructuring": "any", + "ignoreReadBeforeAssign": true + } + ], + "prefer-named-capture-group": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-object-spread": "error", + "prefer-regex-literals": [ + "error", + { + "disallowRedundantWrapping": true + } + ], + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + "radix": [ + "error", + "always" + ], + "require-atomic-updates": [ + "error", + { + "allowProperties": false + } + ], + "require-yield": "error", + "unicode-bom": [ + "error", + "never" + ], + "strict": [ + "error", + "never" + ], + "symbol-description": "error", +}; + +export { RULES as rules }; diff --git a/.eslint/configuration/style/config.mjs b/.eslint/configuration/style/config.mjs new file mode 100644 index 0000000..c197e7f --- /dev/null +++ b/.eslint/configuration/style/config.mjs @@ -0,0 +1,12 @@ +import { rules } from "./rules.mjs"; + +const CONFIGURATION = { + name: "stylistic", + files: [ + "**/*.mts", + ], + ignores: [], + rules: rules, +}; + +export { CONFIGURATION as configuration }; diff --git a/.eslint/configuration/style/rules.mjs b/.eslint/configuration/style/rules.mjs new file mode 100644 index 0000000..4e565fb --- /dev/null +++ b/.eslint/configuration/style/rules.mjs @@ -0,0 +1,806 @@ +const RULES = { + "@style/array-bracket-newline": [ + "error", + "consistent" + // unsupported combination + /* + { + "consistent": true, + "multiline": true + } + */ + ], + "@style/array-bracket-spacing": [ + "error", + "never", + { + "singleValue": false, + "objectsInArrays": false, + "arraysInArrays": false + } + ], + "@style/array-element-newline": [ + "error", + { + "consistent": true, + "multiline": true + } + ], + "@style/arrow-parens": [ + "error", + "always" + ], + "@style/arrow-spacing": [ + "error", + { + "before": true, + "after": true + } + ], + "@style/comma-style": [ + "error", + "last" + ], + "@style/computed-property-spacing": [ + "error", + "never" + ], + "@style/curly-newline": [ + "error", + { + "multiline": true, + "minElements": 1, + "consistent": true, + } + ], + "@style/dot-location": [ + "error", + "property" + ], + "@style/eol-last": [ + "error", + "always" + ], + "@style/function-call-argument-newline": [ + "error", + "consistent" + ], + "@style/function-call-spacing": [ + "error", + "never" + ], + "@style/function-paren-newline": [ + "error", + "multiline-arguments" + ], + "@style/generator-star-spacing": [ + "error", + { + "named": { + "before": false, + "after": true + }, + "anonymous": { + "before": false, + "after": false + }, + "method": { + "before": true, + "after": true + } + } + ], + "@style/jsx-quotes": [ + "error", + "prefer-double" + ], + "@style/linebreak-style": [ + "error", + "unix" + ], + "@style/line-comment-position": [ + "error", + { + "position": "above" + } + ], + "@style/max-len": [ + "error", + { + "code": 200, + "tabWidth": 4, + "comments": 300, + /*"ignorePattern": undefined*/ + "ignoreComments": false, + "ignoreTrailingComments": false, + "ignoreUrls": false, + "ignoreStrings": true, + "ignoreRegExpLiterals": true, + "ignoreTemplateLiterals": true + } + ], + "@style/max-statements-per-line": [ + "error", + { + "max": 1 + } + ], + "@style/multiline-ternary": [ + "error", + "always-multiline" + ], + "@style/new-parens": [ + "error", + "always" + ], + "@style/newline-per-chained-call": [ + "error", + { + /*"consistent": true,*/ + "ignoreChainWithDepth": 3 + } + ], + "@style/no-floating-decimal": "error", + "@style/no-mixed-spaces-and-tabs": [ + "error", + "smart-tabs" + ], + "@style/no-multi-spaces": [ + "error", + { + "includeTabs": false, + "ignoreEOLComments": false, + "exceptions": { + "Property": false, + "VariableDeclarator": false, + "ImportDeclaration": false + } + } + ], + "@style/no-multiple-empty-lines": [ + "error", + { + "max": 1, + "maxBOF": 0, + "maxEOF": 1 + } + ], + "@style/no-tabs": [ + "error", + { + "allowIndentationTabs": true + } + ], + "@style/no-trailing-spaces": [ + "error", + { + "skipBlankLines": false, + "ignoreComments": false + } + ], + "@style/no-whitespace-before-property": "error", + "@style/object-curly-newline": [ + "error", + { + "ObjectExpression": { + "consistent": true, + "multiline": true + }, + "ObjectPattern": { + "consistent": true, + "multiline": true + }, + "ImportDeclaration": { + "consistent": true, + "multiline": true + }, + "ExportDeclaration": { + "consistent": true, + "multiline": true + } + } + ], + "@style/object-property-newline": [ + "error", + { + "allowAllPropertiesOnSameLine": true + } + ], + "@style/operator-linebreak": [ + "error", + "before" + ], + "@style/padded-blocks": [ + "error", + "never" + ], + "@style/rest-spread-spacing": [ + "error", + "never" + ], + "@style/semi-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "@style/semi-style": [ + "error", + "last" + ], + "@style/space-in-parens": [ + "error", + "never" + ], + "@style/space-unary-ops": [ + "error", + { + "words": true, + "nonwords": false + } + ], + "@style/spaced-comment": [ + "error", + "always", + { + "line": { + "markers": [ + "/" + ], + "exceptions": [ + "-", + "=", + "*" + ] + }, + "block": { + "markers": [ + "*" + ], + "exceptions": [ + "*" + ], + "balanced": true + } + } + ], + "@style/switch-colon-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "@style/template-curly-spacing": [ + "error", + "never" + ], + "@style/template-tag-spacing": [ + "error", + "never" + ], + "@style/wrap-iife": [ + "error", + "inside", + { + "functionPrototypeMethods": true + } + ], + "@style/yield-star-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "@style/block-spacing": [ + // Possibly unnecessary + "error", + "always" + ], + "@style/brace-style": [ + "error", + "allman", + { + "allowSingleLine": true + } + ], + "@style/comma-dangle": [ + "error", + { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never", + "enums": "always-multiline", + "generics": "never", + "tuples": "always-multiline" + } + ], + "@style/comma-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "@style/function-call-spacing": [ + "error", + "never" + ], + "@style/implicit-arrow-linebreak": [ + "error", + "beside" + ], + "@style/key-spacing": [ + "error", + { + "beforeColon": false, + "afterColon": true, + "mode": "strict" + } + ], + "@style/keyword-spacing": [ + "error", + { + "before": true, + "after": true + /* + "before": false, + "after": true, + "overrides": { + "as": { + "before": true, + "after": true + }, + "in": { + "before": true, + "after": true + }, + "of": { + "before": true, + "after": true + } + } + */ + } + ], + "@style/lines-between-class-members": [ + "error", + "always", + { + "exceptAfterOverload": true, + "exceptAfterSingleLine": true + } + ], + "@style/member-delimiter-style": [ + "error", + { + "multilineDetection": "brackets", + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } + ], + "@style/no-extra-parens": [ + "error", + "all", + { + "conditionalAssign": true, + "returnAssign": true, + "nestedBinaryExpressions": false, + "ternaryOperandBinaryExpressions": false, + "enforceForSequenceExpressions": false, + "enforceForFunctionPrototypeMethods": false, + /*"allowParensAfterCommentPattern": undefined,*/ + "ignoreJSX": "multi-line", + "ignoredNodes": [ + // Allows () => (a ? b : c) instead of () => a ? b : c + "ArrowFunctionExpression[body.type=ConditionalExpression]", + // Allows (new Foo()).bar instead of new Foo().bar + "MemberExpression[object.type=NewExpression]", + // Allows ...(a ? b : c) instead of ...a ? b : c + "SpreadElement[argument.type=ConditionalExpression]", + // Allows ...(a && b) instead of ...a && b + "SpreadElement[argument.type=LogicalExpression]", + // Allows ...(await foo) instead of ...await foo + "SpreadElement[argument.type=AwaitExpression]", + // Allows type X = (A | B) instead of type X = A | B + "TSTypeAliasDeclaration[typeAnnotation.type=TSUnionType]", + ] + } + ], + "@style/no-extra-semi": "error", + "@style/object-curly-spacing": [ + "error", + "always", + { + "arraysInObjects": true, + "objectsInObjects": true + } + ], + "@style/padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": [ + "const", + "let" + ], + "next": "*" + }, + { + "blankLine": "any", + "prev": [ + "const", + "let" + ], + "next": [ + "const", + "let" + ] + }, + { + "blankLine": "always", + "prev": "*", + "next": [ + "type", + "interface", + "class", + "function", + "continue", + "return", + "throw", + "try", + "switch", + "case", + "default", + "if", + "do", + "while", + "export", + "multiline-expression", + "block", + "block-like", + "multiline-block-like" + ] + }, + { + "blankLine": "always", + "prev": [ + "type", + "interface", + "class", + "function", + "switch", + "require", + "import", + "multiline-expression", + "block", + "block-like", + "multiline-block-like" + ], + "next": "*" + }, + { + "blankLine": "any", + "prev": [ + "case" + ], + "next": [ + "case" + ] + }, + { + "blankLine": "any", + "prev": [ + "import" + ], + "next": [ + "import" + ] + }, + { + "blankLine": "any", + "prev": [ + "export" + ], + "next": [ + "export" + ] + } + ], + "@style/quote-props": [ + "error", + "consistent-as-needed" + ], + "@style/quotes": [ + "error", + "double", + { + "allowTemplateLiterals": "avoidEscape", + "avoidEscape": true + } + ], + "@style/semi": [ + "error", + "always", + { + "omitLastInOneLineBlock": false, + "omitLastInOneLineClassBody": false + } + ], + "@style/space-before-blocks": [ + // Possibly unnecessary + "error", + "always" + ], + "@style/space-before-function-paren": [ + "error", + { + "anonymous": "never", + "named": "never", + "asyncArrow": "always" + } + ], + "@style/space-infix-ops": [ + "error", + { + "int32Hint": false + } + ], + "@style/type-annotation-spacing": [ + "error", + { + "before": false, + "after": true, + "overrides": { + "arrow": "ignore" + } + } + ], + "@style/type-named-tuple-spacing": "error", + "@ts/adjacent-overload-signatures": "error", + "@ts/class-literal-property-style": [ + "error", + "fields" + ], + "@ts/consistent-indexed-object-style": [ + "error", + "record" + ], + "@ts/consistent-type-definitions": [ + "error", + "interface" + ], + "@ts/dot-notation": [ + "error", + { + "allowKeywords": true, + /*"allowPattern": undefined,*/ + "allowPrivateClassPropertyAccess": false, + "allowProtectedClassPropertyAccess": false, + "allowIndexSignaturePropertyAccess": true + } + ], + "@ts/member-ordering": [ + "error", + { + "default": { + "order": "as-written", + "memberTypes": [ + // Signatures + "readonly-signature", + "signature", + "call-signature", + // Static fields + "public-static-readonly-field", + "public-static-field", + "protected-static-readonly-field", + "protected-static-field", + "private-static-readonly-field", + "private-static-field", + "#private-static-readonly-field", + "#private-static-field", + // Abstract fields + "public-abstract-readonly-field", + "public-abstract-field", + "protected-abstract-readonly-field", + "protected-abstract-field", + // Instance fields + "public-instance-readonly-field", + "public-instance-field", + "protected-instance-readonly-field", + "protected-instance-field", + "private-instance-readonly-field", + "private-instance-field", + // Decorated fields + "public-decorated-readonly-field", + "public-decorated-field", + "protected-decorated-readonly-field", + "protected-decorated-field", + "private-decorated-readonly-field", + "private-decorated-field", + // Default fields + "readonly-field", + "field", + // Static initialization + "static-initialization", + // Constructors + "public-constructor", + "protected-constructor", + "private-constructor", + "constructor", + // Static methods + "public-static-method", + "protected-static-method", + "private-static-method", + "#private-static-method", + // Abstract methods + "public-abstract-method", + "protected-abstract-method", + // Instance methods + "public-instance-method", + "protected-instance-method", + "private-instance-method", + // Decorated methods + "public-decorated-method", + "protected-decorated-method", + "private-decorated-method", + // Default methods + "method", + // Static getters and setters + [ + "public-static-get", + "public-static-set" + ], + [ + "protected-static-get", + "protected-static-set" + ], + [ + "private-static-get", + "private-static-set" + ], + // Abstract getters and setters + [ + "public-abstract-get", + "public-abstract-set" + ], + [ + "protected-abstract-get", + "protected-abstract-set" + ], + // Instance getters and setters + [ + "public-instance-get", + "public-instance-set" + ], + [ + "private-instance-get", + "private-instance-set" + ], + [ + "protected-instance-get", + "protected-instance-set" + ], + // Decorated getters and setters + [ + "public-decorated-get", + "public-decorated-set" + ], + [ + "protected-decorated-get", + "protected-decorated-set" + ], + [ + "private-decorated-get", + "private-decorated-set" + ], + // Default getters and setters + [ + "get", + "set" + ] + ] + } + } + ], + "@ts/method-signature-style": [ + "error", + "property" + ], + "@ts/prefer-for-of": "error", + "@ts/prefer-function-type": "error", + "arrow-body-style": [ + "error", + "always" + ], + "curly": [ + "error", + "all" + ], + "func-names": [ + "error", + "never", + { + "generators": "never" + } + ], + "grouped-accessor-pairs": [ + "error", + "getBeforeSet" + ], + "id-denylist": [ + "error", // enable rule + "cb", + "e", + "err", + "el", + "ev", + "ex", + "f", + "fn", + "fun", + "func", + "idx", + "k", + "o", + "obj", + "v", + "val" + ], + "id-length": [ + "error", + { + "min": 3, + "max": 100, + "properties": "always", + "exceptions": [ + "a", + "b", + "i", + "j", + "x", + "y", + "z", + "id", + "to", + "up", + "pH" + ] + } + ], + "logical-assignment-operators": [ + "error", + "never" + ], + "no-regex-spaces": "error", + "operator-assignment": [ + "error", + "never" + ], + "sort-imports": [ + "error", + { + "ignoreDeclarationSort": true + } + ], + "yoda": [ + "error", + "never", + { + "exceptRange": true, + "onlyEquality": false + } + ] +}; + +export { RULES as rules }; diff --git a/.eslint/configuration/test/config.mjs b/.eslint/configuration/test/config.mjs new file mode 100644 index 0000000..6f61c79 --- /dev/null +++ b/.eslint/configuration/test/config.mjs @@ -0,0 +1,14 @@ +import { rules } from "./rules.mjs"; + +const CONFIGURATION = { + name: "test", + files: [ + "*.spec.mts", + "test/**/*.mts", + "mock/**/*.mts", + ], + ignores: [], + rules: rules, +}; + +export { CONFIGURATION as configuration }; diff --git a/.eslint/configuration/test/rules.mjs b/.eslint/configuration/test/rules.mjs new file mode 100644 index 0000000..57e69d4 --- /dev/null +++ b/.eslint/configuration/test/rules.mjs @@ -0,0 +1,45 @@ +const RULES = { + "@style/generator-star-spacing": "off", + "@style/brace-style": "off", + "@style/curly-newline": "off", + "@style/max-statements-per-line": [ + "error", + { + "max": 2 + } + ], + "@ts/class-methods-use-this": "off", + "@ts/dot-notation": [ + "error", + { + "allowKeywords": true, + "allowPrivateClassPropertyAccess": true, + "allowProtectedClassPropertyAccess": true, + "allowIndexSignaturePropertyAccess": true + } + ], + "@ts/explicit-module-boundary-types": "off", + "@ts/no-confusing-void-expression": "off", + "@ts/no-empty-function": "off", + "@ts/no-extraneous-class": "off", + "@ts/no-floating-promises": "off", + "@ts/no-magic-numbers": "off", + "@ts/no-unsafe-assignment": "off", + "@ts/no-unsafe-argument": "off", + "@ts/no-unsafe-member-access": "off", + "@ts/no-unused-expressions": "off", + "@ts/no-unsafe-call": "off", + "@ts/only-throw-error": "off", + "@ts/prefer-promise-reject-errors": "off", + "@ts/unbound-method": "off", + "func-names": "off", + "func-style": "off", + "max-classes-per-file": "off", + "max-lines": "off", + "max-lines-per-function": "off", + "max-statements": "off", + "no-new": "off", + "symbol-description": "off", +}; + +export { RULES as rules }; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 567eac2..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": [ - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "indent": [ - "warn", - 4, - { - "SwitchCase": 1, - "FunctionExpression": { - "parameters": "first" - }, - "FunctionDeclaration": { - "parameters": "first" - } - } - ], - "linebreak-style": [ - "warn", - "unix" - ], - "quotes": [ - "warn", - "single" - ], - "semi": [ - "warn", - "never" - ], - "template-curly-spacing": [ - "warn", - "always" - ], - "brace-style": [ - "warn", - "allman", - { "allowSingleLine": true } - ], - "comma-dangle": [ - "warn", - "always-multiline" - ], - "key-spacing": [ - "warn", { - "beforeColon": false, - "afterColon": true, - "mode": "strict" - } - ], - "object-curly-spacing": [ - "warn", - "always", - { "objectsInObjects": true } - ], - "@typescript-eslint/no-unused-vars": [ - "warn", - { - "argsIgnorePattern": "^_", - "destructuredArrayIgnorePattern": "^_" - } - ] - } -} - diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4942844 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + groups: + dev-dependency: + dependency-type: "development" diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index 2ae2af8..0000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Node.js CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Use Node.js 16.x - uses: actions/setup-node@v3 - with: - node-version: 16.x - - run: npm install - - run: npm test diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..562786d --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,188 @@ +name: Continuous Integration and Deployment + +run-name: Running CI/CD for event ${{github.event_name}} (Triggered by user ${{ github.triggering_actor }}) + +on: + push: + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + ts-files-changed: ${{ steps.parse-changed-files.outputs.ts-files-changed }} + require-spell-check: ${{ steps.parse-changed-files.outputs.require-spell-check }} + steps: + - name: Check out repository code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: PNPM installation + uses: pnpm/action-setup@v4 + id: pnpm-install + with: + version: 10 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: PNPM cache configuration + uses: actions/cache@v4 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: | + pnpm install --frozen-lockfile + + - name: Dependencies caching + uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + + spell-check-global: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: PNPM installation + uses: pnpm/action-setup@v4 + id: pnpm-install + with: + version: 10 + run_install: false + + - name: Dependencies cache unpacking + uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Check spelling with CSpell + run: pnpm ci:spell:check + + ## Lua Engine + + lint-lua-engine: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: PNPM installation + uses: pnpm/action-setup@v4 + id: pnpm-install + with: + version: 10 + run_install: false + + - name: Dependencies cache unpacking + uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Lint with ESlint + run: pnpm ci:lint + + typescript-syntax-check-lua-engine: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: PNPM installation + uses: pnpm/action-setup@v4 + id: pnpm-install + with: + version: 10 + run_install: false + + - name: Dependencies cache unpacking + uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Check TypeScript syntax + run: pnpm ci:ts:check + + test-lua-engine: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: PNPM installation + uses: pnpm/action-setup@v4 + id: pnpm-install + with: + version: 10 + run_install: false + + - uses: actions/setup-node@v4 + with: + node-version: "22.x" + + - name: Dependencies cache unpacking + uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Test with Node Test runner + run: pnpm ci:test:unit + + publish-lua-engine: + # if: ${{ github.ref == 'refs/heads/main' && ! failure() && ! cancelled() && github.event_name == 'push' }} + # needs: + # [ + # spell-check-global, + # lint-lua-engine, + # typescript-syntax-check-lua-engine, + # ] + runs-on: ubuntu-latest + environment: npm + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22.x" + registry-url: "https://registry.npmjs.org" + + - name: PNPM installation + uses: pnpm/action-setup@v4 + id: pnpm-install + with: + version: 10 + run_install: false + + - name: Dependencies cache unpacking + uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Package build + run: pnpm ci:build + + - name: Publish package + id: package-version + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: pnpm ci:publish diff --git a/.gitignore b/.gitignore index b8ffe08..975245a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,259 @@ +# Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos,visualstudiocode,git,node +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos,visualstudiocode,git,node + +### Custom ## +build/ +reports/ +tmp/ +*.crt +*.key + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories node_modules/ -package-lock.json +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!.vscode/cspell.json + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk +# End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,visualstudiocode,git,node diff --git a/.swcrc b/.swcrc new file mode 100644 index 0000000..631d6ce --- /dev/null +++ b/.swcrc @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/swcrc.json", + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": false, + "decorators": false, + "dynamicImport": true + }, + "target": "esnext" + }, + "module": { + "type": "es6", + // These are defaults. + "strict": false, + "strictMode": true, + "lazy": false, + "noInterop": false + } +} diff --git a/.vscode/cspell.json b/.vscode/cspell.json new file mode 100644 index 0000000..27b6d09 --- /dev/null +++ b/.vscode/cspell.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "language": "en-GB", + "allowCompoundWords": true, + "dictionaries": [ + "softwareTerms", + "typescript", + "node", + "html", + "css", + "bash", + "npm" + ], + "words": [ + "Vitruvius", + "architectura", + ], + "ignoreWords": [ + "Lebacq", + "Zamralik" + ], + "flagWords": [ + "hte" + ], + "ignorePaths": [ + ".eslintrc.json", + ".git/**", + ".github/CODEOWNERS", + ".gitignore", + ".mocharc.json", + "**/*.log", + "**/node_modules/**", + "build/", + "CHANGELOG.md", + "package.json", + "pipeline.yml", + "pnpm-lock.yaml", + "reports/", + "tmp/", + "tsconfig.json", + "**/test.mts", + "**/coverage/**" + ] +} diff --git a/README.md b/README.md index e728be6..6909189 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # TSLua -A pure TypeScript reimplementation of the Lua programming language. +A pure TypeScript implementation of a Lua programming language interpreter. This implementation is designed to be as close as possible to the original C implementation, while providing seamless TypeScript/JavaScript interoperability. All 'native' functions are implemented in TypeScript and objects are represented using standard JavaScript objects. Meaning garbage collection is left as the responsibility of the JavaScript engine. @@ -10,10 +10,10 @@ To create a Lua runtime environment, simply instantiate the `Engine` object with Global state is maintained for the whole lifetime of the `Engine` object. Lua variables can be queried by name using the `global` method. `define` and `define_table` can also be used to interact with the global object. An example is shown below: ```ts - const engine = lua.Engine('a = 1 + 2') + const engine = lua.Engine("a = 1 + 2") engine.run() - const a = engine.global('a')?.number + const a = engine.global("a")?.number console.log(a) // --> 3 ``` @@ -86,10 +86,10 @@ The following *keywords* are reserved and cannot be used as names: ```lua and break do else elseif - end false for function goto - if in local nil not - or repeat return then true - until while + end false for function global + goto if in local nil + not or repeat return then + true until while ``` Lua is a case-sensitive language: `and` is a reserved word, but `And`and `AND` are two different, valid names. As a convention, programs should avoid creating names that start with an underscore followed by one or more uppercase letters (such as `_VERSION`). @@ -132,7 +132,7 @@ Examples of valid integer constants are ```lua 3 - 345 + 345 0xff 0xBEBADA ``` @@ -140,7 +140,7 @@ Examples of valid integer constants are Examples of valid float constants are ```lua - 3.0 + 3.0 3.1416 314.16e-2 0.31416E1 @@ -589,9 +589,9 @@ As usual, you can use parentheses to change the precedences of an expression. Th Table constructors are expressions that create tables. Every time a constructor is evaluated, a new table is created. A constructor can be used to create an empty table or to create a table and initialize some of its fields. The general syntax for constructors is ```lua - tableconstructor ::= ‘{’ [fieldlist] ‘}’ - fieldlist ::= field {fieldsep field} [fieldsep] - field ::= ‘[’ exp ‘]’ ‘=’ exp | Name ‘=’ exp | exp + tableconstructor ::= ‘{’ [fieldlist] ‘}’ + fieldlist ::= field {fieldsep field} [fieldsep] + field ::= ‘[’ exp ‘]’ ‘=’ exp | Name ‘=’ exp | exp fieldsep ::= ‘,’ | ‘;’ ``` @@ -1131,11 +1131,11 @@ A *character class* is used to represent a set of characters. The following comb - **`%x`:** represents all hexadecimal digits. - **`%x`:** (where *x* is any non-alphanumeric character) represents the character *x*. This is the standard way to escape the magic characters. Any non-alphanumeric character (including all punctuation characters, even the non-magical) can be preceded by a '`%`' to represent itself in a pattern. - **`[set]`:** represents the class which is the union of all characters in *set*. A range of characters can be specified by separating the end characters of the range, in ascending order, with a '`-`'. All classes `%`*x* described above can also be used as components in *set*. All other characters in *set* represent themselves. For example, `[%w_]` (or `[_%w]`) represents all alphanumeric characters plus the underscore, `[0-7]` represents the octal digits, and `[0-7%l%-]` represents the octal digits plus the lowercase letters plus the '`-`' character. - + You can put a closing square bracket in a set by positioning it as the first character in the set. You can put a hyphen in a set by positioning it as the first or the last character in the set. (You can also use an escape for both cases.) - + The interaction between ranges and classes is not defined. Therefore, patterns like `[%a-z]` or `[a-%%]` have no meaning. - + - **`[^set]`:** represents the complement of *set*, where *set* is interpreted as above. For all classes represented by single letters (`%a`, `%c`, etc.), the corresponding uppercase letter represents the complement of the class. For instance, `%S` represents all non-space characters. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..33aa85d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,3 @@ +import { configurations } from "./.eslint/configuration/_index.mjs"; + +export default configurations; diff --git a/index.ts b/index.ts deleted file mode 100644 index f6b0d2b..0000000 --- a/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import { Engine } from './src/engine' -import { DataType, Variable, NativeFunction, nil } from './src/runtime' -import { make_boolean, make_number, make_string } from './src/runtime' -import { compile } from './src/compiler' -import { std_lib, variable_to_string } from './src/lib' -import * as lexer from './src/lexer' -import * as parser from './src/parser' -import * as ast from './src/ast' -import * as opcode from './src/opcode' -import * as runtime from './src/runtime' - -export const Nil = DataType.Nil -export const Boolean = DataType.Boolean -export const Number = DataType.Number -export const String = DataType.String -export const Function = DataType.Function -export const NativeFunctionType = DataType.NativeFunction -export const Table = DataType.Table - -export -{ - std_lib as std_global, - nil, - make_boolean as boolean, - make_number as number, - make_string as string, - variable_to_string as to_string, - Engine, DataType, Variable, NativeFunction, - lexer, parser, ast, compile, opcode, runtime, -} - diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 463894e..0000000 --- a/jest.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -} - diff --git a/package.json b/package.json index 4dc3838..1302376 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,70 @@ { - "name": "tslua", - "version": "1.0.0", - "description": "", - "scripts": { - "test": "jest" - }, - "files": [ - "index.ts" - ], - "author": "", - "license": "ISC", - "devDependencies": { - "@types/jest": "^29.5.3", - "jest": "^29.6.1", - "ts-jest": "^29.1.1", - "typescript": "^5.1.6" - } + "$schema": "https://json.schemastore.org/package.json", + "name": "@vitruvius-labs/lua-engine", + "version": "0.1.2", + "description": "A TypeScript implementation of a Lua interpreter", + "license": "BSD-2-Clause", + "author": { + "name": "VitruviusLabs" + }, + "contributors": [ + "Nicolas \"SmashingQuasar\" Lebacq ", + "Benjamin Blum ", + "Ben Jilks " + ], + "keywords": [ + "lua" + ], + "homepage": "https://github.com/VitruviusLabs/lua-engine#readme", + "bugs": "https://github.com/VitruviusLabs/lua-engine/issues", + "repository": { + "type": "git", + "url": "https://github.com/VitruviusLabs/lua-engine.git" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "engines": { + "node": ">=18.0.0" + }, + "type": "module", + "exports": { + ".": { + "import": "./build/esm/_index.mjs", + "types": "./build/types/_index.d.mts" + } + }, + "files": [ + "build" + ], + "scripts": { + "clean": "rm -rf reports coverage build dist lib .eslintcache", + "compile": "tsc -p tsconfig.build.json", + "build": "pnpm clean && pnpm compile", + "eslint:check": "eslint", + "eslint:fix": "eslint --fix", + "ts:check": "tsc -p tsconfig.json --noEmit", + "spell:check": "cspell -c ./.vscode/cspell.json .", + "ci:lint": "pnpm eslint:check", + "ci:ts:check": "pnpm ts:check", + "ci:spell:check": "pnpm spell:check", + "ci:publish": "pnpm publish --access public", + "ci:publish:dry": "pnpm publish --access public --dry-run --no-git-checks", + "ci:build": "pnpm build" + }, + "dependencies": { + "@vitruvius-labs/ts-predicate": "^7.0.0" + }, + "devDependencies": { + "@stylistic/eslint-plugin": "^5.7.0", + "@types/node": "^25.0.9", + "@swc/core": "^1.11.21", + "cspell": "^9.6.0", + "eslint": "^9.25.0", + "eslint-plugin-perfectionist": "^5.3.1", + "tsx": "^4.19.3", + "typescript": "^5.8.3", + "typescript-eslint": "^8.30.1" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..4b9d94d --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2143 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@vitruvius-labs/ts-predicate': + specifier: ^7.0.0 + version: 7.0.0 + devDependencies: + '@stylistic/eslint-plugin': + specifier: ^5.7.0 + version: 5.7.0(eslint@9.39.2) + '@swc/core': + specifier: ^1.11.21 + version: 1.15.10 + '@types/node': + specifier: ^25.0.9 + version: 25.0.9 + cspell: + specifier: ^9.6.0 + version: 9.6.0 + eslint: + specifier: ^9.25.0 + version: 9.39.2 + eslint-plugin-perfectionist: + specifier: ^5.3.1 + version: 5.3.1(eslint@9.39.2)(typescript@5.9.3) + tsx: + specifier: ^4.19.3 + version: 4.21.0 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.30.1 + version: 8.53.1(eslint@9.39.2)(typescript@5.9.3) + +packages: + + '@cspell/cspell-bundled-dicts@9.6.0': + resolution: {integrity: sha512-gLNe9bB+5gMsTEhR9YPE0Wt122HR2EV+Q1j9W+MbwbeBJmpTWrgAP1ZdpvHOg+6LF6x/bD/EC9HfWdd/om8wXA==} + engines: {node: '>=20'} + + '@cspell/cspell-json-reporter@9.6.0': + resolution: {integrity: sha512-5sY1lgAXS5xEOsjT5rREMADj7pHIt56XOL7xR80nNl0TwlpZbeBHhoB2aH5sirVTeodJFN5iraXNbVOYPPupPw==} + engines: {node: '>=20'} + + '@cspell/cspell-pipe@9.6.0': + resolution: {integrity: sha512-YNuY8NNXfE+8Qzknm2ps6QbrZLZu6rSZTZr3dYW3K6TK7+IFVlJ6e2Z9iKJTqp6aZ4AGU/r9QYGmNX4Oq4gZ0A==} + engines: {node: '>=20'} + + '@cspell/cspell-resolver@9.6.0': + resolution: {integrity: sha512-Gb2UWNmRpTOQGpYL4Q/LMw+b50KcRZcf/wJg6w0Yl3IT+F/uDNhNh1f5rHuTyGsbMsMxHJhsb2AoP+73GlbIfw==} + engines: {node: '>=20'} + + '@cspell/cspell-service-bus@9.6.0': + resolution: {integrity: sha512-DCuKKkySTEB8MPLTdoPMdmakYcx7XCsHz1YEMbzOcLqJCxXsRlRZg4qE9kRBee/2QY7eYA2kaYNgn/TDMooa4g==} + engines: {node: '>=20'} + + '@cspell/cspell-types@9.6.0': + resolution: {integrity: sha512-JTqrD47tV+rWc1y2W8T0NTfWLQMlSWX4OF64/Jf3WbsOD+4UXVIfjRlzPry7+1Zekm6pa38+23jkDBytYpu8yw==} + engines: {node: '>=20'} + + '@cspell/dict-ada@4.1.1': + resolution: {integrity: sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==} + + '@cspell/dict-al@1.1.1': + resolution: {integrity: sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==} + + '@cspell/dict-aws@4.0.17': + resolution: {integrity: sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==} + + '@cspell/dict-bash@4.2.2': + resolution: {integrity: sha512-kyWbwtX3TsCf5l49gGQIZkRLaB/P8g73GDRm41Zu8Mv51kjl2H7Au0TsEvHv7jzcsRLS6aUYaZv6Zsvk1fOz+Q==} + + '@cspell/dict-companies@3.2.10': + resolution: {integrity: sha512-bJ1qnO1DkTn7JYGXvxp8FRQc4yq6tRXnrII+jbP8hHmq5TX5o1Wu+rdfpoUQaMWTl6balRvcMYiINDesnpR9Bw==} + + '@cspell/dict-cpp@7.0.2': + resolution: {integrity: sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==} + + '@cspell/dict-cryptocurrencies@5.0.5': + resolution: {integrity: sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==} + + '@cspell/dict-csharp@4.0.8': + resolution: {integrity: sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==} + + '@cspell/dict-css@4.0.19': + resolution: {integrity: sha512-VYHtPnZt/Zd/ATbW3rtexWpBnHUohUrQOHff/2JBhsVgxOrksAxJnLAO43Q1ayLJBJUUwNVo+RU0sx0aaysZfg==} + + '@cspell/dict-dart@2.3.2': + resolution: {integrity: sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==} + + '@cspell/dict-data-science@2.0.13': + resolution: {integrity: sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==} + + '@cspell/dict-django@4.1.6': + resolution: {integrity: sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==} + + '@cspell/dict-docker@1.1.17': + resolution: {integrity: sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==} + + '@cspell/dict-dotnet@5.0.11': + resolution: {integrity: sha512-LSVKhpFf/ASTWJcfYeS0Sykcl1gVMsv2Z5Eo0TnTMSTLV3738HH+66pIsjUTChqU6SF3gKPuCe6EOaRYqb/evA==} + + '@cspell/dict-elixir@4.0.8': + resolution: {integrity: sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==} + + '@cspell/dict-en-common-misspellings@2.1.11': + resolution: {integrity: sha512-2jcY494If1udvzd7MT2z/QH/RACUo/I02vIY4ttNdZhgYvUmRKhg8OBdrbzYo0lJOcc7XUb8rhIFQRHzxOSVeA==} + + '@cspell/dict-en-gb-mit@3.1.16': + resolution: {integrity: sha512-4PPdapCJslytxAVJu35Mv97qDyGmAQxtDE790T2bWNhcqN6gvRVAc/eTRaXkUIf21q1xCxxNNqpH4VfMup69rQ==} + + '@cspell/dict-en_us@4.4.27': + resolution: {integrity: sha512-0y4vH2i5cFmi8sxkc4OlD2IlnqDznOtKczm4h6jA288g5VVrm3bhkYK6vcB8b0CoRKtYWKet4VEmHBP1yI+Qfw==} + + '@cspell/dict-filetypes@3.0.15': + resolution: {integrity: sha512-uDMeqYlLlK476w/muEFQGBy9BdQWS0mQ7BJiy/iQv5XUWZxE2O54ZQd9nW8GyQMzAgoyg5SG4hf9l039Qt66oA==} + + '@cspell/dict-flutter@1.1.1': + resolution: {integrity: sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==} + + '@cspell/dict-fonts@4.0.5': + resolution: {integrity: sha512-BbpkX10DUX/xzHs6lb7yzDf/LPjwYIBJHJlUXSBXDtK/1HaeS+Wqol4Mlm2+NAgZ7ikIE5DQMViTgBUY3ezNoQ==} + + '@cspell/dict-fsharp@1.1.1': + resolution: {integrity: sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==} + + '@cspell/dict-fullstack@3.2.7': + resolution: {integrity: sha512-IxEk2YAwAJKYCUEgEeOg3QvTL4XLlyArJElFuMQevU1dPgHgzWElFevN5lsTFnvMFA1riYsVinqJJX0BanCFEg==} + + '@cspell/dict-gaming-terms@1.1.2': + resolution: {integrity: sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==} + + '@cspell/dict-git@3.0.7': + resolution: {integrity: sha512-odOwVKgfxCQfiSb+nblQZc4ErXmnWEnv8XwkaI4sNJ7cNmojnvogYVeMqkXPjvfrgEcizEEA4URRD2Ms5PDk1w==} + + '@cspell/dict-golang@6.0.26': + resolution: {integrity: sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==} + + '@cspell/dict-google@1.0.9': + resolution: {integrity: sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==} + + '@cspell/dict-haskell@4.0.6': + resolution: {integrity: sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==} + + '@cspell/dict-html-symbol-entities@4.0.5': + resolution: {integrity: sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==} + + '@cspell/dict-html@4.0.14': + resolution: {integrity: sha512-2bf7n+kS92g+cMKV0wr9o/Oq9n8JzU7CcrB96gIh2GHgnF+0xDOqO2W/1KeFAqOfqosoOVE48t+4dnEMkkoJ2Q==} + + '@cspell/dict-java@5.0.12': + resolution: {integrity: sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==} + + '@cspell/dict-julia@1.1.1': + resolution: {integrity: sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==} + + '@cspell/dict-k8s@1.0.12': + resolution: {integrity: sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==} + + '@cspell/dict-kotlin@1.1.1': + resolution: {integrity: sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==} + + '@cspell/dict-latex@4.0.4': + resolution: {integrity: sha512-YdTQhnTINEEm/LZgTzr9Voz4mzdOXH7YX+bSFs3hnkUHCUUtX/mhKgf1CFvZ0YNM2afjhQcmLaR9bDQVyYBvpA==} + + '@cspell/dict-lorem-ipsum@4.0.5': + resolution: {integrity: sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==} + + '@cspell/dict-lua@4.0.8': + resolution: {integrity: sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==} + + '@cspell/dict-makefile@1.0.5': + resolution: {integrity: sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==} + + '@cspell/dict-markdown@2.0.14': + resolution: {integrity: sha512-uLKPNJsUcumMQTsZZgAK9RgDLyQhUz/uvbQTEkvF/Q4XfC1i/BnA8XrOrd0+Vp6+tPOKyA+omI5LRWfMu5K/Lw==} + peerDependencies: + '@cspell/dict-css': ^4.0.19 + '@cspell/dict-html': ^4.0.14 + '@cspell/dict-html-symbol-entities': ^4.0.5 + '@cspell/dict-typescript': ^3.2.3 + + '@cspell/dict-monkeyc@1.0.12': + resolution: {integrity: sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==} + + '@cspell/dict-node@5.0.8': + resolution: {integrity: sha512-AirZcN2i84ynev3p2/1NCPEhnNsHKMz9zciTngGoqpdItUb2bDt1nJBjwlsrFI78GZRph/VaqTVFwYikmncpXg==} + + '@cspell/dict-npm@5.2.29': + resolution: {integrity: sha512-ZAef8JpYmbuHFT1zekj/YyImLPvZevjECw663EmG5GPePyNo4AfH8Dd2fFhaOyQ3P5I5LrkAhGwypnOfUxcssw==} + + '@cspell/dict-php@4.1.1': + resolution: {integrity: sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==} + + '@cspell/dict-powershell@5.0.15': + resolution: {integrity: sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==} + + '@cspell/dict-public-licenses@2.0.15': + resolution: {integrity: sha512-cJEOs901H13Pfy0fl4dCD1U+xpWIMaEPq8MeYU83FfDZvellAuSo4GqWCripfIqlhns/L6+UZEIJSOZnjgy7Wg==} + + '@cspell/dict-python@4.2.25': + resolution: {integrity: sha512-hDdN0YhKgpbtZVRjQ2c8jk+n0wQdidAKj1Fk8w7KEHb3YlY5uPJ0mAKJk7AJKPNLOlILoUmN+HAVJz+cfSbWYg==} + + '@cspell/dict-r@2.1.1': + resolution: {integrity: sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==} + + '@cspell/dict-ruby@5.1.0': + resolution: {integrity: sha512-9PJQB3cfkBULrMLp5kSAcFPpzf8oz9vFN+QYZABhQwWkGbuzCIXSorHrmWSASlx4yejt3brjaWS57zZ/YL5ZQQ==} + + '@cspell/dict-rust@4.1.1': + resolution: {integrity: sha512-fXiXnZH0wOaEVTKFRNaz6TsUGhuB8dAT0ubYkDNzRQCaV5JGSOebGb1v2x5ZrOSVp+moxWM/vdBfiNU6KOEaFQ==} + + '@cspell/dict-scala@5.0.9': + resolution: {integrity: sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==} + + '@cspell/dict-shell@1.1.2': + resolution: {integrity: sha512-WqOUvnwcHK1X61wAfwyXq04cn7KYyskg90j4lLg3sGGKMW9Sq13hs91pqrjC44Q+lQLgCobrTkMDw9Wyl9nRFA==} + + '@cspell/dict-software-terms@5.1.20': + resolution: {integrity: sha512-TEk1xHvetTI4pv7Vzje1D322m6QEjaH2P6ucOOf6q7EJCppQIdC0lZSXkgHJAFU5HGSvEXSzvnVeW2RHW86ziQ==} + + '@cspell/dict-sql@2.2.1': + resolution: {integrity: sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==} + + '@cspell/dict-svelte@1.0.7': + resolution: {integrity: sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==} + + '@cspell/dict-swift@2.0.6': + resolution: {integrity: sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==} + + '@cspell/dict-terraform@1.1.3': + resolution: {integrity: sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==} + + '@cspell/dict-typescript@3.2.3': + resolution: {integrity: sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==} + + '@cspell/dict-vue@3.0.5': + resolution: {integrity: sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==} + + '@cspell/dict-zig@1.0.0': + resolution: {integrity: sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==} + + '@cspell/dynamic-import@9.6.0': + resolution: {integrity: sha512-Lkn82wyGj2ltxeYfH2bEjshdes1fx3ouYUZxeW5i6SBBvEVJoSmr43AygI8A317UMIQxVj59qVBorrtGYcRI1w==} + engines: {node: '>=20'} + + '@cspell/filetypes@9.6.0': + resolution: {integrity: sha512-CaWyk5j20H6sr+HCArtUY95jUQb7A/6W0GC4B4umnqoWvgqwR72duowLFa+w1K2C7tZg3GoV4Wf2cUn9jjt5FA==} + engines: {node: '>=20'} + + '@cspell/strong-weak-map@9.6.0': + resolution: {integrity: sha512-9g8LCLv/2RrprGeGnFAaBETWq7ESnBcoMbvgNu+vZE58iF+pbFvP0qGgKvVeKEEpc2LZhNuHLsUH37MUS6DOQg==} + engines: {node: '>=20'} + + '@cspell/url@9.6.0': + resolution: {integrity: sha512-257WOxh9vOYHAVgBNXRCdLEd+ldzlVbzcc9u+6DYoCDCNGe0OvOWOGsAfnUbMc9xEw48XgBlDYgOlPbjWGLOTg==} + engines: {node: '>=20'} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@stylistic/eslint-plugin@5.7.0': + resolution: {integrity: sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=9.0.0' + + '@swc/core-darwin-arm64@1.15.10': + resolution: {integrity: sha512-U72pGqmJYbjrLhMndIemZ7u9Q9owcJczGxwtfJlz/WwMaGYAV/g4nkGiUVk/+QSX8sFCAjanovcU1IUsP2YulA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.10': + resolution: {integrity: sha512-NZpDXtwHH083L40xdyj1sY31MIwLgOxKfZEAGCI8xHXdHa+GWvEiVdGiu4qhkJctoHFzAEc7ZX3GN5phuJcPuQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.10': + resolution: {integrity: sha512-ioieF5iuRziUF1HkH1gg1r93e055dAdeBAPGAk40VjqpL5/igPJ/WxFHGvc6WMLhUubSJI4S0AiZAAhEAp1jDg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.10': + resolution: {integrity: sha512-tD6BClOrxSsNus9cJL7Gxdv7z7Y2hlyvZd9l0NQz+YXzmTWqnfzLpg16ovEI7gknH2AgDBB5ywOsqu8hUgSeEQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.15.10': + resolution: {integrity: sha512-4uAHO3nbfbrTcmO/9YcVweTQdx5fN3l7ewwl5AEK4yoC4wXmoBTEPHAVdKNe4r9+xrTgd4BgyPsy0409OjjlMw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.15.10': + resolution: {integrity: sha512-W0h9ONNw1pVIA0cN7wtboOSTl4Jk3tHq+w2cMPQudu9/+3xoCxpFb9ZdehwCAk29IsvdWzGzY6P7dDVTyFwoqg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.15.10': + resolution: {integrity: sha512-XQNZlLZB62S8nAbw7pqoqwy91Ldy2RpaMRqdRN3T+tAg6Xg6FywXRKCsLh6IQOadr4p1+lGnqM/Wn35z5a/0Vw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.15.10': + resolution: {integrity: sha512-qnAGrRv5Nj/DATxAmCnJQRXXQqnJwR0trxLndhoHoxGci9MuguNIjWahS0gw8YZFjgTinbTxOwzatkoySihnmw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.10': + resolution: {integrity: sha512-i4X/q8QSvzVlaRtv1xfnfl+hVKpCfiJ+9th484rh937fiEZKxZGf51C+uO0lfKDP1FfnT6C1yBYwHy7FLBVXFw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.10': + resolution: {integrity: sha512-HvY8XUFuoTXn6lSccDLYFlXv1SU/PzYi4PyUqGT++WfTnbw/68N/7BdUZqglGRwiSqr0qhYt/EhmBpULj0J9rA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.10': + resolution: {integrity: sha512-udNofxftduMUEv7nqahl2nvodCiCDQ4Ge0ebzsEm6P8s0RC2tBM0Hqx0nNF5J/6t9uagFJyWIDjXy3IIWMHDJw==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.25': + resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@25.0.9': + resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==} + + '@typescript-eslint/eslint-plugin@8.53.1': + resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.53.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.53.1': + resolution: {integrity: sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.53.1': + resolution: {integrity: sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.53.1': + resolution: {integrity: sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.53.1': + resolution: {integrity: sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.53.1': + resolution: {integrity: sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.53.1': + resolution: {integrity: sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.53.1': + resolution: {integrity: sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.53.1': + resolution: {integrity: sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.53.1': + resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitruvius-labs/ts-predicate@7.0.0': + resolution: {integrity: sha512-lnWz0W2BeI/LZh6AGrOhC+SrQ74IeG2hUuT8BUXN9Q0nNj1cZ63e3a/uFd5/guqAqowQaNyHx4mP61CW2DybjQ==} + engines: {node: '>=18.0.0'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk-template@1.1.2: + resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==} + engines: {node: '>=14.16'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + clear-module@4.1.2: + resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==} + engines: {node: '>=8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + + comment-json@4.5.1: + resolution: {integrity: sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cspell-config-lib@9.6.0: + resolution: {integrity: sha512-5ztvheawkmFXNHGN82iOOntU3T5mmlQBP/plgoKdBZ6+lSYrOJLkOyqxYyi7MrUBDtWrXPzFllkBrPNRDlbX/A==} + engines: {node: '>=20'} + + cspell-dictionary@9.6.0: + resolution: {integrity: sha512-wW0m1kLrbK6bRY/GrLUGKUUJ1Z4ZUgIb8LD4zNaECcvGviv9V7VcR3mEwUip3ZjoHa3ClzEoWgQ9gXrtac80Wg==} + engines: {node: '>=20'} + + cspell-gitignore@9.6.0: + resolution: {integrity: sha512-8GfmJuRBBvibyPHnNE2wYJAiQ/ceDYLD1X1sUQaCyj6hPMR7ChJiVhFPtS11hMqkjZ46OBMYTMGWqO792L9fEQ==} + engines: {node: '>=20'} + hasBin: true + + cspell-glob@9.6.0: + resolution: {integrity: sha512-KmEbKN0qdEamsEYbkFu7zjLYfw3hMmn9kmeh94IHr2kq6vWq5vNP5l1BuqmrUeFZlbNd07vj08IKAZHYsoGheQ==} + engines: {node: '>=20'} + + cspell-grammar@9.6.0: + resolution: {integrity: sha512-jZVIM5/3eB9rWURDq+VXdYip+DmPuFzO+bqaRtzqT8w6YoOIGYbiIxdwvyyA9xdH7SmW8uqHJP5x4pzZju1lNQ==} + engines: {node: '>=20'} + hasBin: true + + cspell-io@9.6.0: + resolution: {integrity: sha512-wZuZzKOYIb698kVEINYjGaNFQu+AFZ945TORM3hapmPjez+vsHwl8m/pPpCHeGMpQtHMEDkX84AbQ7R55MRIwg==} + engines: {node: '>=20'} + + cspell-lib@9.6.0: + resolution: {integrity: sha512-m9rIv8hkQ3Dio4s80HQbM9cdxENcd6pS8j2AHWL50OSjJf3Xhw6/wMrIAGbwLHP15K6QZVU2eJ/abCzIJwjA4w==} + engines: {node: '>=20'} + + cspell-trie-lib@9.6.0: + resolution: {integrity: sha512-L7GSff5F9cF60QT78WsebVlb3sppi6jbvTHwsw7WF1jUN/ioAo7OzBYtYB7xkYeejcdVEpqfvf/ZOXPDp8x2Wg==} + engines: {node: '>=20'} + peerDependencies: + '@cspell/cspell-types': 9.6.0 + + cspell@9.6.0: + resolution: {integrity: sha512-Mpf0oT2KAHTIb3YPAXWhW64/4CZKW5Lka4j1YxCLK3jM3nenmIsY/ocrJvqCMF4+1eejRF0N55sT3XmrijI5YQ==} + engines: {node: '>=20'} + hasBin: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-perfectionist@5.3.1: + resolution: {integrity: sha512-v8kAP8TarQYqDC4kxr343ZNi++/oOlBnmWovsUZpbJ7A/pq1VHGlgsf/fDh4CdEvEstzkrc8NLvoVKtfpsC4oA==} + engines: {node: ^20.0.0 || >=22.0.0} + peerDependencies: + eslint: '>=8.45.0' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.0: + resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@11.1.0: + resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-equals@6.0.0: + resolution: {integrity: sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==} + engines: {node: '>=6.0.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensequence@8.0.8: + resolution: {integrity: sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==} + engines: {node: '>=20'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + natural-orderby@5.0.0: + resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parent-module@2.0.0: + resolution: {integrity: sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==} + engines: {node: '>=8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + engines: {node: '>= 18'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.53.1: + resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + xdg-basedir@5.1.0: + resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} + engines: {node: '>=12'} + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@cspell/cspell-bundled-dicts@9.6.0': + dependencies: + '@cspell/dict-ada': 4.1.1 + '@cspell/dict-al': 1.1.1 + '@cspell/dict-aws': 4.0.17 + '@cspell/dict-bash': 4.2.2 + '@cspell/dict-companies': 3.2.10 + '@cspell/dict-cpp': 7.0.2 + '@cspell/dict-cryptocurrencies': 5.0.5 + '@cspell/dict-csharp': 4.0.8 + '@cspell/dict-css': 4.0.19 + '@cspell/dict-dart': 2.3.2 + '@cspell/dict-data-science': 2.0.13 + '@cspell/dict-django': 4.1.6 + '@cspell/dict-docker': 1.1.17 + '@cspell/dict-dotnet': 5.0.11 + '@cspell/dict-elixir': 4.0.8 + '@cspell/dict-en-common-misspellings': 2.1.11 + '@cspell/dict-en-gb-mit': 3.1.16 + '@cspell/dict-en_us': 4.4.27 + '@cspell/dict-filetypes': 3.0.15 + '@cspell/dict-flutter': 1.1.1 + '@cspell/dict-fonts': 4.0.5 + '@cspell/dict-fsharp': 1.1.1 + '@cspell/dict-fullstack': 3.2.7 + '@cspell/dict-gaming-terms': 1.1.2 + '@cspell/dict-git': 3.0.7 + '@cspell/dict-golang': 6.0.26 + '@cspell/dict-google': 1.0.9 + '@cspell/dict-haskell': 4.0.6 + '@cspell/dict-html': 4.0.14 + '@cspell/dict-html-symbol-entities': 4.0.5 + '@cspell/dict-java': 5.0.12 + '@cspell/dict-julia': 1.1.1 + '@cspell/dict-k8s': 1.0.12 + '@cspell/dict-kotlin': 1.1.1 + '@cspell/dict-latex': 4.0.4 + '@cspell/dict-lorem-ipsum': 4.0.5 + '@cspell/dict-lua': 4.0.8 + '@cspell/dict-makefile': 1.0.5 + '@cspell/dict-markdown': 2.0.14(@cspell/dict-css@4.0.19)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.14)(@cspell/dict-typescript@3.2.3) + '@cspell/dict-monkeyc': 1.0.12 + '@cspell/dict-node': 5.0.8 + '@cspell/dict-npm': 5.2.29 + '@cspell/dict-php': 4.1.1 + '@cspell/dict-powershell': 5.0.15 + '@cspell/dict-public-licenses': 2.0.15 + '@cspell/dict-python': 4.2.25 + '@cspell/dict-r': 2.1.1 + '@cspell/dict-ruby': 5.1.0 + '@cspell/dict-rust': 4.1.1 + '@cspell/dict-scala': 5.0.9 + '@cspell/dict-shell': 1.1.2 + '@cspell/dict-software-terms': 5.1.20 + '@cspell/dict-sql': 2.2.1 + '@cspell/dict-svelte': 1.0.7 + '@cspell/dict-swift': 2.0.6 + '@cspell/dict-terraform': 1.1.3 + '@cspell/dict-typescript': 3.2.3 + '@cspell/dict-vue': 3.0.5 + '@cspell/dict-zig': 1.0.0 + + '@cspell/cspell-json-reporter@9.6.0': + dependencies: + '@cspell/cspell-types': 9.6.0 + + '@cspell/cspell-pipe@9.6.0': {} + + '@cspell/cspell-resolver@9.6.0': + dependencies: + global-directory: 4.0.1 + + '@cspell/cspell-service-bus@9.6.0': {} + + '@cspell/cspell-types@9.6.0': {} + + '@cspell/dict-ada@4.1.1': {} + + '@cspell/dict-al@1.1.1': {} + + '@cspell/dict-aws@4.0.17': {} + + '@cspell/dict-bash@4.2.2': + dependencies: + '@cspell/dict-shell': 1.1.2 + + '@cspell/dict-companies@3.2.10': {} + + '@cspell/dict-cpp@7.0.2': {} + + '@cspell/dict-cryptocurrencies@5.0.5': {} + + '@cspell/dict-csharp@4.0.8': {} + + '@cspell/dict-css@4.0.19': {} + + '@cspell/dict-dart@2.3.2': {} + + '@cspell/dict-data-science@2.0.13': {} + + '@cspell/dict-django@4.1.6': {} + + '@cspell/dict-docker@1.1.17': {} + + '@cspell/dict-dotnet@5.0.11': {} + + '@cspell/dict-elixir@4.0.8': {} + + '@cspell/dict-en-common-misspellings@2.1.11': {} + + '@cspell/dict-en-gb-mit@3.1.16': {} + + '@cspell/dict-en_us@4.4.27': {} + + '@cspell/dict-filetypes@3.0.15': {} + + '@cspell/dict-flutter@1.1.1': {} + + '@cspell/dict-fonts@4.0.5': {} + + '@cspell/dict-fsharp@1.1.1': {} + + '@cspell/dict-fullstack@3.2.7': {} + + '@cspell/dict-gaming-terms@1.1.2': {} + + '@cspell/dict-git@3.0.7': {} + + '@cspell/dict-golang@6.0.26': {} + + '@cspell/dict-google@1.0.9': {} + + '@cspell/dict-haskell@4.0.6': {} + + '@cspell/dict-html-symbol-entities@4.0.5': {} + + '@cspell/dict-html@4.0.14': {} + + '@cspell/dict-java@5.0.12': {} + + '@cspell/dict-julia@1.1.1': {} + + '@cspell/dict-k8s@1.0.12': {} + + '@cspell/dict-kotlin@1.1.1': {} + + '@cspell/dict-latex@4.0.4': {} + + '@cspell/dict-lorem-ipsum@4.0.5': {} + + '@cspell/dict-lua@4.0.8': {} + + '@cspell/dict-makefile@1.0.5': {} + + '@cspell/dict-markdown@2.0.14(@cspell/dict-css@4.0.19)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.14)(@cspell/dict-typescript@3.2.3)': + dependencies: + '@cspell/dict-css': 4.0.19 + '@cspell/dict-html': 4.0.14 + '@cspell/dict-html-symbol-entities': 4.0.5 + '@cspell/dict-typescript': 3.2.3 + + '@cspell/dict-monkeyc@1.0.12': {} + + '@cspell/dict-node@5.0.8': {} + + '@cspell/dict-npm@5.2.29': {} + + '@cspell/dict-php@4.1.1': {} + + '@cspell/dict-powershell@5.0.15': {} + + '@cspell/dict-public-licenses@2.0.15': {} + + '@cspell/dict-python@4.2.25': + dependencies: + '@cspell/dict-data-science': 2.0.13 + + '@cspell/dict-r@2.1.1': {} + + '@cspell/dict-ruby@5.1.0': {} + + '@cspell/dict-rust@4.1.1': {} + + '@cspell/dict-scala@5.0.9': {} + + '@cspell/dict-shell@1.1.2': {} + + '@cspell/dict-software-terms@5.1.20': {} + + '@cspell/dict-sql@2.2.1': {} + + '@cspell/dict-svelte@1.0.7': {} + + '@cspell/dict-swift@2.0.6': {} + + '@cspell/dict-terraform@1.1.3': {} + + '@cspell/dict-typescript@3.2.3': {} + + '@cspell/dict-vue@3.0.5': {} + + '@cspell/dict-zig@1.0.0': {} + + '@cspell/dynamic-import@9.6.0': + dependencies: + '@cspell/url': 9.6.0 + import-meta-resolve: 4.2.0 + + '@cspell/filetypes@9.6.0': {} + + '@cspell/strong-weak-map@9.6.0': {} + + '@cspell/url@9.6.0': {} + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': + dependencies: + eslint: 9.39.2 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@stylistic/eslint-plugin@5.7.0(eslint@9.39.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@typescript-eslint/types': 8.53.1 + eslint: 9.39.2 + eslint-visitor-keys: 5.0.0 + espree: 11.1.0 + estraverse: 5.3.0 + picomatch: 4.0.3 + + '@swc/core-darwin-arm64@1.15.10': + optional: true + + '@swc/core-darwin-x64@1.15.10': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.10': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.10': + optional: true + + '@swc/core-linux-arm64-musl@1.15.10': + optional: true + + '@swc/core-linux-x64-gnu@1.15.10': + optional: true + + '@swc/core-linux-x64-musl@1.15.10': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.10': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.10': + optional: true + + '@swc/core-win32-x64-msvc@1.15.10': + optional: true + + '@swc/core@1.15.10': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.10 + '@swc/core-darwin-x64': 1.15.10 + '@swc/core-linux-arm-gnueabihf': 1.15.10 + '@swc/core-linux-arm64-gnu': 1.15.10 + '@swc/core-linux-arm64-musl': 1.15.10 + '@swc/core-linux-x64-gnu': 1.15.10 + '@swc/core-linux-x64-musl': 1.15.10 + '@swc/core-win32-arm64-msvc': 1.15.10 + '@swc/core-win32-ia32-msvc': 1.15.10 + '@swc/core-win32-x64-msvc': 1.15.10 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@25.0.9': + dependencies: + undici-types: 7.16.0 + + '@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.53.1 + '@typescript-eslint/type-utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.53.1 + eslint: 9.39.2 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.53.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.53.1 + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.53.1 + debug: 4.4.3 + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.53.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) + '@typescript-eslint/types': 8.53.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.53.1': + dependencies: + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/visitor-keys': 8.53.1 + + '@typescript-eslint/tsconfig-utils@8.53.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.53.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.53.1': {} + + '@typescript-eslint/typescript-estree@8.53.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.53.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/visitor-keys': 8.53.1 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.53.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.53.1 + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.53.1': + dependencies: + '@typescript-eslint/types': 8.53.1 + eslint-visitor-keys: 4.2.1 + + '@vitruvius-labs/ts-predicate@7.0.0': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + array-timsort@1.0.3: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + callsites@3.1.0: {} + + chalk-template@1.1.2: + dependencies: + chalk: 5.6.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + clear-module@4.1.2: + dependencies: + parent-module: 2.0.0 + resolve-from: 5.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@14.0.2: {} + + comment-json@4.5.1: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + + concat-map@0.0.1: {} + + core-util-is@1.0.3: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cspell-config-lib@9.6.0: + dependencies: + '@cspell/cspell-types': 9.6.0 + comment-json: 4.5.1 + smol-toml: 1.6.0 + yaml: 2.8.2 + + cspell-dictionary@9.6.0: + dependencies: + '@cspell/cspell-pipe': 9.6.0 + '@cspell/cspell-types': 9.6.0 + cspell-trie-lib: 9.6.0(@cspell/cspell-types@9.6.0) + fast-equals: 6.0.0 + + cspell-gitignore@9.6.0: + dependencies: + '@cspell/url': 9.6.0 + cspell-glob: 9.6.0 + cspell-io: 9.6.0 + + cspell-glob@9.6.0: + dependencies: + '@cspell/url': 9.6.0 + picomatch: 4.0.3 + + cspell-grammar@9.6.0: + dependencies: + '@cspell/cspell-pipe': 9.6.0 + '@cspell/cspell-types': 9.6.0 + + cspell-io@9.6.0: + dependencies: + '@cspell/cspell-service-bus': 9.6.0 + '@cspell/url': 9.6.0 + + cspell-lib@9.6.0: + dependencies: + '@cspell/cspell-bundled-dicts': 9.6.0 + '@cspell/cspell-pipe': 9.6.0 + '@cspell/cspell-resolver': 9.6.0 + '@cspell/cspell-types': 9.6.0 + '@cspell/dynamic-import': 9.6.0 + '@cspell/filetypes': 9.6.0 + '@cspell/strong-weak-map': 9.6.0 + '@cspell/url': 9.6.0 + clear-module: 4.1.2 + cspell-config-lib: 9.6.0 + cspell-dictionary: 9.6.0 + cspell-glob: 9.6.0 + cspell-grammar: 9.6.0 + cspell-io: 9.6.0 + cspell-trie-lib: 9.6.0(@cspell/cspell-types@9.6.0) + env-paths: 3.0.0 + gensequence: 8.0.8 + import-fresh: 3.3.1 + resolve-from: 5.0.0 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + xdg-basedir: 5.1.0 + + cspell-trie-lib@9.6.0(@cspell/cspell-types@9.6.0): + dependencies: + '@cspell/cspell-types': 9.6.0 + + cspell@9.6.0: + dependencies: + '@cspell/cspell-json-reporter': 9.6.0 + '@cspell/cspell-pipe': 9.6.0 + '@cspell/cspell-types': 9.6.0 + '@cspell/dynamic-import': 9.6.0 + '@cspell/url': 9.6.0 + ansi-regex: 6.2.2 + chalk: 5.6.2 + chalk-template: 1.1.2 + commander: 14.0.2 + cspell-config-lib: 9.6.0 + cspell-dictionary: 9.6.0 + cspell-gitignore: 9.6.0 + cspell-glob: 9.6.0 + cspell-io: 9.6.0 + cspell-lib: 9.6.0 + fast-json-stable-stringify: 2.1.0 + flatted: 3.3.3 + semver: 7.7.3 + tinyglobby: 0.2.15 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + env-paths@3.0.0: {} + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escape-string-regexp@4.0.0: {} + + eslint-plugin-perfectionist@5.3.1(eslint@9.39.2)(typescript@5.9.3): + dependencies: + '@typescript-eslint/utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + natural-orderby: 5.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.0: {} + + eslint@9.39.2: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + espree@11.1.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 5.0.0 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-equals@6.0.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + gensequence@8.0.8: {} + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + + globals@14.0.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + ini@4.1.1: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + natural-orderby@5.0.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parent-module@2.0.0: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picomatch@4.0.3: {} + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + smol-toml@1.6.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.53.1(eslint@9.39.2)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-uri@3.1.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + xdg-basedir@5.1.0: {} + + yaml@2.8.2: {} + + yocto-queue@0.1.0: {} diff --git a/src/_index.mts b/src/_index.mts new file mode 100644 index 0000000..9537994 --- /dev/null +++ b/src/_index.mts @@ -0,0 +1,35 @@ +import { Engine } from "./engine.mjs"; +import { make_table, make_variable } from "./runtime.mjs"; +import { make_boolean } from "./runtime/make-boolean/make-boolean.mjs"; +import { make_number } from "./runtime/make-number/make-number.mjs"; +import { make_string } from "./runtime/make-string/make-string.mjs"; +import { compile } from "./compiler.mjs"; +import { std_lib } from "./lib.mjs"; +import { variable_to_string } from "./lib/variable-to-string/variable-to-string.mjs"; + +export * as lexer from "./lexer.mjs"; +export * as parser from "./parser.mjs"; +export * as ast from "./ast/_index.mjs"; +export * as opcode from "./opcode/_index.mjs"; +export * as runtime from "./runtime.mjs"; + +export type * from "./boundary/definition/_index.mjs"; +export * from "./create-binding.mjs"; + +export type * from "./variable/definition/interface/_index.mjs"; +export type * from "./variable/definition/type/_index.mjs"; +export * from "./variable/definition/enum/variable-kind.enum.mjs"; +export * from "./variable/nil.mjs"; +export * from "./variable/unwrap-variable.mjs"; + +export { + Engine, + compile, + std_lib as std_global, + make_boolean, + make_number, + make_string, + make_table, + make_variable, + variable_to_string as to_string, +}; diff --git a/src/ast.ts b/src/ast.ts deleted file mode 100644 index d6d16e0..0000000 --- a/src/ast.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import type { Token } from './lexer' - -export enum ValueKind { - NilLiteral, - NumberLiteral, - BooleanLiteral, - StringLiteral, - TableLiteral, - Function, - Variable, -} - -export interface LuaFunction { - parameters: Token[], - body: Chunk, -} - -export interface Value { - kind: ValueKind, - token: Token, - - number?: number, - boolean?: boolean, - string?: string, - table?: Map, - function?: LuaFunction, - identifier?: string, -} - -export enum ExpressionKind { - Value, - Call, - Index, - - Addition, - Subtract, - Multiplication, - Division, - FloorDivision, - Modulo, - Exponent, - Concat, - - BitAnd, - BitOr, - BitXOr, - BitNot, - BitShiftLeft, - BitShiftRight, - - Equals, - NotEquals, - LessThen, - LessThenEquals, - GreaterThen, - GreaterThenEquals, - And, - Or, - - Not, - Negate, - Length, -} - -export interface Expression { - kind: ExpressionKind, - token: Token, - - lhs?: Expression, - rhs?: Expression, - value?: Value, - expression?: Expression, - index?: Expression, - arguments?: Expression[], -} - -export interface Assignment { - local: boolean, - lhs: Expression[], - rhs: Expression[], - token: Token, -} - -export interface Local { - names: Token[], - token: Token, -} - -export interface IfElseBlock { - body: Chunk, - condition: Expression, - token: Token, -} - -export interface IfBlock { - condition: Expression, - body: Chunk, - else_if_bodies: IfElseBlock[], - else_body?: Chunk, - token: Token, -} - -export interface While { - condition: Expression, - body: Chunk, - token: Token, -} - -export interface For { - items: Token[], - itorator: Expression, - body: Chunk, - token: Token, -} - -export interface NumericFor { - index: Token, - start: Expression, - end: Expression, - step: Expression | undefined, - body: Chunk, -} - -export interface Repeat { - body: Chunk, - condition: Expression, - token: Token, -} - -export interface Do { - body: Chunk, - token: Token, -} - -export interface Return { - values: Expression[], - token: Token, -} - -export enum StatementKind { - Invalid, - Empty, - Expression, - Assignment, - Local, - If, - While, - For, - NumericFor, - Repeat, - Do, - Return, - Break, -} - -export interface Statement { - kind: StatementKind, - expression?: Expression, - assignment?: Assignment, - local?: Local, - if?: IfBlock, - while?: While, - for?: For, - numeric_for?: NumericFor, - repeat?: Repeat, - do?: Do, - return?: Return, -} - -export interface Chunk { - statements: Statement[], -} - diff --git a/src/ast/_index.mts b/src/ast/_index.mts new file mode 100644 index 0000000..9b306eb --- /dev/null +++ b/src/ast/_index.mts @@ -0,0 +1 @@ +export * from "./definition/_index.mjs"; diff --git a/src/ast/definition/_index.mts b/src/ast/definition/_index.mts new file mode 100644 index 0000000..833204c --- /dev/null +++ b/src/ast/definition/_index.mts @@ -0,0 +1,2 @@ +export type * from "./interface/_index.mjs"; +export * from "./enum/_index.mjs"; diff --git a/src/ast/definition/enum/_index.mts b/src/ast/definition/enum/_index.mts new file mode 100644 index 0000000..4e797b4 --- /dev/null +++ b/src/ast/definition/enum/_index.mts @@ -0,0 +1,3 @@ +export * from "./expression-kind.enum.mjs"; +export * from "./statement-kind.enum.mjs"; +export * from "./value-kind.enum.mjs"; diff --git a/src/ast/definition/enum/expression-kind.enum.mts b/src/ast/definition/enum/expression-kind.enum.mts new file mode 100644 index 0000000..dea3e49 --- /dev/null +++ b/src/ast/definition/enum/expression-kind.enum.mts @@ -0,0 +1,40 @@ +const ExpressionKind = { + Value: "value", + Call: "call", + Index: "index", + + Negate: "negate", + Addition: "addition", + Subtract: "subtract", + Multiplication: "multiplication", + Division: "division", + FloorDivision: "floor-division", + Modulo: "modulo", + Exponent: "exponent", + Concat: "concat", + + BitAnd: "bit-and", + BitOr: "bit-or", + BitXOr: "bit-xor", + BitNot: "bit-not", + BitShiftLeft: "bit-shift-left", + BitShiftRight: "bit-shift-right", + + Equals: "equals", + NotEquals: "not-equals", + LessThan: "less-than", + LessThanEquals: "less-than-equals", + GreaterThan: "greater-than", + GreaterThanEquals: "greater-than-equals", + + And: "and", + // eslint-disable-next-line id-length + Or: "or", + Not: "not", + + Length: "length", +} as const satisfies Record; + +type ExpressionKindEnum = typeof ExpressionKind[keyof typeof ExpressionKind]; + +export { ExpressionKind, type ExpressionKindEnum }; diff --git a/src/ast/definition/enum/statement-kind.enum.mts b/src/ast/definition/enum/statement-kind.enum.mts new file mode 100644 index 0000000..977a6e8 --- /dev/null +++ b/src/ast/definition/enum/statement-kind.enum.mts @@ -0,0 +1,21 @@ +const StatementKind = { + Invalid: "invalid", + Empty: "empty", + Expression: "expression", + Assignment: "assignment", + Local: "local", + // eslint-disable-next-line id-length + If: "if", + While: "while", + For: "for", + NumericFor: "numeric-for", + Repeat: "repeat", + // eslint-disable-next-line id-length + Do: "do", + Return: "return", + Break: "break", +} as const satisfies Record; + +type StatementKindEnum = typeof StatementKind[keyof typeof StatementKind]; + +export { StatementKind, type StatementKindEnum }; diff --git a/src/ast/definition/enum/value-kind.enum.mts b/src/ast/definition/enum/value-kind.enum.mts new file mode 100644 index 0000000..2f571d9 --- /dev/null +++ b/src/ast/definition/enum/value-kind.enum.mts @@ -0,0 +1,13 @@ +const ValueKind = { + NilLiteral: "nil-literal", + NumberLiteral: "number-literal", + BooleanLiteral: "boolean-literal", + StringLiteral: "string-literal", + TableLiteral: "table-literal", + FunctionLike: "function-like", + Variable: "variable", +} as const satisfies Record; + +type ValueKindEnum = typeof ValueKind[keyof typeof ValueKind]; + +export { ValueKind, type ValueKindEnum }; diff --git a/src/ast/definition/interface/_index.mts b/src/ast/definition/interface/_index.mts new file mode 100644 index 0000000..428b91b --- /dev/null +++ b/src/ast/definition/interface/_index.mts @@ -0,0 +1,15 @@ +export type * from "./assignment.interface.mjs"; +export type * from "./chunk.interface.mjs"; +export type * from "./do.interface.mjs"; +export type * from "./else-if-block.interface.mjs"; +export type * from "./expression.interface.mjs"; +export type * from "./for.interface.mjs"; +export type * from "./if-block.interface.mjs"; +export type * from "./local.interface.mjs"; +export type * from "./lua-function.interface.mjs"; +export type * from "./numeric-for.interface.mjs"; +export type * from "./repeat.interface.mjs"; +export type * from "./return.interface.mjs"; +export type * from "./statement.interface.mjs"; +export type * from "./value.interface.mjs"; +export type * from "./while.interface.mjs"; diff --git a/src/ast/definition/interface/assignment.interface.mts b/src/ast/definition/interface/assignment.interface.mts new file mode 100644 index 0000000..6ce4e9c --- /dev/null +++ b/src/ast/definition/interface/assignment.interface.mts @@ -0,0 +1,13 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface AssignmentInterface +{ + local: boolean; + lhs: Array; + rhs: Array; + token: TokenInterface; +} + +export type { AssignmentInterface }; + diff --git a/src/ast/definition/interface/chunk.interface.mts b/src/ast/definition/interface/chunk.interface.mts new file mode 100644 index 0000000..12a10f3 --- /dev/null +++ b/src/ast/definition/interface/chunk.interface.mts @@ -0,0 +1,8 @@ +import type { StatementInterface } from "./statement.interface.mjs"; + +interface ChunkInterface +{ + statements: Array; +} + +export type { ChunkInterface }; diff --git a/src/ast/definition/interface/do.interface.mts b/src/ast/definition/interface/do.interface.mts new file mode 100644 index 0000000..9b16b23 --- /dev/null +++ b/src/ast/definition/interface/do.interface.mts @@ -0,0 +1,10 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; + +interface DoInterface +{ + body: ChunkInterface; + token: TokenInterface; +} + +export type { DoInterface }; diff --git a/src/ast/definition/interface/else-if-block.interface.mts b/src/ast/definition/interface/else-if-block.interface.mts new file mode 100644 index 0000000..c76b077 --- /dev/null +++ b/src/ast/definition/interface/else-if-block.interface.mts @@ -0,0 +1,12 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface ElseIfBlockInterface +{ + body: ChunkInterface; + condition: ExpressionInterface; + token: TokenInterface; +} + +export type { ElseIfBlockInterface }; diff --git a/src/ast/definition/interface/expression.interface.mts b/src/ast/definition/interface/expression.interface.mts new file mode 100644 index 0000000..0243a5c --- /dev/null +++ b/src/ast/definition/interface/expression.interface.mts @@ -0,0 +1,20 @@ +import type { TokenStream } from "../../../lexer.mjs"; +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ExpressionKindEnum } from "../enum/expression-kind.enum.mjs"; +import type { ValueInterface } from "./value.interface.mjs"; + +interface ExpressionInterface +{ + // @TODO: Remove 'data' field + data?: never; + kind: ExpressionKindEnum; + token: TokenInterface | TokenStream; + lhs?: ExpressionInterface; + rhs?: ExpressionInterface; + value?: ValueInterface; + expression?: ExpressionInterface; + index?: ExpressionInterface; + arguments?: Array; +} + +export type { ExpressionInterface }; diff --git a/src/ast/definition/interface/for.interface.mts b/src/ast/definition/interface/for.interface.mts new file mode 100644 index 0000000..476b163 --- /dev/null +++ b/src/ast/definition/interface/for.interface.mts @@ -0,0 +1,13 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface ForInterface +{ + items: Array; + iterator: ExpressionInterface; + body: ChunkInterface; + token: TokenInterface; +} + +export type { ForInterface }; diff --git a/src/ast/definition/interface/if-block.interface.mts b/src/ast/definition/interface/if-block.interface.mts new file mode 100644 index 0000000..1c73079 --- /dev/null +++ b/src/ast/definition/interface/if-block.interface.mts @@ -0,0 +1,15 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; +import type { ElseIfBlockInterface } from "./else-if-block.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface IfBlockInterface +{ + condition: ExpressionInterface; + body: ChunkInterface; + else_if_bodies: Array; + else_body?: ChunkInterface | undefined; + token: TokenInterface; +} + +export type { IfBlockInterface }; diff --git a/src/ast/definition/interface/local.interface.mts b/src/ast/definition/interface/local.interface.mts new file mode 100644 index 0000000..80ac9f2 --- /dev/null +++ b/src/ast/definition/interface/local.interface.mts @@ -0,0 +1,10 @@ +import type { TokenStream } from "../../../lexer.mjs"; +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; + +interface LocalInterface +{ + names: Array; + token: TokenInterface; +} + +export type { LocalInterface }; diff --git a/src/ast/definition/interface/lua-function.interface.mts b/src/ast/definition/interface/lua-function.interface.mts new file mode 100644 index 0000000..f819cf6 --- /dev/null +++ b/src/ast/definition/interface/lua-function.interface.mts @@ -0,0 +1,10 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; + +interface LuaFunctionInterface +{ + parameters: Array; + body: ChunkInterface; +} + +export type { LuaFunctionInterface }; diff --git a/src/ast/definition/interface/numeric-for.interface.mts b/src/ast/definition/interface/numeric-for.interface.mts new file mode 100644 index 0000000..b6e43d0 --- /dev/null +++ b/src/ast/definition/interface/numeric-for.interface.mts @@ -0,0 +1,14 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface NumericForInterface +{ + index: TokenInterface; + start: ExpressionInterface; + end: ExpressionInterface; + step: ExpressionInterface | undefined; + body: ChunkInterface; +} + +export type { NumericForInterface }; diff --git a/src/ast/definition/interface/repeat.interface.mts b/src/ast/definition/interface/repeat.interface.mts new file mode 100644 index 0000000..a260fdf --- /dev/null +++ b/src/ast/definition/interface/repeat.interface.mts @@ -0,0 +1,12 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface RepeatInterface +{ + body: ChunkInterface; + condition: ExpressionInterface; + token: TokenInterface; +} + +export type { RepeatInterface }; diff --git a/src/ast/definition/interface/return.interface.mts b/src/ast/definition/interface/return.interface.mts new file mode 100644 index 0000000..4ad5020 --- /dev/null +++ b/src/ast/definition/interface/return.interface.mts @@ -0,0 +1,10 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface ReturnInterface +{ + values: Array; + token: TokenInterface; +} + +export type { ReturnInterface }; diff --git a/src/ast/definition/interface/statement.interface.mts b/src/ast/definition/interface/statement.interface.mts new file mode 100644 index 0000000..f32d795 --- /dev/null +++ b/src/ast/definition/interface/statement.interface.mts @@ -0,0 +1,28 @@ +import type { StatementKindEnum } from "../enum/statement-kind.enum.mjs"; +import type { AssignmentInterface } from "./assignment.interface.mjs"; +import type { DoInterface } from "./do.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; +import type { ForInterface } from "./for.interface.mjs"; +import type { IfBlockInterface } from "./if-block.interface.mjs"; +import type { LocalInterface } from "./local.interface.mjs"; +import type { NumericForInterface } from "./numeric-for.interface.mjs"; +import type { RepeatInterface } from "./repeat.interface.mjs"; +import type { ReturnInterface } from "./return.interface.mjs"; +import type { WhileInterface } from "./while.interface.mjs"; + +interface StatementInterface +{ + kind: StatementKindEnum; + expression?: ExpressionInterface | undefined; + assignment?: AssignmentInterface | undefined; + local?: LocalInterface | undefined; + if?: IfBlockInterface | undefined; + while?: WhileInterface | undefined; + for?: ForInterface | undefined; + numeric_for?: NumericForInterface | undefined; + repeat?: RepeatInterface | undefined; + do?: DoInterface | undefined; + return?: ReturnInterface | undefined; +} + +export type { StatementInterface }; diff --git a/src/ast/definition/interface/value.interface.mts b/src/ast/definition/interface/value.interface.mts new file mode 100644 index 0000000..a23211a --- /dev/null +++ b/src/ast/definition/interface/value.interface.mts @@ -0,0 +1,19 @@ +import type { TokenStream } from "../../../lexer.mjs"; +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ValueKindEnum } from "../enum/value-kind.enum.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; +import type { LuaFunctionInterface } from "./lua-function.interface.mjs"; + +interface ValueInterface +{ + kind: ValueKindEnum; + token: TokenInterface | TokenStream; + number?: number | undefined; + boolean?: boolean | undefined; + string?: string | undefined; + table?: Map | undefined; + function?: LuaFunctionInterface | undefined; + identifier?: string | undefined; +} + +export type { ValueInterface }; diff --git a/src/ast/definition/interface/while.interface.mts b/src/ast/definition/interface/while.interface.mts new file mode 100644 index 0000000..d2e0f3e --- /dev/null +++ b/src/ast/definition/interface/while.interface.mts @@ -0,0 +1,12 @@ +import type { TokenInterface } from "../../../lexer/definition/interface/token.interface.mjs"; +import type { ChunkInterface } from "./chunk.interface.mjs"; +import type { ExpressionInterface } from "./expression.interface.mjs"; + +interface WhileInterface +{ + condition: ExpressionInterface; + body: ChunkInterface; + token: TokenInterface; +} + +export type { WhileInterface }; diff --git a/src/boundary/_index.mts b/src/boundary/_index.mts new file mode 100644 index 0000000..9a7a2d4 --- /dev/null +++ b/src/boundary/_index.mts @@ -0,0 +1,2 @@ +export type * from "./definition/_index.mjs"; +export * from "./predicate/_index.mjs"; diff --git a/src/boundary/definition/_index.mts b/src/boundary/definition/_index.mts new file mode 100644 index 0000000..343d0f1 --- /dev/null +++ b/src/boundary/definition/_index.mts @@ -0,0 +1 @@ +export type * from "./type/_index.mjs"; diff --git a/src/boundary/definition/type/_index.mts b/src/boundary/definition/type/_index.mts new file mode 100644 index 0000000..d131d93 --- /dev/null +++ b/src/boundary/definition/type/_index.mts @@ -0,0 +1,8 @@ +export type * from "./matrix2d.type.mjs"; +export type * from "./native-function.type.mjs"; +export type * from "./table-array.type.mjs"; +export type * from "./table-input.type.mjs"; +export type * from "./table-map-key.type.mjs"; +export type * from "./table-map.type.mjs"; +export type * from "./table.type.mjs"; +export type * from "./value.type.mjs"; diff --git a/src/boundary/definition/type/function-reference.type.mts b/src/boundary/definition/type/function-reference.type.mts new file mode 100644 index 0000000..18f7b06 --- /dev/null +++ b/src/boundary/definition/type/function-reference.type.mts @@ -0,0 +1,6 @@ +interface FunctionReferenceType +{ + function_id: number | undefined; +} + +export type { FunctionReferenceType }; diff --git a/src/boundary/definition/type/matrix2d.type.mts b/src/boundary/definition/type/matrix2d.type.mts new file mode 100644 index 0000000..6cd965f --- /dev/null +++ b/src/boundary/definition/type/matrix2d.type.mts @@ -0,0 +1,3 @@ +type Matrix2D = Array>; + +export type { Matrix2D }; diff --git a/src/boundary/definition/type/native-function.type.mts b/src/boundary/definition/type/native-function.type.mts new file mode 100644 index 0000000..9f4ef37 --- /dev/null +++ b/src/boundary/definition/type/native-function.type.mts @@ -0,0 +1,7 @@ +import type { Awaitable } from "@vitruvius-labs/ts-predicate"; +import type { Engine } from "../../../engine.mjs"; +import type { Variable } from "../../../variable/definition/type/variable.type.mjs"; + +type NativeFunction = (engine: Engine, ...args: Array) => Awaitable>; + +export type { NativeFunction }; diff --git a/src/boundary/definition/type/table-array.type.mts b/src/boundary/definition/type/table-array.type.mts new file mode 100644 index 0000000..9133034 --- /dev/null +++ b/src/boundary/definition/type/table-array.type.mts @@ -0,0 +1,3 @@ +type TableArrayType = Array; + +export type { TableArrayType }; diff --git a/src/boundary/definition/type/table-input.type.mts b/src/boundary/definition/type/table-input.type.mts new file mode 100644 index 0000000..15c1bbd --- /dev/null +++ b/src/boundary/definition/type/table-input.type.mts @@ -0,0 +1,6 @@ +import type { TableMapKeyType } from "./table-map-key.type.mjs"; +import type { TableMapType } from "./table-map.type.mjs"; + +type TableInputType = Array | Record | TableMapType; + +export type { TableInputType }; diff --git a/src/boundary/definition/type/table-map-key.type.mts b/src/boundary/definition/type/table-map-key.type.mts new file mode 100644 index 0000000..4d5ec2d --- /dev/null +++ b/src/boundary/definition/type/table-map-key.type.mts @@ -0,0 +1,3 @@ +type TableMapKeyType = number | string; + +export type { TableMapKeyType }; diff --git a/src/boundary/definition/type/table-map.type.mts b/src/boundary/definition/type/table-map.type.mts new file mode 100644 index 0000000..37b2a65 --- /dev/null +++ b/src/boundary/definition/type/table-map.type.mts @@ -0,0 +1,5 @@ +import type { TableMapKeyType } from "./table-map-key.type.mjs"; + +type TableMapType = Map; + +export type { TableMapType }; diff --git a/src/boundary/definition/type/table.type.mts b/src/boundary/definition/type/table.type.mts new file mode 100644 index 0000000..92e9cb3 --- /dev/null +++ b/src/boundary/definition/type/table.type.mts @@ -0,0 +1,6 @@ +import type { TableArrayType } from "./table-array.type.mjs"; +import type { TableMapType } from "./table-map.type.mjs"; + +type TableType = TableArrayType | TableMapType; + +export type { TableType }; diff --git a/src/boundary/definition/type/value.type.mts b/src/boundary/definition/type/value.type.mts new file mode 100644 index 0000000..fdf8d8f --- /dev/null +++ b/src/boundary/definition/type/value.type.mts @@ -0,0 +1,15 @@ +import type { TableType } from "./table.type.mjs"; +import type { NativeFunction } from "./native-function.type.mjs"; +import type { FunctionReferenceType } from "./function-reference.type.mjs"; + +type ValueType = ( + | undefined + | boolean + | number + | string + | TableType + | NativeFunction + | FunctionReferenceType +); + +export type { ValueType }; diff --git a/src/boundary/predicate/_index.mts b/src/boundary/predicate/_index.mts new file mode 100644 index 0000000..b38ef4e --- /dev/null +++ b/src/boundary/predicate/_index.mts @@ -0,0 +1,2 @@ +export * from "./is-table-input-type.mjs"; +export * from "./is-table-map-key-type.mjs"; diff --git a/src/boundary/predicate/is-table-input-type.mts b/src/boundary/predicate/is-table-input-type.mts new file mode 100644 index 0000000..3f11f3e --- /dev/null +++ b/src/boundary/predicate/is-table-input-type.mts @@ -0,0 +1,9 @@ +import { isArray, isInstanceOf, isRecord, isUnion, unary } from "@vitruvius-labs/ts-predicate"; +import type { TableInputType } from "../definition/type/table-input.type.mjs"; + +function isTableInputType(input: unknown): input is TableInputType +{ + return isUnion(input, [isArray, isRecord, unary(isInstanceOf, Map)]); +} + +export { isTableInputType }; diff --git a/src/boundary/predicate/is-table-map-key-type.mts b/src/boundary/predicate/is-table-map-key-type.mts new file mode 100644 index 0000000..35daf95 --- /dev/null +++ b/src/boundary/predicate/is-table-map-key-type.mts @@ -0,0 +1,9 @@ +import { isNumber, isString, isUnion } from "@vitruvius-labs/ts-predicate"; +import type { TableMapKeyType } from "../definition/type/table-map-key.type.mjs"; + +function isTableMapKeyType(value: unknown): value is TableMapKeyType +{ + return isUnion(value, [isNumber, isString]); +} + +export { isTableMapKeyType }; diff --git a/src/compiler.mts b/src/compiler.mts new file mode 100644 index 0000000..3170cdb --- /dev/null +++ b/src/compiler.mts @@ -0,0 +1,859 @@ +import { VariableKind } from "./variable/definition/enum/variable-kind.enum.mjs"; +import { nil } from "./variable/nil.mjs"; +import { ValueKind } from "./ast/definition/enum/value-kind.enum.mjs"; +import { ExpressionKind } from "./ast/definition/enum/expression-kind.enum.mjs"; +import { StatementKind } from "./ast/definition/enum/statement-kind.enum.mjs"; +import { OpCode, type OpCodeEnum } from "./opcode/definition/enum/op-code.enum.mjs"; +import type { OpInterface } from "./opcode/definition/interface/op.interface.mjs"; +import type { ProgramInterface } from "./opcode/definition/interface/program.interface.mjs"; +import type { ValueInterface } from "./ast/definition/interface/value.interface.mjs"; +import type { ExpressionInterface } from "./ast/definition/interface/expression.interface.mjs"; +import type { AssignmentInterface } from "./ast/definition/interface/assignment.interface.mjs"; +import type { LocalInterface } from "./ast/definition/interface/local.interface.mjs"; +import type { IfBlockInterface } from "./ast/definition/interface/if-block.interface.mjs"; +import type { WhileInterface } from "./ast/definition/interface/while.interface.mjs"; +import type { ForInterface } from "./ast/definition/interface/for.interface.mjs"; +import type { NumericForInterface } from "./ast/definition/interface/numeric-for.interface.mjs"; +import type { RepeatInterface } from "./ast/definition/interface/repeat.interface.mjs"; +import type { DoInterface } from "./ast/definition/interface/do.interface.mjs"; +import type { ReturnInterface } from "./ast/definition/interface/return.interface.mjs"; +import type { ChunkInterface } from "./ast/definition/interface/chunk.interface.mjs"; +import type { TokenInterface } from "./lexer/definition/interface/token.interface.mjs"; +import type { TokenStream } from "./lexer.mjs"; +import type { DebugInterface } from "./lexer/definition/interface/debug.interface.mjs"; +import { make_boolean } from "./runtime/make-boolean/make-boolean.mjs"; +import { make_number } from "./runtime/make-number/make-number.mjs"; +import { make_string } from "./runtime/make-string/make-string.mjs"; +import { getDebug } from "./lexer/utility/get-debug.mjs"; + +function compile_function(chunk: ChunkInterface, token: TokenInterface | TokenStream, parameters: Array, functions: Array>): number +{ + const ops: Array = []; + + ops.push({ code: OpCode.ArgumentCount, arg: make_number(parameters.length), debug: token.debug }); + + for (const parameter of parameters.reverse()) + { + ops.push({ code: OpCode.MakeLocal, arg: make_string(parameter.data), debug: parameter.debug }); + ops.push({ code: OpCode.Store, arg: make_string(parameter.data), debug: parameter.debug }); + } + + ops.push(...compile_block(chunk, functions)); + ops.push({ code: OpCode.Push, arg: nil, debug: token.debug }); + ops.push({ code: OpCode.Return, arg: make_number(0), debug: token.debug }); + + functions.push(ops); + + return functions.length - 1; +} + +function compile_value(value: ValueInterface | undefined, functions: Array>): Array +{ + if (value === undefined) + { + throw new Error(); + } + + const debug = value.token.debug; + + switch (value.kind) + { + case ValueKind.NilLiteral: + return [{ code: OpCode.Push, arg: nil, debug: debug }]; + case ValueKind.BooleanLiteral: + return [{ code: OpCode.Push, arg: make_boolean(value.boolean ?? false), debug: debug }]; + case ValueKind.NumberLiteral: + return [{ code: OpCode.Push, arg: make_number(value.number ?? 0), debug: debug }]; + case ValueKind.StringLiteral: + return [{ code: OpCode.Push, arg: make_string(value.string ?? ""), debug: debug }]; + + case ValueKind.FunctionLike: + { + return [{ + code: OpCode.Push, + arg: { + data_type: VariableKind.Function, + function_id: compile_function( + value.function?.body ?? { statements: [] }, + value.token, + value.function?.parameters ?? [], + functions + ), + }, + debug: debug, + }]; + } + + case ValueKind.TableLiteral: + { + const output: Array = []; + + output.push({ code: OpCode.NewTable, debug: debug }); + + for (const [key, expression] of [...value.table?.entries() ?? []].reverse()) + { + output.push(...compile_expression(expression, functions)); + output.push(...compile_expression(key, functions)); + } + + output.push({ code: OpCode.StoreIndex, arg: make_number(value.table?.size ?? 0), debug: debug }); + + return output; + } + + case ValueKind.Variable: + { + return [{ + code: OpCode.Load, + arg: { data_type: VariableKind.String, string: value.identifier ?? "" }, + debug: debug, + }]; + } + } +} + +function compile_operation( + expression: ExpressionInterface, + operation: OpCodeEnum, + functions: Array> +): Array +{ + const { lhs, rhs } = expression; + + if (lhs === undefined || rhs === undefined) + { + throw new Error(); + } + + const ops: Array = []; + + ops.push(...compile_expression(rhs, functions)); + ops.push(...compile_expression(lhs, functions)); + + ops.push({ + code: operation, + debug: getDebug(expression.token), + }); + + return ops; +} + +function compile_call( + callable: ExpressionInterface | undefined, + args: Array | undefined, + functions: Array> +): Array +{ + if (callable === undefined || args === undefined) + { + throw new Error(); + } + + const debug: DebugInterface = getDebug(callable.token); + const ops: Array = []; + + for (const arg of args) + { + ops.push(...compile_expression(arg, functions)); + } + + ops.push(...compile_expression(callable, functions)); + ops.push({ code: OpCode.Push, arg: make_number(args.length), debug: debug }); + ops.push({ code: OpCode.Call, debug: debug }); + + return ops; +} + +function compile_index( + target: ExpressionInterface | undefined, + index: ExpressionInterface | undefined, + functions: Array> +): Array +{ + if (target === undefined || index === undefined) + { + throw new Error(); + } + + const ops: Array = []; + + ops.push(...compile_expression(index, functions)); + ops.push(...compile_expression(target, functions)); + ops.push({ code: OpCode.LoadIndex, debug: getDebug(target.token) }); + + return ops; +} + +function compile_unary_operation( + expression: ExpressionInterface | undefined, + operation: OpCodeEnum, + functions: Array> +): Array +{ + if (expression === undefined || expression.expression === undefined) + { + throw new Error(); + } + + const ops: Array = []; + + ops.push(...compile_expression(expression.expression, functions)); + ops.push({ code: operation, debug: getDebug(expression.token) }); + + return ops; +} + +function compile_expression(expression: ExpressionInterface | undefined, functions: Array>): Array +{ + if (expression === undefined) + { + throw new Error(); + } + + switch (expression.kind) + { + case ExpressionKind.Value: + return compile_value(expression.value, functions); + case ExpressionKind.Call: + return compile_call(expression.expression, expression.arguments, functions); + case ExpressionKind.Index: + return compile_index(expression.expression, expression.index, functions); + + case ExpressionKind.Addition: + return compile_operation(expression, OpCode.Add, functions); + case ExpressionKind.Subtract: + return compile_operation(expression, OpCode.Subtract, functions); + case ExpressionKind.Multiplication: + return compile_operation(expression, OpCode.Multiply, functions); + case ExpressionKind.Division: + return compile_operation(expression, OpCode.Divide, functions); + case ExpressionKind.FloorDivision: + return compile_operation(expression, OpCode.FloorDivide, functions); + case ExpressionKind.Modulo: + return compile_operation(expression, OpCode.Modulo, functions); + case ExpressionKind.Exponent: + return compile_operation(expression, OpCode.Exponent, functions); + case ExpressionKind.Concat: + return compile_operation(expression, OpCode.Concat, functions); + + case ExpressionKind.BitAnd: + return compile_operation(expression, OpCode.BitAnd, functions); + case ExpressionKind.BitOr: + return compile_operation(expression, OpCode.BitOr, functions); + case ExpressionKind.BitXOr: + return compile_operation(expression, OpCode.BitXOr, functions); + case ExpressionKind.BitShiftLeft: + return compile_operation(expression, OpCode.BitShiftLeft, functions); + case ExpressionKind.BitShiftRight: + return compile_operation(expression, OpCode.BitShiftRight, functions); + + case ExpressionKind.Equals: + return compile_operation(expression, OpCode.Equals, functions); + case ExpressionKind.NotEquals: + return compile_operation(expression, OpCode.NotEquals, functions); + case ExpressionKind.LessThan: + return compile_operation(expression, OpCode.LessThan, functions); + case ExpressionKind.LessThanEquals: + return compile_operation(expression, OpCode.LessThanEquals, functions); + case ExpressionKind.GreaterThan: + return compile_operation(expression, OpCode.GreaterThan, functions); + case ExpressionKind.GreaterThanEquals: + return compile_operation(expression, OpCode.GreaterThanEquals, functions); + case ExpressionKind.And: + return compile_operation(expression, OpCode.And, functions); + case ExpressionKind.Or: + return compile_operation(expression, OpCode.Or, functions); + + case ExpressionKind.Not: + return compile_unary_operation(expression, OpCode.Not, functions); + case ExpressionKind.Negate: + return compile_unary_operation(expression, OpCode.Negate, functions); + case ExpressionKind.Length: + return compile_unary_operation(expression, OpCode.Length, functions); + case ExpressionKind.BitNot: + return compile_unary_operation(expression, OpCode.BitNot, functions); + } +} + +function compile_assignment(assignment: AssignmentInterface | undefined, functions: Array>): Array +{ + if (assignment === undefined) + { + throw new Error(); + } + + const ops: Array = []; + const debug = assignment.token.debug; + + ops.push({ code: OpCode.StartStackChange, debug: debug }); + + for (const rhs of assignment.rhs) + { + ops.push(...compile_expression(rhs, functions)); + } + + ops.push({ code: OpCode.EndStackChange, arg: make_number(assignment.lhs.length), debug: debug }); + + for (const lhs of assignment.lhs) + { + const debug: DebugInterface = getDebug(lhs.token); + + switch (lhs.kind) + { + case ExpressionKind.Value: + { + if (lhs.value?.kind !== ValueKind.Variable) + { + throw new Error(); + } + + const identifier = make_string(lhs.value?.identifier ?? ""); + + if (assignment.local) + { + ops.push({ code: OpCode.MakeLocal, arg: identifier, debug: debug }); + } + + ops.push({ code: OpCode.Store, arg: identifier, debug: debug }); + break; + } + + case ExpressionKind.Index: + { + // FIXME: Throw error here if `assignment.local` is true. I think? + + ops.push(...compile_expression(lhs.expression, functions)); + ops.push({ code: OpCode.Swap, debug: debug }); + ops.push(...compile_expression(lhs.index, functions)); + ops.push({ code: OpCode.StoreIndex, debug: debug }); + ops.push({ code: OpCode.Pop, debug: debug }); + break; + } + + default: + throw new Error(); + } + } + + return ops; +} + +function compile_local(local: LocalInterface | undefined): Array +{ + if (local === undefined) + { + throw new Error(); + } + + return local.names.map( + (name): OpInterface => + { + return { + code: OpCode.MakeLocal, + arg: { + data_type: VariableKind.String, + string: name.data, + }, + debug: name.debug, + }; + } + ); +} + +function compile_inverted_conditional_jump(condition: ExpressionInterface | undefined, jump_by: number, functions: Array>): Array +{ + if (condition === undefined) + { + throw new Error(); + } + + const ops: Array = []; + const debug: DebugInterface = getDebug(condition.token); + + switch (condition.kind) + { + case ExpressionKind.And: + { + const rhs = compile_inverted_conditional_jump(condition.rhs, jump_by, functions); + + ops.push(...compile_conditional_jump(condition.lhs, rhs.length, functions)); + ops.push(...rhs); + break; + } + + case ExpressionKind.Or: + { + const rhs = compile_inverted_conditional_jump(condition.rhs, jump_by, functions); + + ops.push(...compile_inverted_conditional_jump(condition.lhs, rhs.length + jump_by, functions)); + ops.push(...rhs); + break; + } + + case ExpressionKind.Not: + { + ops.push(...compile_expression(condition.expression, functions)); + break; + } + + default: + { + ops.push(...compile_expression(condition, functions)); + ops.push({ code: OpCode.JumpIf, arg: make_number(jump_by), debug: debug }); + break; + } + } + + return ops; +} + +function compile_conditional_jump(condition: ExpressionInterface | undefined, jump_by: number, functions: Array>): Array +{ + if (condition === undefined) + { + throw new Error(); + } + + const ops: Array = []; + const debug: DebugInterface = getDebug(condition.token); + + switch (condition.kind) + { + case ExpressionKind.And: + { + const rhs = compile_conditional_jump(condition.rhs, jump_by, functions); + + ops.push(...compile_conditional_jump(condition.lhs, rhs.length + jump_by, functions)); + ops.push(...rhs); + break; + } + + case ExpressionKind.Or: + { + const rhs = compile_conditional_jump(condition.rhs, jump_by, functions); + + ops.push(...compile_inverted_conditional_jump(condition.lhs, rhs.length, functions)); + ops.push(...rhs); + break; + } + + case ExpressionKind.Not: + { + ops.push(...compile_inverted_conditional_jump(condition.expression, jump_by, functions)); + break; + } + + default: + { + ops.push(...compile_expression(condition, functions)); + ops.push({ code: OpCode.JumpIfNot, arg: make_number(jump_by), debug: debug }); + break; + } + } + + return ops; +} + +function compile_if(if_block: IfBlockInterface | undefined, functions: Array>): Array +{ + if (if_block === undefined) + { + throw new Error(); + } + + const else_chunk: Array = []; + + if (if_block.else_body !== undefined) + { + else_chunk.push(...compile_block(if_block.else_body, functions)); + } + + const if_else_chunks: Array> = []; + + for (const { body, condition, token } of if_block.else_if_bodies.reverse()) + { + const ops: Array = []; + const if_else_body = compile_block(body, functions); + + ops.push(...compile_conditional_jump(condition, if_else_body.length + 1, functions)); + ops.push(...if_else_body); + + const offset = if_else_chunks.reduce( + (acc, chunk) => + { + return chunk.length + acc; + }, + 0 + ) + else_chunk.length; + + ops.push({ code: OpCode.Jump, arg: make_number(offset), debug: token.debug }); + if_else_chunks.push(ops); + } + + const debug = if_block.token.debug; + const ops: Array = []; + const body = compile_block(if_block.body, functions); + + ops.push({ code: OpCode.StartBlock, debug: debug }); + ops.push(...compile_conditional_jump(if_block.condition, body.length + 1, functions)); + ops.push(...body); + + const offset = if_else_chunks.reduce( + (acc, chunk) => + { + return chunk.length + acc; + }, + 0 + ) + else_chunk.length; + + ops.push({ code: OpCode.Jump, arg: make_number(offset), debug: debug }); + + for (const if_else_chunk of if_else_chunks) + { + ops.push(...if_else_chunk); + } + + ops.push(...else_chunk); + ops.push({ code: OpCode.EndBlock, debug: debug }); + + return ops; +} + +function replace_breaks(code: Array, offset_from_end: number): void +{ + for (const [i, op] of code.entries()) + { + if (op.code === OpCode.Break) + { + const offset = code.length - i - 1 + offset_from_end; + + op.code = OpCode.Jump; + op.arg = make_number(offset); + } + } +} + +function compile_while(while_block: WhileInterface | undefined, functions: Array>): Array +{ + if (while_block === undefined) + { + throw new Error(); + } + + const debug = while_block.token.debug; + const ops: Array = []; + const body = compile_block(while_block.body, functions); + + replace_breaks(body, 1); + + ops.push({ code: OpCode.StartBlock, debug: debug }); + ops.push(...compile_conditional_jump(while_block.condition, body.length + 1, functions)); + ops.push(...body); + ops.push({ code: OpCode.Jump, arg: make_number(-ops.length - 1), debug: debug }); + ops.push({ code: OpCode.EndBlock, debug: debug }); + + return ops; +} + +function compile_for(for_block: ForInterface | undefined, functions: Array>): Array +{ + if (for_block === undefined) + { + throw new Error(); + } + + const ops: Array = []; + const body = compile_block(for_block.body, functions); + + replace_breaks(body, 1); + + const debug = for_block.token.debug; + + ops.push({ code: OpCode.StartBlock, debug: debug }); + ops.push({ code: OpCode.StartStackChange, debug: debug }); + ops.push(...compile_expression(for_block.iterator, functions)); + ops.push({ code: OpCode.EndStackChange, arg: make_number(3), debug: debug }); + + const after_creating_itorator = ops.length; + + ops.push({ code: OpCode.StartStackChange, debug: debug }); + ops.push({ code: OpCode.IterNext, debug: debug }); + ops.push({ code: OpCode.IterJumpIfDone, arg: make_number(body.length + for_block.items.length + 3), debug: debug }); + + ops.push({ code: OpCode.EndStackChange, arg: make_number(for_block.items.length), debug: debug }); + + for (const [i, item] of [...for_block.items].reverse().entries()) + { + if (i === for_block.items.length - 1) + { + ops.push({ code: OpCode.IterUpdateState, debug: debug }); + } + + ops.push({ code: OpCode.Store, arg: make_string(item.data), debug: item.debug }); + } + + ops.push(...body); + ops.push({ code: OpCode.Jump, arg: make_number(-ops.length + after_creating_itorator - 1), debug: debug }); + + ops.push({ code: OpCode.EndStackChange, arg: make_number(0), debug: debug }); + ops.push({ code: OpCode.Pop, arg: make_number(3), debug: debug }); + ops.push({ code: OpCode.EndBlock, debug: debug }); + + return ops; +} + +function compile_step(step: ExpressionInterface | undefined, functions: Array>): Array +{ + if (step === undefined) + { + return [{ code: OpCode.Push, arg: make_number(1), debug: { line: 0, column: 0 } }]; + } + + return compile_expression(step, functions); +} + +function compile_numeric_for(numeric_for_block: NumericForInterface | undefined, functions: Array>): Array +{ + if (numeric_for_block === undefined) + { + throw new Error(); + } + + const ops: Array = []; + const body = compile_block(numeric_for_block.body, functions); + const step = compile_step(numeric_for_block.step, functions); + const index = numeric_for_block.index.data; + const debug = numeric_for_block.index.debug; + + replace_breaks(body, step.length + 4); + + ops.push({ code: OpCode.StartBlock, debug: debug }); + ops.push(...compile_expression(numeric_for_block.start, functions)); + + const after_creating_itorator = ops.length; + + ops.push({ code: OpCode.Dup, debug: debug }); + ops.push(...compile_expression(numeric_for_block.end, functions)); + ops.push({ code: OpCode.NotEquals, debug: debug }); + ops.push({ code: OpCode.JumpIfNot, arg: make_number(body.length + step.length + 4), debug: debug }); + + ops.push({ code: OpCode.Store, arg: make_string(index), debug: debug }); + ops.push(...body); + ops.push({ code: OpCode.Load, arg: make_string(index), debug: debug }); + ops.push(...step); + ops.push({ code: OpCode.Add, debug: debug }); + ops.push({ code: OpCode.Jump, arg: make_number(-ops.length + after_creating_itorator - 1), debug: debug }); + + ops.push({ code: OpCode.Pop, debug: debug }); + ops.push({ code: OpCode.EndBlock, debug: debug }); + + return ops; +} + +function compile_repeat(repeat: RepeatInterface | undefined, functions: Array>): Array +{ + if (repeat === undefined) + { + throw new Error(); + } + + const ops: Array = []; + const debug = repeat.token.debug; + + ops.push({ code: OpCode.StartBlock, debug: debug }); + + ops.push(...compile_block(repeat.body, functions)); + ops.push(...compile_inverted_conditional_jump(repeat.condition, 1, functions)); + ops.push({ code: OpCode.Jump, arg: make_number(-ops.length), debug: debug }); + + ops.push({ code: OpCode.EndBlock, debug: debug }); + + return ops; +} + +function compile_do(do_block: DoInterface | undefined, functions: Array>): Array +{ + if (do_block === undefined) + { + throw new Error(); + } + + const ops: Array = []; + const debug = do_block.token.debug; + + ops.push({ code: OpCode.StartBlock, debug: debug }); + ops.push(...compile_block(do_block.body, functions)); + ops.push({ code: OpCode.EndBlock, debug: debug }); + + return ops; +} + +function compile_return(return_block: ReturnInterface | undefined, functions: Array>): Array +{ + if (return_block === undefined) + { + throw new Error(); + } + + const ops: Array = []; + + for (const value of return_block.values) + { + ops.push(...compile_expression(value, functions)); + } + + const debug = return_block.token.debug; + const return_count = return_block.values.length; + + ops.push({ code: OpCode.Return, arg: make_number(return_count), debug: debug }); + + return ops; +} + +interface ChunkResult +{ + code: Array; + has_last_expression: boolean; +} + +function compile_block(chunk: ChunkInterface, functions: Array>): Array +{ + const { code, has_last_expression } = compile_chunk(chunk, functions); + + if (has_last_expression) + { + code.push({ code: OpCode.Pop, debug: { line: 0, column: 0 } }); + } + + return code; +} + +function compile_chunk(chunk: ChunkInterface, functions: Array>): ChunkResult +{ + const ops = []; + let has_last_expression = false; + + for (const [index, statement] of chunk.statements.entries()) + { + // eslint-disable-next-line @ts/switch-exhaustiveness-check + switch (statement.kind) + { + case StatementKind.Empty: + break; + case StatementKind.Expression: + { + ops.push(...compile_expression(statement.expression, functions)); + + if (statement.expression === undefined) + { + break; + } + + const is_last_statement: boolean = index === chunk.statements.length - 1; + + if (is_last_statement) + { + has_last_expression = true; + break; + } + + ops.push({ + code: OpCode.Pop, + debug: getDebug(statement.expression.token), + }); + + break; + } + case StatementKind.Assignment: + ops.push(...compile_assignment(statement.assignment, functions)); + break; + case StatementKind.Local: + ops.push(...compile_local(statement.local)); + break; + case StatementKind.If: + ops.push(...compile_if(statement.if, functions)); + break; + case StatementKind.While: + ops.push(...compile_while(statement.while, functions)); + break; + case StatementKind.For: + ops.push(...compile_for(statement.for, functions)); + break; + case StatementKind.NumericFor: + ops.push(...compile_numeric_for(statement.numeric_for, functions)); + break; + case StatementKind.Repeat: + ops.push(...compile_repeat(statement.repeat, functions)); + break; + case StatementKind.Do: + ops.push(...compile_do(statement.do, functions)); + break; + case StatementKind.Return: + ops.push(...compile_return(statement.return, functions)); + break; + case StatementKind.Break: + ops.push({ + code: OpCode.Break, + debug: { line: 0, column: 0 }, + }); + + break; + } + } + + return { + code: ops, + has_last_expression: has_last_expression, + }; +} + +function link(code: Array, function_id: number, location: number): void +{ + for (const op of code) + { + if (op.arg?.data_type === VariableKind.Function + && op.arg?.function_id === function_id) + { + op.arg.function_id = location; + } + } +} + +export function compile(chunk: ChunkInterface, extend?: Array): ProgramInterface +{ + const ops = [...(extend ?? [])]; + const functions: Array> = []; + const { code, has_last_expression } = compile_chunk(chunk, functions); + + const function_locations: Array = []; + + for (const func of functions) + { + function_locations.push(ops.length); + ops.push(...func); + } + + for (const [id, location] of function_locations.entries()) + { + link(code, id, location); + } + + const start = ops.length; + + if (extend?.length ?? 0 > 0) + { + ops.push({ code: OpCode.Pop, debug: { line: 0, column: 0 } }); + } + + ops.push(...code); + + if (!has_last_expression) + { + ops.push({ code: OpCode.Push, arg: nil, debug: { line: 0, column: 0 } }); + } + + return { + code: ops, + start: start, + }; +} diff --git a/src/compiler.ts b/src/compiler.ts deleted file mode 100644 index c8883ab..0000000 --- a/src/compiler.ts +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import type { Chunk, Expression, Value } from './ast' -import type { IfBlock, While, For, NumericFor, Repeat, Do } from './ast' -import type { Op, Program } from './opcode' -import type { Token } from './lexer' -import type { Assignment, Local, Return } from './ast' - -import { StatementKind, ExpressionKind, ValueKind } from './ast' -import { OpCode } from './opcode' -import { DataType } from './runtime' -import { make_boolean, make_number, make_string, nil } from './runtime' - -function compile_function(chunk: Chunk, token: Token, parameters: Token[], functions: Op[][]): number -{ - const ops: Op[] = [] - ops.push({ code: OpCode.ArgumentCount, arg: make_number(parameters.length), debug: token.debug }) - for (const parameter of parameters.reverse()) - { - ops.push({ code: OpCode.MakeLocal, arg: make_string(parameter.data), debug: parameter.debug }) - ops.push({ code: OpCode.Store, arg: make_string(parameter.data), debug: parameter.debug }) - } - ops.push(...compile_block(chunk, functions)) - ops.push({ code: OpCode.Push, arg: nil, debug: token.debug }) - ops.push({ code: OpCode.Return, arg: make_number(0), debug: token.debug }) - - functions.push(ops) - return functions.length - 1 -} - -function compile_value(value: Value | undefined, functions: Op[][]): Op[] -{ - if (value == undefined) - throw new Error() - - const debug = value.token.debug - switch (value.kind) - { - case ValueKind.NilLiteral: - return [{ code: OpCode.Push, arg: nil, debug: debug }] - case ValueKind.BooleanLiteral: - return [{ code: OpCode.Push, arg: make_boolean(value.boolean ?? false), debug: debug }] - case ValueKind.NumberLiteral: - return [{ code: OpCode.Push, arg: make_number(value.number ?? 0), debug: debug }] - case ValueKind.StringLiteral: - return [{ code: OpCode.Push, arg: make_string(value.string ?? ''), debug: debug }] - - case ValueKind.Function: - { - return [{ - code: OpCode.Push, arg: { - data_type: DataType.Function, - function_id: compile_function( - value.function?.body ?? { statements: [] }, - value.token, - value.function?.parameters ?? [], - functions), - }, - debug: debug, - }] - } - - case ValueKind.TableLiteral: - { - const output: Op[] = [] - output.push({ code: OpCode.NewTable, debug: debug }) - - for (const [key, expression] of [...value.table?.entries() ?? []].reverse()) - { - output.push(...compile_expression(expression, functions)) - output.push(...compile_expression(key, functions)) - } - - output.push({ code: OpCode.StoreIndex, arg: make_number(value.table?.size ?? 0), debug: debug }) - return output - } - - case ValueKind.Variable: - { - return [{ - code: OpCode.Load, - arg: { data_type: DataType.String, string: value.identifier ?? '' }, - debug: debug, - }] - } - - default: - throw new Error() - } -} - -function compile_operation(expression: Expression, - operation: OpCode, - functions: Op[][]): Op[] -{ - const { lhs, rhs } = expression - if (lhs == undefined || rhs == undefined) - throw new Error() - - const ops: Op[] = [] - ops.push(...compile_expression(rhs, functions)) - ops.push(...compile_expression(lhs, functions)) - ops.push({ code: operation, debug: expression.token.debug }) - return ops -} - -function compile_call(func: Expression | undefined, - args: Expression[] | undefined, - functions: Op[][]): Op[] -{ - if (func == undefined || args == undefined) - throw new Error() - - const debug = func.token.debug - const ops: Op[] = [] - for (const arg of args) - ops.push(...compile_expression(arg, functions)) - ops.push(...compile_expression(func, functions)) - ops.push({ code: OpCode.Push, arg: make_number(args.length), debug: debug }) - ops.push({ code: OpCode.Call, debug: debug }) - return ops -} - -function compile_index(target: Expression | undefined, - index: Expression | undefined, - functions: Op[][]): Op[] -{ - if (target == undefined || index == undefined) - throw new Error() - - const ops: Op[] = [] - ops.push(...compile_expression(index, functions)) - ops.push(...compile_expression(target, functions)) - ops.push({ code: OpCode.LoadIndex, debug: target.token.debug }) - return ops -} - -function compile_unary_operation(expression: Expression | undefined, - operation: OpCode, - functions: Op[][]): Op[] - -{ - if (expression == undefined || expression.expression == undefined) - throw new Error() - - const ops: Op[] = [] - ops.push(...compile_expression(expression.expression, functions)) - ops.push({ code: operation, debug: expression.token.debug }) - return ops -} - -function compile_expression(expression: Expression | undefined, functions: Op[][]): Op[] -{ - if (expression == undefined) - throw new Error() - - switch (expression.kind) - { - case ExpressionKind.Value: - return compile_value(expression.value, functions) - case ExpressionKind.Call: - return compile_call(expression.expression, expression.arguments, functions) - case ExpressionKind.Index: - return compile_index(expression.expression, expression.index, functions) - - case ExpressionKind.Addition: - return compile_operation(expression, OpCode.Add, functions) - case ExpressionKind.Subtract: - return compile_operation(expression, OpCode.Subtract, functions) - case ExpressionKind.Multiplication: - return compile_operation(expression, OpCode.Multiply, functions) - case ExpressionKind.Division: - return compile_operation(expression, OpCode.Divide, functions) - case ExpressionKind.FloorDivision: - return compile_operation(expression, OpCode.FloorDivide, functions) - case ExpressionKind.Modulo: - return compile_operation(expression, OpCode.Modulo, functions) - case ExpressionKind.Exponent: - return compile_operation(expression, OpCode.Exponent, functions) - case ExpressionKind.Concat: - return compile_operation(expression, OpCode.Concat, functions) - - case ExpressionKind.BitAnd: - return compile_operation(expression, OpCode.BitAnd, functions) - case ExpressionKind.BitOr: - return compile_operation(expression, OpCode.BitOr, functions) - case ExpressionKind.BitXOr: - return compile_operation(expression, OpCode.BitXOr, functions) - case ExpressionKind.BitShiftLeft: - return compile_operation(expression, OpCode.BitShiftLeft, functions) - case ExpressionKind.BitShiftRight: - return compile_operation(expression, OpCode.BitShiftRight, functions) - - case ExpressionKind.Equals: - return compile_operation(expression, OpCode.Equals, functions) - case ExpressionKind.NotEquals: - return compile_operation(expression, OpCode.NotEquals, functions) - case ExpressionKind.LessThen: - return compile_operation(expression, OpCode.LessThen, functions) - case ExpressionKind.LessThenEquals: - return compile_operation(expression, OpCode.LessThenEquals, functions) - case ExpressionKind.GreaterThen: - return compile_operation(expression, OpCode.GreaterThen, functions) - case ExpressionKind.GreaterThenEquals: - return compile_operation(expression, OpCode.GreaterThenEquals, functions) - case ExpressionKind.And: - return compile_operation(expression, OpCode.And, functions) - case ExpressionKind.Or: - return compile_operation(expression, OpCode.Or, functions) - - case ExpressionKind.Not: - return compile_unary_operation(expression, OpCode.Not, functions) - case ExpressionKind.Negate: - return compile_unary_operation(expression, OpCode.Negate, functions) - case ExpressionKind.Length: - return compile_unary_operation(expression, OpCode.Length, functions) - case ExpressionKind.BitNot: - return compile_unary_operation(expression, OpCode.BitNot, functions) - - default: - throw new Error() - } -} - -function compile_assignment(assignment: Assignment | undefined, functions: Op[][]): Op[] -{ - if (assignment == undefined) - throw new Error() - - const ops: Op[] = [] - const debug = assignment.token.debug - ops.push({ code: OpCode.StartStackChange, debug: debug }) - for (const rhs of assignment.rhs) - ops.push(...compile_expression(rhs, functions)) - ops.push({ code: OpCode.EndStackChange, arg: make_number(assignment.lhs.length), debug: debug }) - - for (const lhs of assignment.lhs) - { - const debug = lhs.token.debug - switch (lhs.kind) - { - case ExpressionKind.Value: - { - if (lhs.value?.kind != ValueKind.Variable) - throw new Error() - - const identifier = make_string(lhs.value?.identifier ?? '') - if (assignment.local) - ops.push({ code: OpCode.MakeLocal, arg: identifier, debug: debug }) - ops.push({ code: OpCode.Store, arg: identifier, debug: debug }) - break - } - - case ExpressionKind.Index: - { - // FIXME: Throw error here if `assignment.local` is true. I think? - - ops.push(...compile_expression(lhs.expression, functions)) - ops.push({ code: OpCode.Swap, debug: debug }) - ops.push(...compile_expression(lhs.index, functions)) - ops.push({ code: OpCode.StoreIndex, debug: debug }) - ops.push({ code: OpCode.Pop, debug: debug }) - break - } - - default: - throw new Error() - } - } - - return ops -} - -function compile_local(local: Local | undefined): Op[] -{ - if (local == undefined) - throw new Error() - - return local.names - .map(name => ({ - code: OpCode.MakeLocal, - arg: { - data_type: DataType.String, - string: name.data, - }, - debug: name.debug, - })) -} - -function compile_inverted_conditional_jump(condition: Expression | undefined, jump_by: number, functions: Op[][]): Op[] -{ - if (condition == undefined) - throw new Error() - - const ops: Op[] = [] - const debug = condition.token.debug - switch (condition.kind) - { - case ExpressionKind.And: - { - const rhs = compile_inverted_conditional_jump(condition.rhs, jump_by, functions) - ops.push(...compile_conditional_jump(condition.lhs, rhs.length, functions)) - ops.push(...rhs) - break - } - - case ExpressionKind.Or: - { - const rhs = compile_inverted_conditional_jump(condition.rhs, jump_by, functions) - ops.push(...compile_inverted_conditional_jump(condition.lhs, rhs.length + jump_by, functions)) - ops.push(...rhs) - break - } - - case ExpressionKind.Not: - { - ops.push(...compile_expression(condition.expression, functions)) - break - } - - default: - { - ops.push(...compile_expression(condition, functions)) - ops.push({ code: OpCode.JumpIf, arg: make_number(jump_by), debug: debug }) - break - } - } - - return ops -} - -function compile_conditional_jump(condition: Expression | undefined, jump_by: number, functions: Op[][]): Op[] -{ - if (condition == undefined) - throw new Error() - - const ops: Op[] = [] - const debug = condition.token.debug - switch (condition.kind) - { - case ExpressionKind.And: - { - const rhs = compile_conditional_jump(condition.rhs, jump_by, functions) - ops.push(...compile_conditional_jump(condition.lhs, rhs.length + jump_by, functions)) - ops.push(...rhs) - break - } - - case ExpressionKind.Or: - { - const rhs = compile_conditional_jump(condition.rhs, jump_by, functions) - ops.push(...compile_inverted_conditional_jump(condition.lhs, rhs.length, functions)) - ops.push(...rhs) - break - } - - case ExpressionKind.Not: - { - ops.push(...compile_inverted_conditional_jump(condition.expression, jump_by, functions)) - break - } - - default: - { - ops.push(...compile_expression(condition, functions)) - ops.push({ code: OpCode.JumpIfNot, arg: make_number(jump_by), debug: debug }) - break - } - } - - return ops -} - -function compile_if(if_block: IfBlock | undefined, functions: Op[][]): Op[] -{ - if (if_block == undefined) - throw new Error() - - const else_chunk: Op[] = [] - if (if_block.else_body != undefined) - else_chunk.push(...compile_block(if_block.else_body, functions)) - - const if_else_chunks: Op[][] = [] - for (const { body, condition, token } of if_block.else_if_bodies.reverse()) - { - const ops: Op[] = [] - const if_else_body = compile_block(body, functions) - ops.push(...compile_conditional_jump(condition, if_else_body.length + 1, functions)) - ops.push(...if_else_body) - - const offset = if_else_chunks.reduce((acc, chunk) => chunk.length + acc, 0) + else_chunk.length - ops.push({ code: OpCode.Jump, arg: make_number(offset), debug: token.debug }) - if_else_chunks.push(ops) - } - - const debug = if_block.token.debug - const ops: Op[] = [] - const body = compile_block(if_block.body, functions) - ops.push({ code: OpCode.StartBlock, debug: debug }) - ops.push(...compile_conditional_jump(if_block.condition, body.length + 1, functions)) - ops.push(...body) - - const offset = if_else_chunks.reduce((acc, chunk) => chunk.length + acc, 0) + else_chunk.length - ops.push({ code: OpCode.Jump, arg: make_number(offset), debug: debug }) - for (const if_else_chunk of if_else_chunks) - ops.push(...if_else_chunk) - ops.push(...else_chunk) - ops.push({ code: OpCode.EndBlock, debug: debug }) - - return ops -} - -function replace_breaks(code: Op[], offset_from_end: number) -{ - for (const [i, op] of code.entries()) - { - if (op.code == OpCode.Break) - { - const offset = code.length - i - 1 + offset_from_end - op.code = OpCode.Jump - op.arg = make_number(offset) - } - } -} - -function compile_while(while_block: While | undefined, functions: Op[][]): Op[] -{ - if (while_block == undefined) - throw new Error() - - const debug = while_block.token.debug - const ops: Op[] = [] - const body = compile_block(while_block.body, functions) - replace_breaks(body, 1) - - ops.push({ code: OpCode.StartBlock, debug: debug }) - ops.push(...compile_conditional_jump(while_block.condition, body.length + 1, functions)) - ops.push(...body) - ops.push({ code: OpCode.Jump, arg: make_number(-ops.length - 1), debug: debug }) - ops.push({ code: OpCode.EndBlock, debug: debug }) - return ops -} - -function compile_for(for_block: For | undefined, functions: Op[][]): Op[] -{ - if (for_block == undefined) - throw new Error() - - const ops: Op[] = [] - const body = compile_block(for_block.body, functions) - replace_breaks(body, 1) - - const debug = for_block.token.debug - ops.push({ code: OpCode.StartBlock, debug: debug }) - ops.push({ code: OpCode.StartStackChange, debug: debug }) - ops.push(...compile_expression(for_block.itorator, functions)) - ops.push({ code: OpCode.EndStackChange, arg: make_number(3), debug: debug }) - - const after_creating_itorator = ops.length - ops.push({ code: OpCode.StartStackChange, debug: debug }) - ops.push({ code: OpCode.IterNext, debug: debug }) - ops.push({ code: OpCode.IterJumpIfDone, arg: make_number(body.length + for_block.items.length + 3), debug: debug }) - - ops.push({ code: OpCode.EndStackChange, arg: make_number(for_block.items.length), debug: debug }) - for (const [i, item] of [...for_block.items].reverse().entries()) - { - if (i == for_block.items.length - 1) - ops.push({ code: OpCode.IterUpdateState, debug: debug }) - ops.push({ code: OpCode.Store, arg: make_string(item.data), debug: item.debug }) - } - ops.push(...body) - ops.push({ code: OpCode.Jump, arg: make_number(-ops.length + after_creating_itorator - 1), debug: debug }) - - ops.push({ code: OpCode.EndStackChange, arg: make_number(0), debug: debug }) - ops.push({ code: OpCode.Pop, arg: make_number(3), debug: debug }) - ops.push({ code: OpCode.EndBlock, debug: debug }) - return ops -} - -function compile_step(step: Expression | undefined, functions: Op[][]): Op[] -{ - if (step == undefined) - return [{ code: OpCode.Push, arg: make_number(1), debug: { line: 0, column: 0 } }] - - return compile_expression(step, functions) -} - -function compile_numeric_for(numeric_for_block: NumericFor | undefined, functions: Op[][]): Op[] -{ - if (numeric_for_block == undefined) - throw new Error() - - const ops: Op[] = [] - const body = compile_block(numeric_for_block.body, functions) - const step = compile_step(numeric_for_block.step, functions) - const index = numeric_for_block.index.data - const debug = numeric_for_block.index.debug - replace_breaks(body, step.length + 4) - - ops.push({ code: OpCode.StartBlock, debug: debug }) - ops.push(...compile_expression(numeric_for_block.start, functions)) - - const after_creating_itorator = ops.length - ops.push({ code: OpCode.Dup, debug: debug }) - ops.push(...compile_expression(numeric_for_block.end, functions)) - ops.push({ code: OpCode.NotEquals, debug: debug }) - ops.push({ code: OpCode.JumpIfNot, arg: make_number(body.length + step.length + 4), debug: debug }) - - ops.push({ code: OpCode.Store, arg: make_string(index), debug: debug }) - ops.push(...body) - ops.push({ code: OpCode.Load, arg: make_string(index), debug: debug }) - ops.push(...step) - ops.push({ code: OpCode.Add, debug: debug }) - ops.push({ code: OpCode.Jump, arg: make_number(-ops.length + after_creating_itorator - 1), debug: debug }) - - ops.push({ code: OpCode.Pop, debug: debug }) - ops.push({ code: OpCode.EndBlock, debug: debug }) - return ops -} - -function compile_repeat(repeat: Repeat | undefined, functions: Op[][]): Op[] -{ - if (repeat == undefined) - throw new Error() - - const ops: Op[] = [] - const debug = repeat.token.debug - ops.push({ code: OpCode.StartBlock, debug: debug }) - - ops.push(...compile_block(repeat.body, functions)) - ops.push(...compile_inverted_conditional_jump(repeat.condition, 1, functions)) - ops.push({ code: OpCode.Jump, arg: make_number(-ops.length), debug: debug }) - - ops.push({ code: OpCode.EndBlock, debug: debug }) - return ops -} - -function compile_do(do_block: Do | undefined, functions: Op[][]): Op[] -{ - if (do_block == undefined) - throw new Error() - - const ops: Op[] = [] - const debug = do_block.token.debug - ops.push({ code: OpCode.StartBlock, debug: debug }) - ops.push(...compile_block(do_block.body, functions)) - ops.push({ code: OpCode.EndBlock, debug: debug }) - return ops -} - -function compile_return(return_block: Return | undefined, functions: Op[][]): Op[] -{ - if (return_block == undefined) - throw new Error() - - const ops: Op[] = [] - for (const value of return_block.values) - ops.push(...compile_expression(value, functions)) - - const debug = return_block.token.debug - const return_count = return_block.values.length - ops.push({ code: OpCode.Return, arg: make_number(return_count), debug: debug }) - return ops -} - -interface ChunkResult -{ - code: Op[] - has_last_expression: boolean, -} - -function compile_block(chunk: Chunk, functions: Op[][]): Op[] -{ - const { code, has_last_expression } = compile_chunk(chunk, functions) - if (has_last_expression) - code.push({ code: OpCode.Pop, debug: { line: 0, column: 0 } }) - - return code -} - -function compile_chunk(chunk: Chunk, functions: Op[][]): ChunkResult -{ - const ops = [] - let has_last_expression = false - - for (const [index, statement] of chunk.statements.entries()) - { - const is_last_statement = (index == chunk.statements.length - 1) - switch (statement.kind) - { - case StatementKind.Empty: - break - case StatementKind.Expression: - ops.push(...compile_expression(statement.expression, functions)) - if (statement.expression == undefined) - break - - if (is_last_statement) - has_last_expression = true - else - ops.push({ code: OpCode.Pop, debug: statement.expression.token.debug }) - break - case StatementKind.Assignment: - ops.push(...compile_assignment(statement.assignment, functions)) - break - case StatementKind.Local: - ops.push(...compile_local(statement.local)) - break - case StatementKind.If: - ops.push(...compile_if(statement.if, functions)) - break - case StatementKind.While: - ops.push(...compile_while(statement.while, functions)) - break - case StatementKind.For: - ops.push(...compile_for(statement.for, functions)) - break - case StatementKind.NumericFor: - ops.push(...compile_numeric_for(statement.numeric_for, functions)) - break - case StatementKind.Repeat: - ops.push(...compile_repeat(statement.repeat, functions)) - break - case StatementKind.Do: - ops.push(...compile_do(statement.do, functions)) - break - case StatementKind.Return: - ops.push(...compile_return(statement.return, functions)) - break - case StatementKind.Break: - ops.push({ code: OpCode.Break, debug: { line: 0, column: 0 } }) - break - } - } - - return { - code: ops, - has_last_expression: has_last_expression, - } -} - -function link(code: Op[], function_id: number, location: number) -{ - for (const op of code) - { - if (op.arg?.data_type == DataType.Function && - op.arg?.function_id == function_id) - { - op.arg.function_id = location - } - } -} - -export function compile(chunk: Chunk, extend?: Op[]): Program -{ - const ops = [...(extend ?? [])] - const functions: Op[][] = [] - const { code, has_last_expression } = compile_chunk(chunk, functions) - - const function_locations: number[] = [] - for (const func of functions) - { - function_locations.push(ops.length) - ops.push(...func) - } - - for (const [id, location] of function_locations.entries()) - link(code, id, location) - - const start = ops.length - if (extend?.length ?? 0 > 0) - ops.push({ code: OpCode.Pop, debug: { line: 0, column: 0 } }) - ops.push(...code) - if (!has_last_expression) - ops.push({ code: OpCode.Push, arg: nil, debug: { line: 0, column: 0 } }) - - return { - code: ops, - start: start, - } -} - diff --git a/src/create-binding.mts b/src/create-binding.mts new file mode 100644 index 0000000..076cbb4 --- /dev/null +++ b/src/create-binding.mts @@ -0,0 +1,98 @@ +import { assertArray, assertDefined, isNullish } from "@vitruvius-labs/ts-predicate"; +import type { Engine } from "./engine.mjs"; +import { make_variable } from "./runtime.mjs"; +import { VariableKind } from "./variable/definition/enum/variable-kind.enum.mjs"; +import type { Variable } from "./variable/definition/type/variable.type.mjs"; +import type { NativeFunction } from "./boundary/definition/type/native-function.type.mjs"; +import { VariableUnwrapUtility } from "./variable/unwrap-variable.mjs"; +import type { VariableNativeFunction } from "./variable/definition/interface/variable-native-function.interface.mjs"; +import { handle_error } from "./create-binding/handle-error/handle-error.mjs"; + +export const ParameterOptionEnum = { + REQUIRED: "required", + OPTIONAL: "optional", + VARIADIC: "variadic", +} as const satisfies Record; + +export type ParameterOptionEnum = typeof ParameterOptionEnum[keyof typeof ParameterOptionEnum]; + +export interface ParameterDescriptorInterface +{ + test: (value: unknown) => asserts value is T; + option?: ParameterOptionEnum; +} + +function sanitize_parameters(input: Array, descriptors: Array): Array +{ + const output: Array = []; + + for (let i = 0; i < descriptors.length; ++i) + { + const descriptor: ParameterDescriptorInterface | undefined = descriptors.at(i); + + assertDefined(descriptor); + + const option: ParameterOptionEnum = descriptor.option ?? ParameterOptionEnum.REQUIRED; + + if (option === ParameterOptionEnum.VARIADIC) + { + const variadic_args: Array = input.slice(i); + + assertArray(variadic_args, descriptor.test); + + output.push(...variadic_args); + + break; + } + + const parameter_value: unknown = input.at(i); + + if (isNullish(parameter_value)) + { + if (option === ParameterOptionEnum.REQUIRED) + { + throw new Error(`Missing required parameter at index ${i.toFixed(0)}.`); + } + + output.push(undefined); + + continue; + } + + // @ts-expect-error: We cannot know which type is expected + descriptor.test(parameter_value); + + output.push(parameter_value); + } + + return output; +} + +export function make_function(callable: (...args: Array) => unknown, parameters_descriptor: Array): VariableNativeFunction +{ + // @ts-expect-error: engine is unused for now. + const proxy_function: NativeFunction = async (engine: Engine, ...args: Array): Promise> => + { + try + { + const unwrapped_args: Array = args.map(VariableUnwrapUtility.unwrap); + const sanitized_args: Array = sanitize_parameters(unwrapped_args, parameters_descriptor); + const result: unknown = await callable(...sanitized_args); + const variable: Variable = make_variable(result); + + return [variable]; + } + catch (error: unknown) + { + handle_error(error, callable); + } + }; + + // Bypass readonly constraint + Object.defineProperty(proxy_function, "name", { value: callable.name }); + + return { + data_type: VariableKind.NativeFunction, + native_function: proxy_function, + }; +} diff --git a/src/create-binding/handle-error/handle-error.mts b/src/create-binding/handle-error/handle-error.mts new file mode 100644 index 0000000..9150082 --- /dev/null +++ b/src/create-binding/handle-error/handle-error.mts @@ -0,0 +1,19 @@ +import { ValidationError, isInstanceOf } from "@vitruvius-labs/ts-predicate"; +import { RuntimeError } from "../../runtime-error.mjs"; + +function handle_error(error: unknown, callable: (...args: Array) => unknown): never +{ + if (isInstanceOf(error, RuntimeError) || isInstanceOf(error, ValidationError)) + { + throw error; + } + + if (callable.name === "") + { + throw new RuntimeError("An error occurred during native anonymous function execution.", { cause: error }); + } + + throw new RuntimeError(`An error occurred during native function ${callable.name} execution.`, { cause: error }); +} + +export { handle_error }; diff --git a/src/engine.mts b/src/engine.mts new file mode 100644 index 0000000..de63a50 --- /dev/null +++ b/src/engine.mts @@ -0,0 +1,965 @@ +import { assertUnion, unary } from "@vitruvius-labs/ts-predicate"; + +import { make_table } from "./runtime.mjs"; +import { TokenStream } from "./lexer.mjs"; +import { parse } from "./parser.mjs"; +import { compile } from "./compiler.mjs"; +import { optimize_chunk } from "./optimizer.mjs"; +import * as std from "./lib.mjs"; +import { VariableKind, type VariableKindEnum } from "./variable/definition/enum/variable-kind.enum.mjs"; +import type { Variable } from "./variable/definition/type/variable.type.mjs"; +import { RuntimeError } from "./runtime-error.mjs"; +import { assertVariableKind } from "./variable/predicate/assert-variable-kind.mjs"; +import { isNil } from "./variable/predicate/is-nil.mjs"; +import { nil } from "./variable/nil.mjs"; +import { isVariableKind } from "./variable/predicate/is-variable-kind.mjs"; +import type { VariableTableMapType } from "./variable/definition/type/variable-table-map.type.mjs"; +import type { NativeFunction } from "./boundary/definition/type/native-function.type.mjs"; +import type { VariableFunction } from "./variable/definition/interface/variable-function.interface.mjs"; +import type { VariableNativeFunction } from "./variable/definition/interface/variable-native-function.interface.mjs"; +import { equals } from "./variable/equals.mjs"; +import { assertVariable } from "./variable/predicate/assert-variable.mjs"; +import { OpCode } from "./opcode/definition/enum/op-code.enum.mjs"; +import { op_code_name } from "./opcode/op-code-name/op-code-name.mjs"; +import type { OpInterface } from "./opcode/definition/interface/op.interface.mjs"; +import { index } from "./engine/index/index.mjs"; +import type { LuaOptionsInterface } from "./engine/interface/lua-options.interface.mjs"; +import { table_size } from "./lib/table-size/table-size.mjs"; +import { variable_to_string } from "./lib/variable-to-string/variable-to-string.mjs"; +import { make_boolean } from "./runtime/make-boolean/make-boolean.mjs"; +import { make_number } from "./runtime/make-number/make-number.mjs"; +import { make_string } from "./runtime/make-string/make-string.mjs"; + +function is_true(val: Variable | undefined): boolean +{ + if (isNil(val)) + { + return false; + } + + if (isVariableKind(val, VariableKind.Boolean)) + { + return val.boolean; + } + + return true; +} + +export class Engine +{ + private program: Array; + private readonly globals: VariableTableMapType; + private start_ip: number = 0; + + private ip: number = 0; + private stack: Array = []; + private locals_stack: Array> = []; + private locals_capture: Array> = []; + private call_stack: Array = []; + private assign_height_stack: Array = []; + + private error: Error | undefined; + + public constructor( + script?: string, + globals?: VariableTableMapType + ) + { + this.program = []; + this.globals = globals ?? std.std_lib(); + this.error = undefined; + this.reset(); + + if (script !== undefined) + { + const result = this.load(script); + + if (result !== undefined) + { + this.error = result; + } + } + } + + load(chunk: string): undefined | Error + { + const stream: TokenStream = new TokenStream(); + + stream.feed(chunk); + + const ast = parse(stream); + + if (ast instanceof Error) + { + return ast; + } + + optimize_chunk(ast); + const program = compile(ast, this.program); + + this.program = program.code; + this.ip = program.start; + this.start_ip = program.start; + + return undefined; + } + + bytecode(): Array + { + if (this.error !== undefined) + { + return [this.error.message]; + } + + const output = []; + + for (const [i, op] of this.program.entries()) + { + const arg = op.arg !== undefined ? variable_to_string(op.arg) : ""; + + if (i === this.ip) + { + output.push(`* ${i} ${op_code_name(op.code)} ${arg}`); + } + else + { + output.push(`${i} ${op_code_name(op.code)} ${arg}`); + } + } + + return output; + } + + global(name: string): Variable | undefined + { + return this.globals.get(name); + } + + set(name: string, variable: Variable): void + { + this.globals.set(name, variable); + } + + define(name: string, func: NativeFunction): void + { + this.globals.set(name, { + data_type: VariableKind.NativeFunction, + native_function: func, + }); + } + + define_table(name: string, table: VariableTableMapType): void + { + this.globals.set(name, { + data_type: VariableKind.Table, + table: table, + }); + } + + reset(): void + { + this.ip = this.start_ip; + this.stack = []; + this.locals_stack = []; + this.locals_capture = []; + this.call_stack = []; + this.assign_height_stack = []; + + this.locals_stack.push(new Map()); + } + + async call(func: Variable, ...args: Array): Promise | Error> + { + if (isVariableKind(func, VariableKind.NativeFunction)) + { + return await this.call_native_function(func.native_function, ...args); + } + + assertUnion(func, [unary(assertVariableKind, VariableKind.Function), unary(assertVariableKind, VariableKind.NativeFunction)]); + + const old_stack = this.stack; + const old_call_stack = this.call_stack; + const old_ip = this.ip; + const old_locals_stack = this.locals_stack; + + this.stack = []; + this.call_stack = []; + this.locals_stack = [...func.locals ?? []]; + this.ip = func.function_id ?? this.ip; + + for (const arg of args) + { + this.stack.push(arg); + } + + this.stack.push(make_number(args.length)); + this.locals_stack.push(new Map()); + + const result = await this.run(); + const return_values = this.stack; + + this.stack = old_stack; + this.call_stack = old_call_stack; + this.locals_stack = old_locals_stack; + this.ip = old_ip; + + if (result instanceof Error) + { + return result; + } + + return return_values; + } + + async run_for_steps(steps: number, options?: LuaOptionsInterface): Promise + { + if (this.error !== undefined) + { + return this.error; + } + + if (options?.locals !== undefined) + { + this.locals_stack.push(options.locals); + } + + let step_count = 0; + + while (this.ip < this.program.length) + { + const result = await this.step(options); + + if (result !== undefined) + { + return result; + } + + step_count = step_count + 1; + + if (step_count >= steps) + { + return undefined; + } + } + + return this.stack_get(0); + } + + async run(options?: LuaOptionsInterface): Promise + { + const result = await this.run_for_steps(1000, options); + + if (result === undefined) + { + return new Error("Program ran for too long"); + } + + return result; + } + + raise_error(message: string): void + { + const op = this.program.at(this.ip - 1); + + this.error = this.runtime_error(op, message); + } + + private stack_get(index: number): Variable + { + if (index < 0) + { + return this.stack[this.stack.length + index] ?? nil; + } + + return this.stack[index] ?? nil; + } + + private stack_pop_maybe(): Variable + { + return this.stack.pop() ?? nil; + } + + private stack_pop(): Variable + { + const value = this.stack.pop(); + + assertVariable(value); + + return value; + } + + private stack_pop_kind(kind: K): Variable & { data_type: K } + { + const value = this.stack.pop(); + + assertVariableKind(value, kind); + + return value; + } + + private async call_native_function(native_function: NativeFunction, ...args: Array): Promise | Error> + { + const results = await native_function(this, ...args); + + if (this.error !== undefined) + { + const error = this.error; + + this.error = undefined; + + return error; + } + + return results; + } + + private operation(op: (x: number, y: number) => number): void + { + const x = this.stack_pop_kind(VariableKind.Number); + const y = this.stack_pop_kind(VariableKind.Number); + + this.stack.push(make_number(op(x.number, y.number))); + } + + private compare(op: (x: number, y: number) => boolean): void + { + const x = this.stack_pop_kind(VariableKind.Number); + const y = this.stack_pop_kind(VariableKind.Number); + + this.stack.push(make_boolean(op(x.number, y.number))); + } + + private force_stack_height(expected: number, got: number): void + { + for (let i = got; i < expected; ++i) + { + this.stack.push(nil); + } + + for (let i = expected; i < got; ++i) + { + this.stack.pop(); + } + } + + private runtime_error(op: OpInterface | undefined, message: string): never + { + if (op === undefined) + { + throw new RuntimeError(message); + } + + throw new RuntimeError(message, {}, op.debug); + } + + private async run_instruction(op: OpInterface): Promise + { + const { code, arg } = op; + + switch (code) + { + case OpCode.Pop: + { + const count = isVariableKind(arg, VariableKind.Number) ? arg.number : 1; + + this.stack.splice(this.stack.length - count, count); + break; + } + + case OpCode.Dup: + { + const count = isVariableKind(arg, VariableKind.Number) ? arg.number : 1; + const items = this.stack.splice(this.stack.length - count, count); + + this.stack.push(...items, ...items); + break; + } + + case OpCode.Swap: + { + const x = this.stack.splice(this.stack.length - 2, 1); + + this.stack.push(...x); + break; + } + + case OpCode.IterUpdateState: + { + this.stack[this.stack.length - 2] = this.stack_get(-1); + break; + } + + case OpCode.IterNext: + { + const state = this.stack_get(-1); + const control = this.stack_get(-2); + const iter = this.stack_get(-3); + + if (isVariableKind(iter, VariableKind.NativeFunction)) + { + const result = await this.call_native_function(iter.native_function, control, state); + + if (result instanceof Error) + { + return result; + } + + this.stack.push(...result); + break; + } + + this.stack.push(control, state, make_number(2)); + this.call_stack.push(this.ip); + this.locals_stack.push(new Map()); + this.locals_capture = iter.locals ?? []; + + if (isVariableKind(iter, VariableKind.Function) && iter.function_id !== undefined) + { + this.ip = iter.function_id; + } + + break; + } + + case OpCode.IterJumpIfDone: + { + if (isVariableKind(arg, VariableKind.Number) && isNil(this.stack_get(-1))) + { + this.ip = this.ip + arg.number; + } + + break; + } + + case OpCode.Add: + this.operation( + (x, y) => + { + return x + y; + } + ); + + break; + case OpCode.Subtract: + this.operation( + (x, y) => + { + return x - y; + } + ); + + break; + case OpCode.Multiply: + this.operation( + (x, y) => + { + return x * y; + } + ); + + break; + case OpCode.Divide: + this.operation( + (x, y) => + { + return x / y; + } + ); + + break; + case OpCode.FloorDivide: + this.operation( + (x, y) => + { + return Math.floor(x / y); + } + ); + + break; + case OpCode.Modulo: + this.operation( + (x, y) => + { + return x % y; + } + ); + + break; + case OpCode.Exponent: + this.operation( + (x, y) => + { + return Math.pow(x, y); + } + ); + + break; + + case OpCode.LessThan: + this.compare( + (x, y) => + { + return x < y; + } + ); + + break; + case OpCode.LessThanEquals: + this.compare( + (x, y) => + { + return x <= y; + } + ); + + break; + case OpCode.GreaterThan: + this.compare( + (x, y) => + { + return x > y; + } + ); + + break; + case OpCode.GreaterThanEquals: + this.compare( + (x, y) => + { + return x >= y; + } + ); + + break; + + case OpCode.BitAnd: + this.operation( + (x, y) => + { + return x & y; + } + ); + + break; + case OpCode.BitOr: + this.operation( + (x, y) => + { + return x | y; + } + ); + + break; + case OpCode.BitXOr: + this.operation( + (x, y) => + { + return x ^ y; + } + ); + + break; + case OpCode.BitShiftLeft: + this.operation( + (x, y) => + { + return x << y; + } + ); + + break; + case OpCode.BitShiftRight: + this.operation( + (x, y) => + { + return x >> y; + } + ); + + break; + + case OpCode.Concat: + { + const x = this.stack_pop(); + const y = this.stack_pop(); + + const result = variable_to_string(x) + variable_to_string(y); + + this.stack.push(make_string(result)); + break; + } + + case OpCode.Equals: + { + const x = this.stack_pop(); + const y = this.stack_pop(); + + this.stack.push(make_boolean(equals(x, y))); + break; + } + + case OpCode.NotEquals: + { + const x = this.stack_pop(); + const y = this.stack_pop(); + + this.stack.push(make_boolean(!equals(x, y))); + break; + } + + case OpCode.And: + { + const x = this.stack_pop(); + const y = this.stack_pop(); + + const result = is_true(x) ? y : x; + + this.stack.push(result); + break; + } + + case OpCode.Or: + { + const x = this.stack_pop(); + const y = this.stack_pop(); + + const result = is_true(x) ? x : y; + + this.stack.push(result); + break; + } + + case OpCode.Not: + this.stack.push(make_boolean(!is_true(this.stack_pop()))); + break; + + case OpCode.BitNot: + this.stack.push(make_number(~this.stack_pop_kind(VariableKind.Number).number)); + break; + + case OpCode.Negate: + this.stack.push(make_number(-this.stack_pop_kind(VariableKind.Number).number)); + break; + + case OpCode.IsNotNil: + this.stack.push(make_boolean(!isNil(this.stack_pop()))); + break; + + case OpCode.Jump: + if (isVariableKind(arg, VariableKind.Number)) + { + this.ip = this.ip + arg.number; + } + + break; + + case OpCode.JumpIfNot: + if (isVariableKind(arg, VariableKind.Number) && !is_true(this.stack_pop())) + { + this.ip = this.ip + arg.number; + } + + break; + + case OpCode.JumpIf: + if (isVariableKind(arg, VariableKind.Number) && is_true(this.stack_pop())) + { + this.ip = this.ip + arg.number; + } + + break; + + case OpCode.MakeLocal: + const last_locals = this.locals_stack.at(-1); + + if (last_locals) + { + last_locals.set(isVariableKind(arg, VariableKind.String) ? arg.string : "", nil); + } + + break; + + case OpCode.NewTable: + this.stack.push(make_table()); + break; + + case OpCode.StartBlock: + this.locals_stack.push(new Map()); + break; + + case OpCode.EndBlock: + this.locals_stack.pop(); + break; + + case OpCode.Length: + { + const variable = this.stack_pop_maybe(); + + switch (variable.data_type) + { + case VariableKind.String: + this.stack.push(make_number(variable.string.length)); + break; + case VariableKind.Table: + this.stack.push(make_number(table_size(variable))); + break; + + default: + this.runtime_error(op, `Attempt to get length of a ${variable.data_type} value`); + } + + break; + } + + case OpCode.Return: + { + this.ip = this.call_stack.pop() ?? this.program.length; + this.locals_stack = this.locals_stack.slice(0, this.call_stack.pop()); + this.locals_capture = []; + break; + } + + case OpCode.LoadIndex: + { + const table = this.stack_pop_maybe(); + + if (isNil(table)) + { + this.runtime_error(op, "Attempt to index a nil value"); + } + + assertVariableKind(table, VariableKind.Table); + + const i_var = this.stack_pop_maybe(); + + if (isNil(i_var)) + { + this.runtime_error(op, "Attempt to index with a nil value"); + } + + const i = index(i_var); + + if (i === undefined) + { + this.runtime_error(op, "Invalid index, must be a number or string"); + } + + this.stack.push(table.table.get(i) ?? nil); + break; + } + + case OpCode.StoreIndex: + { + const count = isVariableKind(arg, VariableKind.Number) ? arg.number : 1; + const table = this.stack_get(-1 - count * 2); + + assertVariableKind(table, VariableKind.Table); + + for (let i = 0; i < count; ++i) + { + const key = index(this.stack_pop_maybe()); + const value = this.stack_pop_maybe(); + + if (key === undefined) + { + return this.runtime_error(op, "Invalid key, must be a number or string"); + } + + table.table.set(key, value); + } + + break; + } + + case OpCode.Store: + { + const name = isVariableKind(arg, VariableKind.String) ? arg.string : ""; + const value = this.stack_pop_maybe(); + const local = [...this.locals_capture, ...this.locals_stack].findLast( + (x) => + { + return x.has(name); + } + ); + + if (local !== undefined) + { + local.set(name, value); + } + else + { + this.globals.set(name, value); + } + + break; + } + + case OpCode.Push: + { + if (this.locals_stack.length > 0 && isVariableKind(arg, VariableKind.Function)) + { + arg.locals = [...this.locals_stack]; + } + + this.stack.push(arg ?? nil); + break; + } + + case OpCode.Load: + { + const name = isVariableKind(arg, VariableKind.String) ? arg.string : ""; + const local = [...this.locals_capture, ...this.locals_stack] + .map( + (x) => + { + return x.get(name); + } + ) + .findLast( + (x) => + { + return !isNil(x); + } + ); + + const global = this.globals.get(name); + + this.stack.push(local ?? global ?? nil); + break; + } + + case OpCode.Call: + { + const x = this.stack_pop_maybe(); + const count = isVariableKind(x, VariableKind.Number) ? x.number : 0; + const func_var = this.stack_pop_maybe(); + + switch (func_var.data_type) + { + case VariableKind.NativeFunction: + { + const args = this.stack.splice(this.stack.length - count, count); + + if (func_var.native_function !== undefined) + { + const result = await this.call_native_function(func_var.native_function, ...args); + + if (result instanceof Error) + { + return result; + } + + this.stack.push(...result); + } + + if (this.error !== undefined) + { + const error = this.error; + + this.error = undefined; + + return error; + } + + break; + } + + case VariableKind.Function: + { + this.stack.push(make_number(count)); + this.call_stack.push(this.locals_stack.length, this.ip); + this.locals_stack.push(new Map()); + this.locals_capture = func_var.locals ?? []; + this.ip = func_var.function_id ?? this.ip; + break; + } + + default: + { + return this.runtime_error(op, `Object of type '${func_var.data_type}' is not callable`); + } + } + + break; + } + + case OpCode.ArgumentCount: + { + const x = this.stack_pop_maybe(); + const got = isVariableKind(x, VariableKind.Number) ? x.number : 0; + const expected = isVariableKind(arg, VariableKind.Number) ? arg.number : 0; + + this.force_stack_height(expected, got); + break; + } + + case OpCode.StartStackChange: + { + this.assign_height_stack.push(this.stack.length); + break; + } + + case OpCode.EndStackChange: + { + const got = this.stack.length - (this.assign_height_stack.pop() ?? 0); + const expected = isVariableKind(arg, VariableKind.Number) ? arg.number : 0; + + this.force_stack_height(expected, got); + break; + } + } + + return undefined; + } + + async step(options?: LuaOptionsInterface): Promise + { + if (this.error !== undefined) + { + return this.error; + } + + if (this.ip >= this.program.length) + { + return undefined; + } + + const op = this.program[this.ip]; + + ++this.ip; + + if (op === undefined) + { + this.runtime_error(op, "Instruction pointer out of bounds"); + } + + if (options?.trace || options?.trace_instructions) + { + const arg = op.arg !== undefined ? variable_to_string(op.arg) : ""; + + console.log(this.ip - 1, op_code_name(op.code), arg); + } + + const result = await this.run_instruction(op); + + if (result !== undefined) + { + return result; + } + + if (options?.trace || options?.trace_stack) + { + console.log( + this.ip - 1, + ...this.stack.map( + (x) => + { + return variable_to_string(x); + } + ) + ); + } + + return undefined; + } +} diff --git a/src/engine.ts b/src/engine.ts deleted file mode 100644 index 00774e5..0000000 --- a/src/engine.ts +++ /dev/null @@ -1,628 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import type { Op } from './opcode' -import type { NativeFunction, Variable } from './runtime' - -import { OpCode, op_code_name } from './opcode' -import { DataType, nil, make_number, make_boolean, make_string } from './runtime' -import { TokenStream } from './lexer' -import { parse } from './parser' -import { compile } from './compiler' -import { optimize_chunk } from './optimizer' -import * as std from './lib' - -function index(val: Variable | undefined): string | number | undefined -{ - if (val == undefined) - return undefined - else if (val.data_type == DataType.String) - return val.string - else if (val.data_type == DataType.Number) - return val.number - else - return undefined -} - -function is_true(val: Variable | undefined): boolean -{ - if (val == undefined) - return false - - switch (val.data_type) - { - case DataType.Number: return val.number != 0 - case DataType.String: return val.string != '' - case DataType.Boolean: return val.boolean ?? false - case DataType.Function: return true - case DataType.NativeFunction: return true - default: - return false - } -} - -function equals(a: Variable | undefined, b: Variable | undefined): boolean -{ - if (a == undefined && b == undefined) - return true - if (a == undefined || b == undefined) - return false - - if (a.data_type != b.data_type) - return false - - switch (a.data_type) - { - case DataType.Nil: return true - case DataType.Boolean: return a.boolean == b.boolean - case DataType.Number: return a.number == b.number - case DataType.String: return a.string == b.string - case DataType.Function: return a.function_id == b.function_id - case DataType.NativeFunction: return a.native_function == b.native_function - case DataType.Table: - { - if (a.table?.keys() != b.table?.keys()) - return false - - for (const key of a.table?.keys() ?? []) - { - if (!equals(a.table?.get(key), b.table?.get(key))) - return false - } - - return true - } - } -} - -export interface LuaOptions -{ - trace?: boolean - trace_instructions?: boolean - trace_stack?: boolean - locals?: Map, -} - -export class Engine -{ - - private program: Op[] - private globals: Map - private start_ip: number = 0 - - private ip: number = 0 - private stack: Variable[] = [] - private locals_stack: Map[] = [] - private locals_capture: Map[] = [] - private call_stack: number[] = [] - private assign_height_stack: number[] = [] - - private error: Error | undefined - - constructor(script?: string, - globals?: Map) - { - this.program = [] - this.globals = globals ?? std.std_lib() - this.error = undefined - this.reset() - - if (script != undefined) - { - const result = this.load(script) - if (result != undefined) - this.error = result - } - } - - load(chunk: string): undefined | Error - { - const stream = new TokenStream() - stream.feed(chunk) - - const ast = parse(stream) - if (ast instanceof Error) - return ast - - optimize_chunk(ast) - const program = compile(ast, this.program) - this.program = program.code - this.ip = program.start - this.start_ip = program.start - } - - bytecode(): string[] - { - if (this.error != undefined) - return [ this.error.message ] - - const output = [] - for (const [i, op] of this.program.entries()) - { - const arg = op.arg != undefined ? std.variable_to_string(op.arg) : '' - if (i == this.ip) - output.push(`* ${ i } ${ op_code_name(op.code) } ${ arg }`) - else - output.push(`${ i } ${ op_code_name(op.code) } ${ arg }`) - } - - return output - } - - global(name: string): Variable | undefined - { - return this.globals.get(name) - } - - define(name: string, func: NativeFunction) - { - this.globals.set(name, { - data_type: DataType.NativeFunction, - native_function: func, - }) - } - - define_table(name: string, table: Map) - { - this.globals.set(name, { - data_type: DataType.Table, - table: table, - }) - } - - reset() - { - this.ip = this.start_ip - this.stack = [] - this.locals_stack = [] - this.locals_capture = [] - this.call_stack = [] - this.assign_height_stack = [] - - this.locals_stack.push(new Map()) - } - - call(func: Variable, ...args: Variable[]): Variable[] | Error - { - if (func.native_function != undefined) - return this.call_native_function(func.native_function, ...args) - - if (func.function_id == undefined) - return new Error() // TODO: Error message - - const old_stack = this.stack - const old_call_stack = this.call_stack - const old_ip = this.ip - const old_locals_stack = this.locals_stack - this.stack = [] - this.call_stack = [] - this.locals_stack = [...func.locals ?? []] - this.ip = func.function_id ?? this.ip - - for (const arg of args) - this.stack.push(arg) - this.stack.push(make_number(args.length)) - this.locals_stack.push(new Map()) - - const result = this.run() - const return_values = this.stack - this.stack = old_stack - this.call_stack = old_call_stack - this.locals_stack = old_locals_stack - this.ip = old_ip - if (result instanceof Error) - return result - - return return_values - } - - run_for_steps(steps: number, options?: LuaOptions): Variable | Error | undefined - { - if (this.error != undefined) - return this.error - - if (options?.locals != undefined) - this.locals_stack.push(options.locals) - - let step_count = 0 - while (this.ip < this.program.length) - { - const result = this.step(options) - if (result != undefined) - return result - - step_count += 1 - if (step_count >= steps) - return undefined - } - - return this.stack[0] ?? nil - } - - run(options?: LuaOptions): Variable | Error - { - const result = this.run_for_steps(1000, options) - if (result == undefined) - return new Error('Program ran for too long') - else - return result - } - - raise_error(message: string) - { - const op = this.program.at(this.ip - 1) - this.error = this.runtime_error(op, message) - } - - private call_native_function(native_function: NativeFunction, ...args: Variable[]): Variable[] | Error - { - const results = native_function(this, ...args) - if (this.error != undefined) - { - const error = this.error - this.error = undefined - return error - } - - return results - } - - private operation(op: (x: number, y: number) => number) - { - const x = this.stack.pop()?.number ?? 0 - const y = this.stack.pop()?.number ?? 0 - this.stack.push(make_number(op(x, y))) - } - - private compair(op: (x: number, y: number) => boolean) - { - const x = this.stack.pop()?.number ?? 0 - const y = this.stack.pop()?.number ?? 0 - this.stack.push(make_boolean(op(x, y))) - } - - private force_stack_height(expected: number, got: number) - { - for (let i = got; i < expected; i++) - this.stack.push(nil) - for (let i = expected; i < got; i++) - this.stack.pop() - } - - private runtime_error(op: Op | undefined, message: string): Error - { - if (op == undefined) - return new Error(`${ message }`) - else - return new Error(`${ op.debug.line }:${ op.debug.column }: ${ message }`) - } - - private run_instruction(op: Op): undefined | Error - { - const { code, arg } = op - switch(code) - { - case OpCode.Pop: - { - const count = arg?.number ?? 1 - this.stack.splice(this.stack.length - count, count) - break - } - - case OpCode.Dup: - { - const count = arg?.number ?? 1 - const items = this.stack.splice(this.stack.length - count, count) - this.stack.push(...items, ...items) - break - } - - case OpCode.Swap: - { - const x = this.stack.splice(this.stack.length - 2, 1) - this.stack.push(...x) - break - } - - case OpCode.IterUpdateState: - { - this.stack[this.stack.length - 2] = this.stack[this.stack.length - 1] - break - } - - case OpCode.IterNext: - { - const state = this.stack[this.stack.length - 1] - const control = this.stack[this.stack.length - 2] - const iter = this.stack[this.stack.length - 3] - if (iter.native_function != undefined) - { - const result = this.call_native_function(iter.native_function, control, state) - if (result instanceof Error) - return result - this.stack.push(...result) - break - } - - this.stack.push(control, state, make_number(2)) - this.call_stack.push(this.ip) - this.locals_stack.push(new Map()) - this.locals_capture = iter.locals ?? [] - this.ip = iter.function_id ?? this.ip - break - } - - case OpCode.IterJumpIfDone: - { - if (this.stack[this.stack.length - 1].data_type == DataType.Nil) - this.ip += arg?.number ?? 0 - break - } - - case OpCode.Add: this.operation((x, y) => x + y); break - case OpCode.Subtract: this.operation((x, y) => x - y); break - case OpCode.Multiply: this.operation((x, y) => x * y); break - case OpCode.Divide: this.operation((x, y) => x / y); break - case OpCode.FloorDivide: this.operation((x, y) => Math.floor(x / y)); break - case OpCode.Modulo: this.operation((x, y) => x % y); break - case OpCode.Exponent: this.operation((x, y) => Math.pow(x, y)); break - case OpCode.LessThen: this.compair((x, y) => x < y); break - case OpCode.LessThenEquals: this.compair((x, y) => x <= y); break - case OpCode.GreaterThen: this.compair((x, y) => x > y); break - case OpCode.GreaterThenEquals: this.compair((x, y) => x >= y); break - - case OpCode.BitAnd: this.operation((x, y) => x & y); break - case OpCode.BitOr: this.operation((x, y) => x | y); break - case OpCode.BitXOr: this.operation((x, y) => x ^ y); break - case OpCode.BitShiftLeft: this.operation((x, y) => x << y); break - case OpCode.BitShiftRight: this.operation((x, y) => x >> y); break - - case OpCode.Concat: - { - const x = std.variable_to_string(this.stack.pop() ?? nil) - const y = std.variable_to_string(this.stack.pop() ?? nil) - this.stack.push(make_string(x + y)) - break - } - - case OpCode.Equals: - { - const [x, y] = [this.stack.pop(), this.stack.pop()] - this.stack.push(make_boolean(equals(x, y))) - break - } - - case OpCode.NotEquals: - { - const [x, y] = [this.stack.pop(), this.stack.pop()] - this.stack.push(make_boolean(!equals(x, y))) - break - } - - case OpCode.And: - { - const [x, y] = [this.stack.pop(), this.stack.pop()] - this.stack.push(make_boolean(is_true(x) && is_true(y))) - break - } - - case OpCode.Or: - { - const [x, y] = [this.stack.pop(), this.stack.pop()] - this.stack.push(make_boolean(is_true(x) || is_true(y))) - break - } - - case OpCode.Not: this.stack.push(make_boolean(!is_true(this.stack.pop()))); break - case OpCode.BitNot: this.stack.push(make_number(~(this.stack.pop()?.number ?? 0))); break - case OpCode.Negate: this.stack.push(make_number(-(this.stack.pop()?.number ?? 0))); break - case OpCode.IsNotNil: this.stack.push(make_boolean((this.stack.pop() ?? nil) != nil)); break - case OpCode.Jump: this.ip += arg?.number ?? 0; break - case OpCode.JumpIfNot: if (!is_true(this.stack.pop())) { this.ip += arg?.number ?? 0 } break - case OpCode.JumpIf: if (is_true(this.stack.pop())) { this.ip += arg?.number ?? 0 } break - case OpCode.MakeLocal: this.locals_stack.at(-1)?.set(arg?.string ?? '', nil); break - case OpCode.NewTable: this.stack.push({ data_type: DataType.Table, table: new Map() }); break - case OpCode.StartBlock: this.locals_stack.push(new Map()); break - case OpCode.EndBlock: this.locals_stack.pop(); break - - case OpCode.Length: - { - const size = std.variable_size(this.stack.pop() ?? nil) - if (size == undefined) - this.stack.push(nil) - else - this.stack.push(make_number(size)) - break - } - - case OpCode.Return: - { - this.ip = this.call_stack.pop() ?? this.program.length - this.locals_stack = this.locals_stack.slice(0, this.call_stack.pop()) - this.locals_capture = [] - break - } - - case OpCode.LoadIndex: - { - const table = this.stack.pop() - if (table?.data_type == DataType.Nil) - { - this.stack.pop() // Pop index - this.stack.push(nil) - break - } - - if (table == undefined || table.table == undefined) - return this.runtime_error(op, 'Can only index on tables') - - const i_var = this.stack.pop() - if (i_var?.data_type == DataType.Nil) - { - this.stack.push(nil) - break - } - - const i = index(i_var) - if (i == undefined) - return this.runtime_error(op, 'Invalid index, must be a number or string') - - this.stack.push(table.table.get(i) ?? nil) - break - } - - case OpCode.StoreIndex: - { - const count = arg?.number ?? 1 - const table = this.stack[this.stack.length - count*2 - 1] ?? nil - if (table.table == undefined) - return this.runtime_error(op, 'Can only index tables') - - for (let i = 0; i < count; i++) - { - const key = index(this.stack.pop()) - const value = this.stack.pop() ?? nil - if (key == undefined) - return this.runtime_error(op, 'Invalid key, must be a number or string') - - table.table.set(key, value) - } - - break - } - - case OpCode.Store: - { - const name = arg?.string ?? '' - const value = this.stack.pop() ?? nil - const local = [...this.locals_capture, ...this.locals_stack] - .reverse() - .find(x => x.has(name)) - - if (local != undefined) - local.set(name, value) - else - this.globals.set(name, value) - break - } - - case OpCode.Push: - { - if (this.locals_stack.length > 0 && arg?.data_type == DataType.Function) - arg.locals = [...this.locals_stack] - this.stack.push(arg ?? nil) - break - } - - case OpCode.Load: - { - const name = arg?.string ?? '' - const local = [...this.locals_capture, ...this.locals_stack] - .reverse() - .map(x => x.get(name)) - .find(x => x != undefined && x.data_type != DataType.Nil) - - const global = this.globals.get(name) - this.stack.push(local ?? global ?? nil) - break - } - - case OpCode.Call: - { - const count = this.stack.pop()?.number ?? 0 - const func_var = this.stack.pop() ?? nil - switch (func_var.data_type) - { - case DataType.NativeFunction: - { - const args = this.stack.splice(this.stack.length - count, count) - if (func_var.native_function != undefined) - { - const result = this.call_native_function(func_var.native_function, ...args) - if (result instanceof Error) - return result - this.stack.push(...result) - } - - if (this.error != undefined) - { - const error = this.error - this.error = undefined - return error - } - break - } - - case DataType.Function: - { - this.stack.push(make_number(count)) - this.call_stack.push(this.locals_stack.length, this.ip) - this.locals_stack.push(new Map()) - this.locals_capture = func_var.locals ?? [] - this.ip = func_var.function_id ?? this.ip - break - } - - default: - { - return this.runtime_error(op, - `Object of type '${ std.type_name(func_var.data_type) }' ` + - 'is not callable') - } - } - - break - } - - case OpCode.ArgumentCount: - { - const got = this.stack.pop()?.number ?? 0 - const expected = arg?.number ?? 0 - this.force_stack_height(expected, got) - break - } - - case OpCode.StartStackChange: - { - this.assign_height_stack.push(this.stack.length) - break - } - - case OpCode.EndStackChange: - { - const got = this.stack.length - (this.assign_height_stack.pop() ?? 0) - const expected = arg?.number ?? 0 - this.force_stack_height(expected, got) - break - } - } - } - - step(options?: LuaOptions): undefined | Error - { - if (this.error != undefined) - return this.error - - if (this.ip >= this.program.length) - return - - const op = this.program[this.ip++] - if (options?.trace || options?.trace_instructions) - { - const arg = op.arg != undefined ? std.variable_to_string(op.arg) : '' - console.log(this.ip - 1, op_code_name(op.code), arg) - } - - const result = this.run_instruction(op) - if (result != undefined) - return result - - if (options?.trace || options?.trace_stack) - console.log(this.ip - 1, ...this.stack.map(x => std.variable_to_string(x))) - } - -} - diff --git a/src/engine/index/index.mts b/src/engine/index/index.mts new file mode 100644 index 0000000..d62286c --- /dev/null +++ b/src/engine/index/index.mts @@ -0,0 +1,23 @@ +import { type Variable, VariableKind } from "../../_index.mjs"; + +function index(value: Variable | undefined): string | number | undefined +{ + if (value === undefined) + { + return undefined; + } + + if (value.data_type === VariableKind.String) + { + return value.string; + } + + if (value.data_type === VariableKind.Number) + { + return value.number; + } + + return undefined; +} + +export { index }; diff --git a/src/engine/interface/lua-options.interface.mts b/src/engine/interface/lua-options.interface.mts new file mode 100644 index 0000000..205e62e --- /dev/null +++ b/src/engine/interface/lua-options.interface.mts @@ -0,0 +1,11 @@ +import type { Variable } from "../../_index.mjs"; + +interface LuaOptionsInterface +{ + trace?: boolean; + trace_instructions?: boolean; + trace_stack?: boolean; + locals?: Map; +} + +export type { LuaOptionsInterface }; diff --git a/src/lexer.mts b/src/lexer.mts new file mode 100644 index 0000000..944a6c8 --- /dev/null +++ b/src/lexer.mts @@ -0,0 +1,537 @@ +import { State, type StateEnum } from "./lexer/definition/enum/state.enum.mjs"; +import { TokenKind } from "./lexer/definition/enum/token-kind.enum.mjs"; +import type { DebugInterface } from "./lexer/definition/interface/debug.interface.mjs"; +import type { TokenInterface } from "./lexer/definition/interface/token.interface.mjs"; +import { get_double_token } from "./lexer/get-double-token/get-double-token.mjs"; +import { get_keyword } from "./lexer/get-keyword/get-keyword.mjs"; +import { get_single_token } from "./lexer/get-single-token/get-single-token.mjs"; + +export class TokenStream +{ + public debug: DebugInterface = { line: -1, column: -1 }; + + private readonly processing_stream: Array; + private readonly peek_queue: Array; + + private state: StateEnum; + private end_of_stream: boolean = false; + private buffer: string; + private token_start_debug: DebugInterface; + + private line: number; + private column: number; + + public constructor() + { + this.state = State.Initial; + this.processing_stream = []; + this.buffer = ""; + this.token_start_debug = { line: 0, column: 0 }; + + this.line = 1; + this.column = 1; + this.peek_queue = []; + } + + public peek(count: number = 1): TokenInterface + { + while (this.peek_queue.length < count) + { + this.on_char(); + } + + const token = this.peek_queue[count - 1]; + + if (token === undefined) + { + throw new Error(); + } + + return token; + } + + private current(): string | undefined + { + if (this.processing_stream.length > 0) + { + return this.processing_stream[0]; + } + + if (this.end_of_stream) + { + return undefined; + } + + this.end_of_stream = true; + + return "\0"; + } + + private consume() + { + if (this.processing_stream.length <= 0) + { + return; + } + + this.column = this.column + 1; + + if (this.processing_stream.shift() === "\n") + { + this.line = this.line + 1; + this.column = 1; + } + } + + private start_token() + { + this.token_start_debug = { + line: this.line, + column: this.column, + }; + + this.buffer = ""; + } + + private initial() + { + if (this.processing_stream.length === 0) + { + this.peek_queue.push({ + data: "", + kind: TokenKind.EOF, + debug: { + line: this.line, + column: this.column, + }, + }); + + return; + } + + const c = this.current() ?? "\0"; + + if (/\s/.test(c)) + { + this.consume(); + + return; + } + + if (this.processing_stream.length > 1) + { + const double = c + this.processing_stream[1]; + + if (double === "--") + { + this.state = State.Comment; + + return; + } + + if (double === "[[") + { + this.state = State.MultiLineString; + this.start_token(); + this.consume(); + this.consume(); + + return; + } + + const dobule_token_type = get_double_token(double); + + if (dobule_token_type !== undefined) + { + this.peek_queue.push({ + data: double, + kind: dobule_token_type, + debug: { + line: this.line, + column: this.column, + }, + }); + + this.consume(); + this.consume(); + + return; + } + } + + const single_token_type = get_single_token(c); + + if (single_token_type !== undefined) + { + this.peek_queue.push({ + data: c, + kind: single_token_type, + debug: { + line: this.line, + column: this.column, + }, + }); + + this.consume(); + + return; + } + + if (c === '"') + { + this.start_token(); + this.consume(); + this.state = State.StringLiteral; + + return; + } + + if (/[a-zA-Z_]/.test(c)) + { + this.start_token(); + this.state = State.Identifier; + + return; + } + + if (/[0-9]/.test(c)) + { + this.start_token(); + this.state = State.NumberLiteral; + } + } + + private read_string() + { + const c = this.current(); + + this.consume(); + + if (c === '"') + { + this.peek_queue.push({ + data: this.buffer, + kind: TokenKind.StringLiteral, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + + return; + } + + if (c === "\\") + { + this.state = State.StringLiteralEscape; + + return; + } + + this.buffer = this.buffer + c; + } + + private read_string_escape() + { + const c = this.current(); + + this.consume(); + this.state = State.StringLiteral; + + switch (c) + { + case "n": this.buffer = `${this.buffer}\n`; break; + case "0": this.buffer = `${this.buffer}\0`; break; + case "r": this.buffer = `${this.buffer}\r`; break; + case "t": this.buffer = `${this.buffer}\t`; break; + + default: + this.buffer = this.buffer + c; + break; + } + } + + private read_multi_line_string() + { + const c = this.current() ?? "\0"; + + this.consume(); + + if (c + this.current() === "]]") + { + this.peek_queue.push({ + data: this.buffer, + kind: TokenKind.StringLiteral, + debug: this.token_start_debug, + }); + + this.consume(); + this.state = State.Initial; + + return; + } + + this.buffer = this.buffer + c; + } + + private read_identifier() + { + const c = this.current() ?? "\0"; + + if (!/[a-zA-Z0-9_]/.test(c)) + { + const kind = get_keyword(this.buffer); + + this.peek_queue.push({ + data: this.buffer, + kind: kind ?? TokenKind.Identifier, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + + return; + } + + this.buffer = this.buffer + c; + this.consume(); + } + + private number() + { + const c = this.current() ?? "\0"; + + if (/[0-9]/.test(c)) + { + this.buffer = this.buffer + c; + this.consume(); + + return; + } + + if (c === ".") + { + this.buffer = this.buffer + c; + this.consume(); + this.state = State.NumberLiteralDot; + + return; + } + + if (c === "e" || c === "E") + { + this.buffer = this.buffer + c; + this.consume(); + this.state = State.NumberLiteralExp; + + return; + } + + if (c === "x") + { + if (this.buffer !== "0") + { + this.peek_queue.push({ + data: this.buffer, + kind: TokenKind.NumberLiteral, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + + return; + } + + this.buffer = this.buffer + c; + this.state = State.NumberHex; + this.consume(); + + return; + } + + this.peek_queue.push({ + data: this.buffer, + kind: TokenKind.NumberLiteral, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + } + + private number_dot() + { + const c = this.current() ?? "\0"; + + if (/[0-9]/.test(c)) + { + this.buffer = this.buffer + c; + this.consume(); + + return; + } + + if (c === "e" || c === "E") + { + this.buffer = this.buffer + c; + this.state = State.NumberLiteralExpSign; + this.consume(); + + return; + } + + this.peek_queue.push({ + data: this.buffer, + kind: TokenKind.NumberLiteral, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + } + + private number_exp_sign() + { + const c = this.current() ?? "\0"; + + if (/[0-9+-]/.test(c)) + { + this.buffer = this.buffer + c; + this.consume(); + this.state = State.NumberLiteralExp; + + return; + } + + this.peek_queue.push({ + data: this.buffer, + kind: TokenKind.NumberLiteral, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + } + + private number_exp() + { + const c = this.current() ?? "\0"; + + if (/[0-9]/.test(c)) + { + this.buffer = this.buffer + c; + this.consume(); + + return; + } + + this.peek_queue.push({ + data: this.buffer, + kind: TokenKind.NumberLiteral, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + } + + private number_hex() + { + const c = this.current() ?? "\0"; + + if (/[0-9a-fA-F]/.test(c)) + { + this.buffer = this.buffer + c; + this.consume(); + + return; + } + + this.peek_queue.push({ + data: parseInt(this.buffer.slice(2), 16).toString(), + kind: TokenKind.NumberLiteral, + debug: this.token_start_debug, + }); + + this.state = State.Initial; + } + + private comment() + { + const c = this.current(); + + this.consume(); + + if (c === "\n") + { + this.state = State.Initial; + } + } + + private on_char() + { + if (this.current() === undefined) + { + this.peek_queue.push({ + data: "", + kind: this.state === State.Initial + ? TokenKind.EOF + : TokenKind.NotFinished, + debug: { + line: this.line, + column: this.column, + }, + }); + + return; + } + + switch (this.state) + { + case State.Initial: + this.initial(); + break; + case State.Identifier: + this.read_identifier(); + break; + case State.StringLiteral: + this.read_string(); + break; + case State.StringLiteralEscape: + this.read_string_escape(); + break; + case State.MultiLineString: + this.read_multi_line_string(); + break; + case State.NumberLiteral: + this.number(); + break; + case State.NumberLiteralDot: + this.number_dot(); + break; + case State.NumberLiteralExpSign: + this.number_exp_sign(); + break; + case State.NumberLiteralExp: + this.number_exp(); + break; + case State.NumberHex: + this.number_hex(); + break; + case State.Comment: + this.comment(); + break; + } + } + + feed(stream: string) + { + this.processing_stream.push(...stream.split("")); + this.end_of_stream = false; + } + + next(): TokenInterface + { + if (this.peek_queue.length === 0) + { + this.peek(); + } + + return this.peek_queue.shift() as TokenInterface; + } +} diff --git a/src/lexer.ts b/src/lexer.ts deleted file mode 100644 index cbaf7c9..0000000 --- a/src/lexer.ts +++ /dev/null @@ -1,676 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -export enum State { - Initial, - Identifier, - StringLiteral, - StringLiteralEscape, - MultiLineString, - NumberLiteral, - NumberLiteralDot, - NumberLiteralExpSign, - NumberLiteralExp, - NumberHex, - Comment, -} - -export enum TokenKind { - EOF, - NotFinished, - - Identifier, - StringLiteral, - BooleanLiteral, - NumberLiteral, - NilLiteral, - - OpenBrace, - CloseBrace, - OpenSquare, - CloseSquare, - SquiglyOpen, - SquiglyClose, - - Addition, - Subtract, - Multiply, - Division, - FloorDivision, - Modulo, - Exponent, - Concat, - Hash, - - BitAnd, - BitOr, - BitXOrNot, - BitShiftLeft, - BitShiftRight, - - Equals, - NotEquals, - LessThen, - LessThenEquals, - GreaterThen, - GreaterThenEquals, - And, - Or, - Not, - - Assign, - Semicolon, - Comma, - Dot, - - Function, - If, - While, - For, - Repeat, - In, - Do, - Then, - ElseIf, - Else, - Until, - End, - Return, - Break, - Local, -} - -export function token_kind_to_string(kind: TokenKind) -{ - switch(kind) - { - case TokenKind.EOF: return 'EOF' - case TokenKind.NotFinished: return 'NotFinished' - case TokenKind.Identifier: return 'Identifier' - case TokenKind.StringLiteral: return 'StringLiteral' - case TokenKind.BooleanLiteral: return 'BooleanLiteral' - case TokenKind.NumberLiteral: return 'NumberLiteral' - case TokenKind.NilLiteral: return 'nil' - case TokenKind.OpenBrace: return '(' - case TokenKind.CloseBrace: return ')' - case TokenKind.OpenSquare: return '[' - case TokenKind.CloseSquare: return ']' - case TokenKind.SquiglyOpen: return '{' - case TokenKind.SquiglyClose: return '}' - case TokenKind.Addition: return '+' - case TokenKind.Subtract: return '-' - case TokenKind.Multiply: return '*' - case TokenKind.Division: return '/' - case TokenKind.FloorDivision: return '//' - case TokenKind.Modulo: return '%' - case TokenKind.Exponent: return '^' - case TokenKind.BitAnd: return '&' - case TokenKind.BitOr: return '|' - case TokenKind.BitXOrNot: return '~' - case TokenKind.BitShiftLeft: return '<<' - case TokenKind.BitShiftRight: return '>>' - case TokenKind.LessThen: return '<' - case TokenKind.GreaterThen: return '>' - case TokenKind.And: return 'and' - case TokenKind.Or: return 'or' - case TokenKind.Not: return 'not' - case TokenKind.Assign: return '=' - case TokenKind.Semicolon: return ';' - case TokenKind.Comma: return ',' - case TokenKind.Dot: return '.' - case TokenKind.Function: return 'function' - case TokenKind.If: return 'if' - case TokenKind.While: return 'while' - case TokenKind.For: return 'for' - case TokenKind.Repeat: return 'repeat' - case TokenKind.In: return 'in' - case TokenKind.Do: return 'do' - case TokenKind.Then: return 'then' - case TokenKind.ElseIf: return 'elseif' - case TokenKind.Else: return 'else' - case TokenKind.Until: return 'until' - case TokenKind.End: return 'end' - case TokenKind.Return: return 'return' - case TokenKind.Break: return 'break' - case TokenKind.Local: return 'local' - } -} - -export interface Debug { - line: number - column: number -} - -export interface Token { - data: string - kind: TokenKind - debug: Debug -} - -const single_token_map: Map = new Map([ - ['(', TokenKind.OpenBrace], - [')', TokenKind.CloseBrace], - ['[', TokenKind.OpenSquare], - [']', TokenKind.CloseSquare], - ['{', TokenKind.SquiglyOpen], - ['}', TokenKind.SquiglyClose], - - ['+', TokenKind.Addition], - ['-', TokenKind.Subtract], - ['*', TokenKind.Multiply], - ['/', TokenKind.Division], - ['%', TokenKind.Modulo], - ['^', TokenKind.Exponent], - ['&', TokenKind.BitAnd], - ['|', TokenKind.BitOr], - ['~', TokenKind.BitXOrNot], - - ['<', TokenKind.LessThen], - ['>', TokenKind.GreaterThen], - - ['=', TokenKind.Assign], - [';', TokenKind.Semicolon], - [',', TokenKind.Comma], - ['.', TokenKind.Dot], - ['#', TokenKind.Hash], -]) - -const double_token_map: Map = new Map([ - ['==', TokenKind.Equals], - ['<=', TokenKind.LessThenEquals], - ['>=', TokenKind.GreaterThenEquals], - ['~=', TokenKind.NotEquals], - ['..', TokenKind.Concat], - ['//', TokenKind.FloorDivision], - ['<<', TokenKind.BitShiftLeft], - ['>>', TokenKind.BitShiftRight], -]) - -const keyword_map: Map = new Map([ - ['function', TokenKind.Function], - ['if', TokenKind.If], - ['while', TokenKind.While], - ['for', TokenKind.For], - ['repeat', TokenKind.Repeat], - ['in', TokenKind.In], - ['do', TokenKind.Do], - ['then', TokenKind.Then], - ['elseif', TokenKind.ElseIf], - ['else', TokenKind.Else], - ['until', TokenKind.Until], - ['end', TokenKind.End], - ['return', TokenKind.Return], - ['break', TokenKind.Break], - - ['and', TokenKind.And], - ['or', TokenKind.Or], - ['not', TokenKind.Not], - - ['true', TokenKind.BooleanLiteral], - ['false', TokenKind.BooleanLiteral], - ['nil', TokenKind.NilLiteral], - ['local', TokenKind.Local], -]) - -export class TokenStream -{ - - private readonly processing_stream: string[] - private readonly peek_queue: Token[] - - private state: State - private end_of_stream: boolean = false - private buffer: string - private token_start_debug: Debug - - private line: number - private column: number - - constructor() - { - this.state = State.Initial - this.processing_stream = [] - this.buffer = '' - this.token_start_debug = { line: 0, column: 0 } - - this.line = 1 - this.column = 1 - this.peek_queue = [] - } - - private current(): string | undefined - { - if (this.processing_stream.length > 0) - return this.processing_stream[0] - - if (this.end_of_stream) - return undefined - - this.end_of_stream = true - return '\0' - } - - private consume() - { - if (this.processing_stream.length <= 0) - return - - this.column += 1 - if (this.processing_stream.shift() == '\n') - { - this.line += 1 - this.column = 1 - } - } - - private start_token() - { - this.token_start_debug = { - line: this.line, - column: this.column, - } - this.buffer = '' - } - - private initial() - { - if (this.processing_stream.length == 0) - { - this.peek_queue.push({ - data: '', - kind: TokenKind.EOF, - debug: { - line: this.line, - column: this.column, - }, - }) - - return - } - - const c = this.current() ?? '\0' - if (/\s/.test(c)) - return this.consume() - - if (this.processing_stream.length > 1) - { - const double = c + this.processing_stream[1] - if (double == '--') - { - this.state = State.Comment - return - } - - if (double == '[[') - { - this.state = State.MultiLineString - this.start_token() - this.consume() - this.consume() - return - } - - const dobule_token_type = double_token_map.get(double) - if (dobule_token_type != undefined) - { - this.peek_queue.push({ - data: double, - kind: dobule_token_type, - debug: { - line: this.line, - column: this.column, - }, - }) - this.consume() - this.consume() - return - } - } - - const single_token_type = single_token_map.get(c) - if (single_token_type != undefined) - { - this.peek_queue.push({ - data: c, - kind: single_token_type, - debug: { - line: this.line, - column: this.column, - }, - }) - this.consume() - return - } - - if (c == '"') - { - this.start_token() - this.consume() - this.state = State.StringLiteral - return - } - - if (/[a-zA-Z_]/.test(c)) - { - this.start_token() - this.state = State.Identifier - return - } - - if (/[0-9]/.test(c)) - { - this.start_token() - this.state = State.NumberLiteral - return - } - } - - private read_string() - { - const c = this.current() - this.consume() - - if (c == '"') - { - this.peek_queue.push({ - data: this.buffer, - kind: TokenKind.StringLiteral, - debug: this.token_start_debug, - }) - - this.state = State.Initial - return - } - - if (c == '\\') - { - this.state = State.StringLiteralEscape - return - } - - this.buffer += c - } - - private read_string_escape() - { - const c = this.current() - this.consume() - this.state = State.StringLiteral - - switch (c) - { - case 'n': this.buffer += '\n'; break - case '0': this.buffer += '\0'; break - case 'r': this.buffer += '\r'; break - case 't': this.buffer += '\t'; break - default: - this.buffer += c - break - } - } - - private read_multi_line_string() - { - const c = this.current() ?? '\0' - this.consume() - - if (c + this.current() == ']]') - { - this.peek_queue.push({ - data: this.buffer, - kind: TokenKind.StringLiteral, - debug: this.token_start_debug, - }) - - this.consume() - this.state = State.Initial - return - } - - this.buffer += c - } - - private read_identifier() - { - const c = this.current() ?? '\0' - if (!/[a-zA-Z0-9_]/.test(c)) - { - const kind = keyword_map.get(this.buffer) - this.peek_queue.push({ - data: this.buffer, - kind: kind ?? TokenKind.Identifier, - debug: this.token_start_debug, - }) - - this.state = State.Initial - return - } - - this.buffer += c - this.consume() - } - - private number() - { - const c = this.current() ?? '\0' - if (/[0-9]/.test(c)) - { - this.buffer += c - this.consume() - return - } - - if (c == '.') - { - this.buffer += c - this.consume() - this.state = State.NumberLiteralDot - return - } - - if (c == 'e' || c == 'E') - { - this.buffer += c - this.consume() - this.state = State.NumberLiteralExp - return - } - - if (c == 'x') - { - if (this.buffer != '0') - { - this.peek_queue.push({ - data: this.buffer, - kind: TokenKind.NumberLiteral, - debug: this.token_start_debug, - }) - this.state = State.Initial - return - } - - this.buffer += c - this.state = State.NumberHex - this.consume() - return - } - - this.peek_queue.push({ - data: this.buffer, - kind: TokenKind.NumberLiteral, - debug: this.token_start_debug, - }) - this.state = State.Initial - } - - private number_dot() - { - const c = this.current() ?? '\0' - if (/[0-9]/.test(c)) - { - this.buffer += c - this.consume() - return - } - - if (c == 'e' || c == 'E') - { - this.buffer += c - this.state = State.NumberLiteralExpSign - this.consume() - return - } - - this.peek_queue.push({ - data: this.buffer, - kind: TokenKind.NumberLiteral, - debug: this.token_start_debug, - }) - this.state = State.Initial - } - - private number_exp_sign() - { - const c = this.current() ?? '\0' - if (/[0-9+-]/.test(c)) - { - this.buffer += c - this.consume() - this.state = State.NumberLiteralExp - return - } - - this.peek_queue.push({ - data: this.buffer, - kind: TokenKind.NumberLiteral, - debug: this.token_start_debug, - }) - this.state = State.Initial - } - - private number_exp() - { - const c = this.current() ?? '\0' - if (/[0-9]/.test(c)) - { - this.buffer += c - this.consume() - return - } - - this.peek_queue.push({ - data: this.buffer, - kind: TokenKind.NumberLiteral, - debug: this.token_start_debug, - }) - this.state = State.Initial - } - - private number_hex() - { - const c = this.current() ?? '\0' - if (/[0-9a-fA-F]/.test(c)) - { - this.buffer += c - this.consume() - return - } - - this.peek_queue.push({ - data: parseInt(this.buffer.slice(2), 16).toString(), - kind: TokenKind.NumberLiteral, - debug: this.token_start_debug, - }) - this.state = State.Initial - } - - private comment() - { - const c = this.current() - this.consume() - - if (c == '\n') - this.state = State.Initial - } - - private on_char() - { - if (this.current() == undefined) - { - this.peek_queue.push({ - data: '', - kind: this.state == State.Initial - ? TokenKind.EOF - : TokenKind.NotFinished, - debug: { - line: this.line, - column: this.column, - }, - }) - - return - } - - switch (this.state) - { - case State.Initial: - this.initial() - break - case State.Identifier: - this.read_identifier() - break - case State.StringLiteral: - this.read_string() - break - case State.StringLiteralEscape: - this.read_string_escape() - break - case State.MultiLineString: - this.read_multi_line_string() - break - case State.NumberLiteral: - this.number() - break - case State.NumberLiteralDot: - this.number_dot() - break - case State.NumberLiteralExpSign: - this.number_exp_sign() - break - case State.NumberLiteralExp: - this.number_exp() - break - case State.NumberHex: - this.number_hex() - break - case State.Comment: - this.comment() - break - } - } - - feed(stream: string) - { - this.processing_stream.push(...stream.split('')) - this.end_of_stream = false - } - - next(): Token - { - if (this.peek_queue.length == 0) - this.peek() - return this.peek_queue.shift() - } - - peek(count = 1): Token - { - while (this.peek_queue.length < count) - this.on_char() - return this.peek_queue[count - 1] - } - -} - diff --git a/src/lexer/definition/enum/state.enum.mts b/src/lexer/definition/enum/state.enum.mts new file mode 100644 index 0000000..bfef1fb --- /dev/null +++ b/src/lexer/definition/enum/state.enum.mts @@ -0,0 +1,17 @@ +const State = { + Initial: "initial", + Identifier: "identifier", + StringLiteral: "string-literal", + StringLiteralEscape: "string-literal-escape", + MultiLineString: "multi-line-string", + NumberLiteral: "number-literal", + NumberLiteralDot: "number-literal-dot", + NumberLiteralExpSign: "number-literal-exp-sign", + NumberLiteralExp: "number-literal-exp", + NumberHex: "number-hex", + Comment: "comment", +} as const satisfies Record; + +type StateEnum = typeof State[keyof typeof State]; + +export { State, type StateEnum }; diff --git a/src/lexer/definition/enum/token-kind.enum.mts b/src/lexer/definition/enum/token-kind.enum.mts new file mode 100644 index 0000000..2742964 --- /dev/null +++ b/src/lexer/definition/enum/token-kind.enum.mts @@ -0,0 +1,69 @@ +/* eslint-disable id-length */ +const TokenKind = { + EOF: "EOF", + NotFinished: "not-finished", + + Identifier: "identifier", + StringLiteral: "string-literal", + BooleanLiteral: "boolean-literal", + NumberLiteral: "number-literal", + NilLiteral: "nil-literal", + + OpenBrace: "open-brace", + CloseBrace: "close-brace", + OpenSquare: "open-square", + CloseSquare: "close-square", + SquiglyOpen: "squigly-open", + SquiglyClose: "squigly-close", + + Addition: "addition", + Subtract: "subtract", + Multiply: "multiply", + Division: "division", + FloorDivision: "floor-division", + Modulo: "modulo", + Exponent: "exponent", + Concat: "concat", + Hash: "hash", + + BitAnd: "bit-and", + BitOr: "bit-or", + BitXOrNot: "bit-xor-not", + BitShiftLeft: "bit-shift-left", + BitShiftRight: "bit-shift-right", + + Equals: "equals", + NotEquals: "not-equals", + LessThan: "less-than", + LessThanEquals: "less-than-equals", + GreaterThan: "greater-than", + GreaterThanEquals: "greater-than-equals", + And: "and", + Or: "or", + Not: "not", + + Assign: "assign", + Semicolon: "semicolon", + Comma: "comma", + Dot: "dot", + + FunctionLike: "function", + If: "if", + While: "while", + For: "for", + Repeat: "repeat", + In: "in", + Do: "do", + Then: "then", + ElseIf: "elseif", + Else: "else", + Until: "until", + End: "end", + Return: "return", + Break: "break", + Local: "local", +} as const satisfies Record; + +type TokenKindEnum = typeof TokenKind[keyof typeof TokenKind]; + +export { TokenKind, type TokenKindEnum }; diff --git a/src/lexer/definition/interface/debug.interface.mts b/src/lexer/definition/interface/debug.interface.mts new file mode 100644 index 0000000..4217c25 --- /dev/null +++ b/src/lexer/definition/interface/debug.interface.mts @@ -0,0 +1,6 @@ +interface DebugInterface { + line: number; + column: number; +} + +export type { DebugInterface }; diff --git a/src/lexer/definition/interface/token.interface.mts b/src/lexer/definition/interface/token.interface.mts new file mode 100644 index 0000000..e96517c --- /dev/null +++ b/src/lexer/definition/interface/token.interface.mts @@ -0,0 +1,11 @@ +import type { TokenKindEnum } from "../enum/token-kind.enum.mjs"; +import type { DebugInterface } from "./debug.interface.mjs"; + +interface TokenInterface +{ + data: string; + kind: TokenKindEnum; + debug: DebugInterface; +} + +export type { TokenInterface }; diff --git a/src/lexer/get-double-token/get-double-token.mts b/src/lexer/get-double-token/get-double-token.mts new file mode 100644 index 0000000..74b20b3 --- /dev/null +++ b/src/lexer/get-double-token/get-double-token.mts @@ -0,0 +1,28 @@ +import { TokenKind, type TokenKindEnum } from "../definition/enum/token-kind.enum.mjs"; + +function get_double_token(double: string): TokenKindEnum | undefined +{ + switch (double) + { + case "==": + return TokenKind.Equals; + case "<=": + return TokenKind.LessThanEquals; + case ">=": + return TokenKind.GreaterThanEquals; + case "~=": + return TokenKind.NotEquals; + case "..": + return TokenKind.Concat; + case "//": + return TokenKind.FloorDivision; + case "<<": + return TokenKind.BitShiftLeft; + case ">>": + return TokenKind.BitShiftRight; + } + + return undefined; +} + +export { get_double_token }; diff --git a/src/lexer/get-keyword/get-keyword.mts b/src/lexer/get-keyword/get-keyword.mts new file mode 100644 index 0000000..7670386 --- /dev/null +++ b/src/lexer/get-keyword/get-keyword.mts @@ -0,0 +1,54 @@ +import { TokenKind, type TokenKindEnum } from "../definition/enum/token-kind.enum.mjs"; + +function get_keyword(keyword: string): TokenKindEnum | undefined +{ + switch (keyword) + { + case "function": + return TokenKind.FunctionLike; + case "if": + return TokenKind.If; + case "while": + return TokenKind.While; + case "for": + return TokenKind.For; + case "repeat": + return TokenKind.Repeat; + case "in": + return TokenKind.In; + case "do": + return TokenKind.Do; + case "then": + return TokenKind.Then; + case "elseif": + return TokenKind.ElseIf; + case "else": + return TokenKind.Else; + case "until": + return TokenKind.Until; + case "end": + return TokenKind.End; + case "return": + return TokenKind.Return; + case "break": + return TokenKind.Break; + case "and": + return TokenKind.And; + case "or": + return TokenKind.Or; + case "not": + return TokenKind.Not; + case "true": + return TokenKind.BooleanLiteral; + case "false": + return TokenKind.BooleanLiteral; + case "nil": + return TokenKind.NilLiteral; + case "local": + return TokenKind.Local; + } + + return undefined; +} + +export { get_keyword }; diff --git a/src/lexer/get-single-token/get-single-token.mts b/src/lexer/get-single-token/get-single-token.mts new file mode 100644 index 0000000..218959b --- /dev/null +++ b/src/lexer/get-single-token/get-single-token.mts @@ -0,0 +1,56 @@ +import { TokenKind, type TokenKindEnum } from "../definition/enum/token-kind.enum.mjs"; + +function get_single_token(token: string): TokenKindEnum | undefined +{ + switch (token) + { + case "(": + return TokenKind.OpenBrace; + case ")": + return TokenKind.CloseBrace; + case "[": + return TokenKind.OpenSquare; + case "]": + return TokenKind.CloseSquare; + case "{": + return TokenKind.SquiglyOpen; + case "}": + return TokenKind.SquiglyClose; + case "+": + return TokenKind.Addition; + case "-": + return TokenKind.Subtract; + case "*": + return TokenKind.Multiply; + case "/": + return TokenKind.Division; + case "%": + return TokenKind.Modulo; + case "^": + return TokenKind.Exponent; + case "&": + return TokenKind.BitAnd; + case "|": + return TokenKind.BitOr; + case "~": + return TokenKind.BitXOrNot; + case "<": + return TokenKind.LessThan; + case ">": + return TokenKind.GreaterThan; + case "=": + return TokenKind.Assign; + case ";": + return TokenKind.Semicolon; + case ",": + return TokenKind.Comma; + case ".": + return TokenKind.Dot; + case "#": + return TokenKind.Hash; + } + + return undefined; +} + +export { get_single_token }; diff --git a/src/lexer/token-kind-to-string/token-kind-to-string.mts b/src/lexer/token-kind-to-string/token-kind-to-string.mts new file mode 100644 index 0000000..7e8c209 --- /dev/null +++ b/src/lexer/token-kind-to-string/token-kind-to-string.mts @@ -0,0 +1,121 @@ +import { TokenKind, type TokenKindEnum } from "../definition/enum/token-kind.enum.mjs"; + +// eslint-disable-next-line complexity, max-lines-per-function +function token_kind_to_string(kind: TokenKindEnum): string +{ + switch (kind) + { + case TokenKind.EOF: + return "EOF"; + case TokenKind.NotFinished: + return "NotFinished"; + case TokenKind.Identifier: + return "Identifier"; + case TokenKind.StringLiteral: + return "StringLiteral"; + case TokenKind.BooleanLiteral: + return "BooleanLiteral"; + case TokenKind.NumberLiteral: + return "NumberLiteral"; + case TokenKind.NilLiteral: + return "nil"; + case TokenKind.OpenBrace: + return "("; + case TokenKind.CloseBrace: + return ")"; + case TokenKind.OpenSquare: + return "["; + case TokenKind.CloseSquare: + return "]"; + case TokenKind.SquiglyOpen: + return "{"; + case TokenKind.SquiglyClose: + return "}"; + case TokenKind.Addition: + return "+"; + case TokenKind.Subtract: + return "-"; + case TokenKind.Multiply: + return "*"; + case TokenKind.Division: + return "/"; + case TokenKind.FloorDivision: + return "//"; + case TokenKind.Modulo: + return "%"; + case TokenKind.Exponent: + return "^"; + case TokenKind.BitAnd: + return "&"; + case TokenKind.BitOr: + return "|"; + case TokenKind.BitXOrNot: + return "~"; + case TokenKind.BitShiftLeft: + return "<<"; + case TokenKind.BitShiftRight: + return ">>"; + case TokenKind.Equals: + return "=="; + case TokenKind.NotEquals: + return "~="; + case TokenKind.LessThan: + return "<"; + case TokenKind.LessThanEquals: + return "<="; + case TokenKind.GreaterThan: + return ">"; + case TokenKind.GreaterThanEquals: + return ">="; + case TokenKind.And: + return "and"; + case TokenKind.Or: + return "or"; + case TokenKind.Not: + return "not"; + case TokenKind.Assign: + return "="; + case TokenKind.Semicolon: + return ";"; + case TokenKind.Comma: + return ","; + case TokenKind.Dot: + return "."; + case TokenKind.FunctionLike: + return "function"; + case TokenKind.If: + return "if"; + case TokenKind.While: + return "while"; + case TokenKind.For: + return "for"; + case TokenKind.Repeat: + return "repeat"; + case TokenKind.In: + return "in"; + case TokenKind.Do: + return "do"; + case TokenKind.Then: + return "then"; + case TokenKind.ElseIf: + return "elseif"; + case TokenKind.Else: + return "else"; + case TokenKind.Until: + return "until"; + case TokenKind.End: + return "end"; + case TokenKind.Return: + return "return"; + case TokenKind.Break: + return "break"; + case TokenKind.Local: + return "local"; + case TokenKind.Concat: + return ".."; + case TokenKind.Hash: + return "#"; + } +} + +export { token_kind_to_string }; diff --git a/src/lexer/utility/get-debug.mts b/src/lexer/utility/get-debug.mts new file mode 100644 index 0000000..4b452ea --- /dev/null +++ b/src/lexer/utility/get-debug.mts @@ -0,0 +1,15 @@ +import type { DebugInterface } from "../definition/interface/debug.interface.mjs"; +import type { TokenInterface } from "../definition/interface/token.interface.mjs"; +import { TokenStream } from "../../lexer.mjs"; + +function getDebug(token: TokenInterface | TokenStream): DebugInterface +{ + if (token instanceof TokenStream) + { + return token.peek().debug; + } + + return token.debug; +} + +export { getDebug }; diff --git a/src/lib.mts b/src/lib.mts new file mode 100644 index 0000000..7395235 --- /dev/null +++ b/src/lib.mts @@ -0,0 +1,1066 @@ +import type { Variable } from "./variable/definition/type/variable.type.mjs"; +import type { VariableValueType } from "./variable/definition/type/variable-value.type.mjs"; +import { VariableKind, type VariableKindEnum } from "./variable/definition/enum/variable-kind.enum.mjs"; +import { isVariableKind } from "./variable/predicate/is-variable-kind.mjs"; +import { assertVariableKind } from "./variable/predicate/assert-variable-kind.mjs"; +import { nil } from "./variable/nil.mjs"; +import type { Engine } from "./engine.mjs"; +import { make_variable } from "./runtime.mjs"; +import { ValidationError, assertArray, assertPopulatedArray, isCallable, isInteger, unary } from "@vitruvius-labs/ts-predicate"; +import type { VariableNumber } from "./variable/definition/interface/variable-number.interface.mjs"; +import { assertVariable } from "./variable/predicate/assert-variable.mjs"; +import { VariableUnwrapUtility } from "./variable/unwrap-variable.mjs"; +import type { VariableTable } from "./variable/definition/interface/variable-table.interface.mjs"; +import { isNil } from "./variable/predicate/is-nil.mjs"; +import type { VariableNativeFunction } from "./variable/definition/interface/variable-native-function.interface.mjs"; +import type { NativeFunction } from "./boundary/definition/type/native-function.type.mjs"; +import type { VariableTableMapType } from "./variable/definition/type/variable-table-map.type.mjs"; +import { RuntimeError } from "./runtime-error.mjs"; +import { table_size } from "./lib/table-size/table-size.mjs"; +import { variable_to_string } from "./lib/variable-to-string/variable-to-string.mjs"; +import { print } from "./lib/print/print.mjs"; +import { type } from "./lib/type/type.mjs"; +import { make_boolean } from "./runtime/make-boolean/make-boolean.mjs"; +import { make_number } from "./runtime/make-number/make-number.mjs"; +import { make_string } from "./runtime/make-string/make-string.mjs"; +import { to_error } from "./parser/error/to-error.mjs"; + +function optional_parameter( + expected_kind: K, + parameter: Variable | undefined +): VariableValueType | undefined +{ + if (isNil(parameter)) + { + return undefined; + } + + assertVariableKind(parameter, expected_kind); + + const value: unknown = VariableUnwrapUtility.unwrap(parameter); + + return value as VariableValueType; +} + +function inext(_: Engine, table: Variable, previous_index: Variable): Array +{ + assertVariableKind(table, VariableKind.Table); + assertVariableKind(previous_index, VariableKind.Number); + + const table_map = table.table; + const next_index: number = previous_index.number + 1; + + const next_value: Variable | undefined = table_map.get(next_index); + + if (next_value === undefined) + { + return [nil]; + } + + return [make_number(next_index), next_value]; +} + +const inext_binding: VariableNativeFunction = { + data_type: VariableKind.NativeFunction, + native_function: inext, +}; + +function ipairs(_: Engine, table: Variable): Array +{ + assertVariableKind(table, VariableKind.Table); + + return [ + inext_binding, + table, + make_number(0), + ]; +} + +function compare_keys(next_key: unknown, index: Variable): boolean +{ + if (isVariableKind(index, VariableKind.Boolean)) + { + return next_key === index.boolean; + } + + if (isVariableKind(index, VariableKind.Number)) + { + return next_key === index.number; + } + + if (isVariableKind(index, VariableKind.String)) + { + return next_key === index.string; + } + + if (isVariableKind(index, VariableKind.Table)) + { + return next_key === index.table; + } + + if (isVariableKind(index, VariableKind.Function)) + { + return next_key === index.function_id; + } + + if (isVariableKind(index, VariableKind.NativeFunction)) + { + return next_key === index.native_function; + } + + return false; +} + +function next(_: Engine, variable: Variable, start_index?: Variable): Array +{ + assertVariableKind(variable, VariableKind.Table); + + if (isNil(start_index)) + { + const first_key: unknown = variable.table.keys().next().value; + const first_value: Variable | undefined = variable.table.get(first_key); + + if (isNil(first_value)) + { + return [nil]; + } + + return [make_variable(first_key), first_value]; + } + + let previous_key_found: boolean = false; + + for (const [key, value] of variable.table.entries()) + { + if (previous_key_found) + { + return [key_variable(key), value]; + } + + if (compare_keys(key, start_index)) + { + previous_key_found = true; + break; + } + } + + return [nil]; +} + +function pairs(_: Engine, table: Variable): Array +{ + const next_func: VariableNativeFunction = { + data_type: VariableKind.NativeFunction, + native_function: next, + }; + + return [next_func, table, nil]; +} + +function range(_: Engine, count: Variable): Array +{ + assertVariableKind(count, VariableKind.Number); + + let index = 0; + + return [{ + data_type: VariableKind.NativeFunction, + native_function: () => + { + index = index + 1; + + if (index >= (count.number ?? 0)) + { + return [nil]; + } + + return [{ data_type: VariableKind.Number, number: index }]; + }, + }]; +} + +function is_empty(_: Engine, table: Variable): Array +{ + assertVariableKind(table, VariableKind.Table); + const empty: boolean = table.table.size === 0; + + return [make_boolean(empty)]; +} + +function key_variable(key: unknown): Variable +{ + if (isCallable(key)) + { + return { + data_type: VariableKind.NativeFunction, + native_function: key, + }; + } + + return make_variable(key); +} + +// @ts-expect-error: unimplemented +function table_sort(engine: Engine, table: Variable, by: Variable): Array +{ + assertVariableKind(table, VariableKind.Table); + + throw new RuntimeError("Unimplemented: table.sort"); + + /* + + const entries: Array<[unknown, Variable]> = [...table.table.entries()] + + entries.sort( + ([_, a], [__, b]) => + { + const result = engine.call(by, a, b) + if (result instanceof Error) + return 0 + + const comparison = result.at(0) + assertVariableKind(comparison, VariableKind.Number); + return comparison.number + } + ) + + const numbered_entries = entries.map(([key, _], i) => [i + 1, key_variable(key)] as const) + return [{ data_type: VariableKind.Table, table: new Map(numbered_entries) }] + + */ +} + +async function find(engine: Engine, table: Variable, matches: Variable): Promise> +{ + assertVariableKind(table, VariableKind.Table); + const entries: Array<[unknown, Variable]> = [...table.table.entries()]; + + for (const [key, value] of entries) + { + const result = await engine.call(matches, value); + + if (result instanceof Error) + { + throw result; + } + + const matching: Variable | undefined = result.at(0); + + assertVariableKind(matching, VariableKind.Boolean); + + return [make_variable(key)]; + } + + return [nil]; +} + +function first(_: Engine, table: Variable): Array +{ + assertVariableKind(table, VariableKind.Table); + const key = table.table.keys().next().value; + + if (typeof key === "string") + { + return [make_string(key)]; + } + + if (typeof key === "number") + { + return [make_number(key)]; + } + + return [nil]; +} + +function keys(_: Engine, table: Variable): Array +{ + assertVariableKind(table, VariableKind.Table); + const keys = [...table.table.keys()]; + const entries = keys.map( + (key, i) => + { + return [i + 1, key_variable(key)] as const; + } + ); + + return [{ data_type: VariableKind.Table, table: new Map(entries) }]; +} + +function values(_: Engine, table: Variable): Array +{ + assertVariableKind(table, VariableKind.Table); + const values = [...table.table.values()]; + const entries = values.map( + (value, i) => + { + return [i + 1, value] as const; + } + ); + + return [{ data_type: VariableKind.Table, table: new Map(entries) }]; +} + +function to_number(_: Engine, arg: Variable): Array +{ + switch (arg.data_type) + { + case VariableKind.Number: + return [arg]; + case VariableKind.String: + const text: string = arg.string.trim(); + + if (!/^-?\d+(\.\d+)?$/.test(text)) + { + return [nil]; + } + + return [make_number(parseFloat(text))]; + + default: + return [nil]; + } +} + +function to_string(_: Engine, arg: Variable): Array +{ + return [make_string(variable_to_string(arg))]; +} + +function assert(engine: Engine, condition: Variable, message?: Variable): Array +{ + if (isVariableKind(condition, VariableKind.Nil) || isVariableKind(condition, VariableKind.Boolean) && !condition.boolean) + { + to_error(engine, message ?? make_string("assertion failed!")); + } + + return [nil]; +} + +function error(engine: Engine, message: Variable): Array +{ + assertVariableKind(message, VariableKind.String); + engine.raise_error(message.string); + + return [nil]; +} + +let warnings_on = true; + +function warn(_: Engine, ...messages: Array): Array +{ + const message = messages[0]; + + if (isVariableKind(message, VariableKind.String)) + { + switch (message.string) + { + case "@on": + warnings_on = true; + break; + case "@off": + warnings_on = false; + break; + } + } + + if (warnings_on) + { + console.error( + "WARNING", + messages.map( + (x) => + { + return variable_to_string(x); + } + ) + ); + } + + return [nil]; +} + +function select(_: Engine, index: Variable, ...args: Array): Array +{ + if (isVariableKind(index, VariableKind.Number)) + { + if (index.number > 0) + { + return args.slice(index.number - 1); + } + + return args.slice(args.length + index.number - 1); + } + + if (isVariableKind(index, VariableKind.String) && index.string === "#") + { + return [make_number(args.length)]; + } + + return [nil]; +} + +function string_byte(_: Engine, s: Variable, i?: Variable, j?: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + + let start: number = 1; + + if (!isNil(i)) + { + assertVariableKind(i, VariableKind.Number); + start = i.number; + } + + let end: number = start; + + if (!isNil(j)) + { + assertVariableKind(j, VariableKind.Number); + end = j.number; + } + + const bytes: Array = []; + + for (let index = start - 1; index <= end - 1; index++) + { + bytes.push(make_number(s.string.charCodeAt(index))); + } + + return bytes; +} + +function string_char(_: Engine, ...chars: Array): Array +{ + assertArray(chars, unary(assertVariableKind, VariableKind.Number)); + + const s = String.fromCharCode(...chars.map( + (c) => + { + return c.number; + } + )); + + return [make_string(s)]; +} + +function string_format_helper(char: string, args_iterator: IterableIterator): string +{ + switch (char) + { + case "d": + { + const arg = args_iterator.next().value; + + assertVariableKind(arg, VariableKind.Number); + + return Math.floor(arg.number).toString(); + } + case "f": + { + const arg = args_iterator.next().value; + + assertVariableKind(arg, VariableKind.Number); + + return arg.number.toString(); + } + case "s": + { + const arg = args_iterator.next().value; + + assertVariable(arg); + + return variable_to_string(arg); + } + case "%": + return "%"; + + default: + throw new Error(`Invalid format specifier: %${char}`); + } +} + +function string_format(_: Engine, format: Variable, ...args: Array): Array +{ + assertVariableKind(format, VariableKind.String); + + const args_iterator = args[Symbol.iterator](); + + let result = ""; + let is_format = false; + + for (const char of format.string) + { + if (is_format) + { + result = result + string_format_helper(char, args_iterator); + is_format = false; + + continue; + } + + if (char === "%") + { + is_format = true; + + continue; + } + + result = result + char; + } + + if (is_format) + { + throw new Error("Invalid format specifier: %"); + } + + return [make_string(result)]; +} + +function string_find(_: Engine, s: Variable, pattern: Variable, init?: Variable, plain?: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + assertVariableKind(pattern, VariableKind.String); + + const offset: number = optional_parameter(VariableKind.Number, init) ?? 1; + const str = s.string.slice(offset - 1); + + const plain_param = optional_parameter(VariableKind.Boolean, plain) ?? false; + + if (plain_param) + { + return [make_number(str.indexOf(pattern.string) + 1)]; + } + + const results = RegExp(pattern.string).exec(str); + + if (results === null || results.length === 0) + { + return [nil]; + } + + const index = s.string.indexOf(results[0]); + + return [make_number(index + 1)]; +} + +function string_len(_: Engine, s: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + + return [make_number(s.string.length)]; +} + +function string_lower(_: Engine, s: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + + return [make_string(s.string.toLowerCase())]; +} + +function string_rep(_: Engine, s: Variable, n: Variable, sep?: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + assertVariableKind(n, VariableKind.Number); + + const separator: string = optional_parameter(VariableKind.String, sep) ?? ""; + + return [make_string(new Array(n.number).fill(s.string).join(separator))]; +} + +function string_sub(_: Engine, s: Variable, i: Variable, j?: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + assertVariableKind(i, VariableKind.Number); + + const end: number | undefined = optional_parameter(VariableKind.Number, j); + + const start = i.number ?? 1; + + return [make_string(s.string.slice(start - 1, end))]; +} + +function string_upper(_: Engine, s: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + + return [make_string(s.string.toUpperCase())]; +} + +function string_reverse(_: Engine, s: Variable): Array +{ + assertVariableKind(s, VariableKind.String); + + return [make_string(s.string.split("").reverse().join(""))]; +} + +function table_concat(_: Engine, list: Variable, sep?: Variable, i?: Variable, j?: Variable): Array +{ + assertVariableKind(list, VariableKind.Table); + + const separator = optional_parameter(VariableKind.String, sep) ?? ""; + const start = optional_parameter(VariableKind.Number, i) ?? 1; + const end = optional_parameter(VariableKind.Number, j); + + const result = [...list.table.values()] + .slice(start - 1, end) + .map((item) => +{ +return variable_to_string(item); +}) + .join(separator); + + return [make_string(result)]; +} + +function table_insert(_: Engine, list: Variable, index?: Variable, value?: Variable): Array +{ + assertVariableKind(list, VariableKind.Table); + + const size = table_size(list); + + if (isNil(value)) + { + return table_insert(_, list, make_number(size + 1), index); + } + + assertVariableKind(index, VariableKind.Number); + + const position: number = index.number; + + for (let i = size + 1; i > position; --i) + { + list.table.set(i, list.table.get(i - 1) ?? nil); + } + + list.table.set(position, value); + + return [nil]; +} + +function table_move(_: Engine, a1: Variable, f: Variable, e: Variable, t: Variable, a2?: Variable): Array +{ + if (isNil(a2)) + { + return table_move(_, a1, f, e, t, a1); + } + + assertVariableKind(a1, VariableKind.Table); + assertVariableKind(f, VariableKind.Number); + assertVariableKind(e, VariableKind.Number); + assertVariableKind(t, VariableKind.Number); + assertVariableKind(a2, VariableKind.Table); + + const src_start = f.number; + const src_end = e.number; + const dest_start = t.number; + const count = src_end - src_start; + + for (let index = 0; index <= count; index++) + { + a2.table.set(dest_start + index, a1.table.get(src_start + index) ?? nil); + } + + return [a2]; +} + +function table_pack(_: Engine, ...args: Array): Array +{ + const elements = args.map( + (item, i) => + { + return [i + 1, item] as [number | string, Variable]; + } + ); + + return [{ + data_type: VariableKind.Table, + table: new Map([...elements, ["n", make_number(args.length)]]), + }]; +} + +function table_remove(_: Engine, list: Variable, pos?: Variable): Array +{ + assertVariableKind(list, VariableKind.Table); + + const size = table_size(list); + const remove_index = optional_parameter(VariableKind.Number, pos) ?? (size + 1); + const deleted_value = list.table.get(remove_index) ?? nil; + + for (let index = remove_index; index < size; ++index) + { + list.table.set(index, list.table.get(index + 1) ?? nil); + } + + list.table.delete(size); + + return [deleted_value]; +} + +function table_unpack(_: Engine, list: Variable, i?: Variable, j?: Variable): Array +{ + assertVariableKind(list, VariableKind.Table); + + const size = table_size(list) ?? 0; + const start = optional_parameter(VariableKind.Number, i) ?? 1; + const end = optional_parameter(VariableKind.Number, j) ?? size; + + return [...list.table.values()].splice(start - 1, end); +} + +function math_abs(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.abs(x.number))]; +} + +function math_acos(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.acos(x.number))]; +} + +function math_asin(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.asin(x.number))]; +} + +function math_atan(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.atan(x.number))]; +} + +function math_ceil(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.ceil(x.number))]; +} + +function math_cos(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.cos(x.number))]; +} + +function math_deg(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(x.number * (180 / Math.PI))]; +} + +function math_exp(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.exp(x.number))]; +} + +function math_floor(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.floor(x.number))]; +} + +function math_fmod(_: Engine, x: Variable, y: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + assertVariableKind(y, VariableKind.Number); + + return [make_number(x.number % y.number)]; +} + +function math_log(_: Engine, x: Variable, base: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + assertVariableKind(base, VariableKind.Number); + + return [make_number(Math.log(x.number) / Math.log(base.number))]; +} + +function math_max(_: Engine, ...args: Array): Array +{ + assertPopulatedArray(args, unary(assertVariableKind, VariableKind.Number)); + + const max = args.reduce( + (acc, x) => + { + return Math.max(acc, x.number); + }, + Number.NEGATIVE_INFINITY + ); + + return [make_number(max)]; +} + +function math_min(_: Engine, ...args: Array): Array +{ + assertPopulatedArray(args, unary(assertVariableKind, VariableKind.Number)); + const min = args.reduce( + (acc, x) => + { + return Math.min(acc, x.number); + }, + Number.POSITIVE_INFINITY + ); + + return [make_number(min)]; +} + +function math_modf(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + const n = x.number; + const integral = Math.trunc(n); + + return [make_number(integral), make_number(n - integral)]; +} + +function math_rad(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(x.number * Math.PI / 180)]; +} + +/** + * no args: [0;1) + * 1 arg : [1; m] + * 2 args: [m; n] +*/ +function math_random(_: Engine, m?: Variable, n?: Variable): Array +{ + if (isNil(m)) + { + return [make_number(Math.random())]; + } + + if (isNil(n)) + { + return math_random(_, make_number(1), m); + } + + assertVariableKind(m, VariableKind.Number); + assertVariableKind(n, VariableKind.Number); + + const min = m.number; + const max = n.number; + + if (max === 0) + { + throw new ValidationError("math.random: upper bound must be greater than 0."); + } + + if (min > max) + { + throw new ValidationError("math.random: upper bound must be greater than or equal to lower bound."); + } + + const result: number = Math.floor(Math.random() * (max + 1 - min) + min); + + return [make_number(result)]; +} + +function math_sin(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.sin(x.number))]; +} + +function math_sqrt(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.sqrt(x.number))]; +} + +function math_tan(_: Engine, x: Variable): Array +{ + assertVariableKind(x, VariableKind.Number); + + return [make_number(Math.tan(x.number))]; +} + +function math_tointeger(_: Engine, x: Variable): Array +{ + assertVariable(x); + + switch (x.data_type) + { + case VariableKind.Number: + return [x]; + case VariableKind.String: + const text: string = x.string.trim(); + + if (!/^-?\d+$/.test(text)) + { + return [nil]; + } + + return [make_number(parseInt(text))]; + + default: + return [nil]; + } +} + +function math_type(_: Engine, x: Variable): Array +{ + assertVariable(x); + + if (!isVariableKind(x, VariableKind.Number)) + { + return [nil]; + } + + if (isInteger(x.number)) + { + return [make_string("integer")]; + } + + return [make_string("float")]; +} + +function math_ult(_: Engine, m: Variable, n: Variable): Array +{ + assertVariableKind(m, VariableKind.Number); + assertVariableKind(n, VariableKind.Number); + + const buffer = Buffer.from(new ArrayBuffer(8)); + + buffer.writeInt32LE(m.number); + buffer.writeInt32LE(n.number, 4); + + const m_unsigned = buffer.readUInt32LE(); + const n_unsigned = buffer.readUInt32LE(4); + + return [make_boolean(m_unsigned < n_unsigned)]; +} + +function fwrap(fn: NativeFunction): VariableNativeFunction +{ + return { + data_type: VariableKind.NativeFunction, + native_function: fn, + }; +} + +function twrap(content: Array<[unknown, Variable]>): VariableTable +{ + return { + data_type: VariableKind.Table, + table: new Map(content), + }; +} + +export function std_lib(): VariableTableMapType +{ + const string_mapping: VariableTable = twrap([ + ["byte", fwrap(string_byte)], + ["char", fwrap(string_char)], + // dump + ["format", fwrap(string_format)], + ["find", fwrap(string_find)], + // gfind + // gsub + ["len", fwrap(string_len)], + ["lower", fwrap(string_lower)], + ["rep", fwrap(string_rep)], + ["reverse", fwrap(string_reverse)], + ["sub", fwrap(string_sub)], + ["upper", fwrap(string_upper)], + ]); + + const table_mapping: VariableTable = twrap([ + ["concat", { data_type: VariableKind.NativeFunction, native_function: table_concat }], + // foreach + // foreachi + // getn + ["insert", fwrap(table_insert)], + ["move", fwrap(table_move)], + ["pack", fwrap(table_pack)], + ["remove", fwrap(table_remove)], + // setn + ["sort", fwrap(table_sort)], + ["unpack", fwrap(table_unpack)], + ]); + + const math_mapping: VariableTable = twrap([ + ["abs", fwrap(math_abs)], + ["acos", fwrap(math_acos)], + ["asin", fwrap(math_asin)], + ["atan", fwrap(math_atan)], + // atan2 + ["ceil", fwrap(math_ceil)], + ["cos", fwrap(math_cos)], + ["deg", fwrap(math_deg)], + ["exp", fwrap(math_exp)], + ["floor", fwrap(math_floor)], + ["fmod", fwrap(math_fmod)], + ["log", fwrap(math_log)], + // log10 + ["max", fwrap(math_max)], + ["min", fwrap(math_min)], + // mod + ["modf", fwrap(math_modf)], + // pow + ["rad", fwrap(math_rad)], + ["random", fwrap(math_random)], + ["sin", fwrap(math_sin)], + ["sqrt", fwrap(math_sqrt)], + ["tan", fwrap(math_tan)], + // frexp + // ldexp + ["tointeger", fwrap(math_tointeger)], + ["type", fwrap(math_type)], + ["ult", fwrap(math_ult)], + // Constants + ["pi", make_number(Math.PI)], + ["maxinteger", make_number(Number.MAX_SAFE_INTEGER)], + ["mininteger", make_number(Number.MIN_SAFE_INTEGER)], + ["huge", make_number(Number.POSITIVE_INFINITY)], + ]); + + const global: VariableTable = twrap([ + ["assert", fwrap(assert)], + ["error", fwrap(error)], + ["find", fwrap(find)], + ["first", fwrap(first)], + // getmetatable + ["ipairs", fwrap(ipairs)], + ["isempty", fwrap(is_empty)], + ["keys", fwrap(keys)], + ["math", math_mapping], + ["next", fwrap(next)], + ["pairs", fwrap(pairs)], + // pcall + ["print", fwrap(print)], + ["range", fwrap(range)], + // rawequal + // rawget + // rawset + ["select", fwrap(select)], + // setmetatable + ["string", string_mapping], + ["table", table_mapping], + ["tonumber", fwrap(to_number)], + ["tostring", fwrap(to_string)], + ["type", fwrap(type)], + ["values", fwrap(values)], + ["warn", fwrap(warn)], + // xpcall + ]); + + global.table.set("_G", global); + + return global.table; +} diff --git a/src/lib.ts b/src/lib.ts deleted file mode 100644 index 65a0a67..0000000 --- a/src/lib.ts +++ /dev/null @@ -1,780 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import type { Variable } from './runtime' -import { Engine } from './engine' -import { DataType, make_boolean, make_number, make_string, nil } from './runtime' - -let rand = xoroshiro([0, 0, 0, 0].map(_ => BigInt(Math.floor(Math.random() * 100)))) - -export function variable_size(value: Variable): number | undefined -{ - switch (value.data_type) - { - case DataType.String: return value.string?.length ?? 0 - case DataType.Table: - return ([...value.table?.keys() ?? []] - .filter(key => typeof key == 'number') as number[]) - .reduce((acc, key) => Math.max(acc, key), 0) - default: - return undefined - } -} - -export function variable_to_string(variable: Variable, tables_done: Variable[] = []): string -{ - switch (variable.data_type) - { - case DataType.Nil: return 'nil' - case DataType.Boolean: return variable.boolean ? 'true' : 'false' - case DataType.Number: return variable.number?.toString() ?? '0' - case DataType.String: return variable.string ?? '' - case DataType.Function: return `` - case DataType.NativeFunction: return `` - case DataType.Table: - { - if (tables_done.includes(variable)) - return '...' - - tables_done.push(variable) - return `{ ${ [...variable.table?.entries() ?? []] - .map(([i, v]) => - `${ i } = ${ variable_to_string(v, tables_done) }`) - .join(', ') } }` - } - default: return '' - } -} - -export function type_name(data_type: DataType): string -{ - switch (data_type) - { - case DataType.Nil: return 'nil' - case DataType.Boolean: return 'boolean' - case DataType.Number: return 'number' - case DataType.String: return 'string' - case DataType.Function: return 'function' - case DataType.NativeFunction: return 'function' - case DataType.Table: return 'table' - } -} - -function print(_: Engine, ...args: Variable[]): Variable[] -{ - console.log(...args.map(arg => variable_to_string(arg))) - return [nil] -} - -function type(_: Engine, v: Variable): Variable[] -{ - return [make_string(type_name(v.data_type))] -} - -function ipairs(_: Engine, table: Variable): Variable[] -{ - if (table.data_type != DataType.Table || table.table == undefined) - return [nil] - - let index = 1 - return [{ - data_type: DataType.NativeFunction, - native_function: () => - { - const key = key_variable(index) - const value = table.table?.get(index++) - if (value == undefined) - return [nil] - - return [key, value] - }, - }] -} - -function next(_: Engine, { table }: Variable, index?: Variable): Variable[] -{ - if (table == undefined) - return [nil] - - const keys = table.keys() - if (index == undefined || index.data_type == DataType.Nil) - { - const first_key: number | string | undefined = keys.next().value - if (first_key == undefined) - return [nil] - - return [key_variable(first_key), table.get(first_key) ?? nil] - } - - - let next_key: number | string | undefined = keys.next().value - while (next_key != undefined && next_key != index.number && next_key != index.string) - next_key = keys.next().value - - next_key = keys.next().value - if (next_key == undefined) - return [nil] - - return [key_variable(next_key), table.get(next_key) ?? nil] -} - -function pairs(_: Engine, table: Variable): Variable[] -{ - const next_func = { data_type: DataType.NativeFunction, native_function: next } - return [next_func, table, nil] -} - -function range(_: Engine, count: Variable): Variable[] -{ - let index = 0 - return [{ - data_type: DataType.NativeFunction, - native_function: () => - { - index += 1 - if (index >= (count.number ?? 0)) - return [nil] - return [{ data_type: DataType.Number, number: index }] - }, - }] -} - -function random(_: Engine): Variable[] -{ - return [{ data_type: DataType.Number, number: Math.random() }] -} - -function is_empty(_: Engine, table: Variable): Variable[] -{ - const len = table.table?.size ?? 0 - return [{ data_type: DataType.Boolean, boolean: len == 0 }] -} - -function key_variable(key: number | string): Variable -{ - if (typeof key == 'number') - return make_number(key) - else if (typeof key == 'string') - return make_string(key) - else - return nil -} - -function sort(engine: Engine, table: Variable, by: Variable): Variable[] -{ - const entries: [string | number, Variable][] = [...table.table?.entries() ?? []] - entries.sort(([_, a], [__, b]) => - { - const result = engine.call(by, a, b) - if (result instanceof Error) - return 0 - - return result.at(0)?.number ?? 0 - }) - - const numbered_entries = entries.map(([key, _], i) => [i + 1, key_variable(key)] as const) - return [{ data_type: DataType.Table, table: new Map(numbered_entries) }] -} - -function find(engine: Engine, table: Variable, matches: Variable): Variable[] -{ - const entries: [string | number, Variable][] = [...table.table?.entries() ?? []] - const found = entries.find(([_, a]) => - { - const result = engine.call(matches, a) - if (result instanceof Error) - return 0 - - return result.at(0)?.boolean ?? 0 - }) - - if (found == undefined) - return [nil] - - const [key, _] = found - if (typeof key == 'number') - return [make_number(key)] - else if (typeof key == 'string') - return [make_string(key)] - else - return [nil] -} - -function first(_: Engine, table: Variable): Variable[] -{ - if (table.table == undefined) - return [nil] - - const key = table.table.keys().next().value - if (typeof key == 'string') - return [make_string(key)] - else if (typeof key == 'number') - return [make_number(key)] - else - return [nil] -} - -function keys(_: Engine, table: Variable): Variable[] -{ - if (table.table == undefined) - return [nil] - - const keys = [...table.table.keys()] - const entries = keys.map((key, i) => [i + 1, key_variable(key)] as const) - return [{ data_type: DataType.Table, table: new Map(entries) }] -} - -function values(_: Engine, table: Variable): Variable[] -{ - if (table.table == undefined) - return [nil] - - const values = [...table.table.values()] - const entries = values.map((value, i) => [i + 1, value] as const) - return [{ data_type: DataType.Table, table: new Map(entries) }] -} - -function to_number(_: Engine, arg: Variable): Variable[] -{ - switch (arg.data_type) - { - case DataType.Number: - return [arg] - case DataType.String: - return [make_number(parseFloat(arg.string ?? '0'))] - default: - return [nil] - } -} - -function to_string(_: Engine, arg: Variable): Variable[] -{ - return [make_string(variable_to_string(arg))] -} - -function assert(engine: Engine, condition: Variable, message?: Variable): Variable[] -{ - if (!(condition.boolean ?? false)) - engine.raise_error(message?.string ?? 'assertion failed!') - - return [nil] -} - -function error(engine: Engine, message: Variable): Variable[] -{ - engine.raise_error(message?.string ?? '') - return [nil] -} - -let warnings_on = true -function warn(_: Engine, ...messages: Variable[]): Variable[] -{ - if (messages.length == 1) - { - switch (messages[0].string) - { - case '@on': - warnings_on = true - break - case '@off': - warnings_on = false - break - } - } - - if (warnings_on) - console.error('WARNING', messages.map(x => variable_to_string(x))) - return [nil] -} - -function select(_: Engine, index: Variable, ...args: Variable[]): Variable[] -{ - if (index.number != undefined) - { - if (index.number > 0) - return args.slice(index.number - 1) - else - return args.slice(args.length + index.number - 1) - } - - if (index.string == '#') - return [make_number(args.length)] - - return [nil] -} - -function string_byte(_: Engine, s: Variable, i?: Variable, j?: Variable): Variable[] -{ - if (s.string == undefined) - return [nil] - - const start = i?.number ?? 1 - const end = j?.number ?? start - const bytes: Variable[] = [] - for (let index = start - 1; index <= end - 1; index++) - bytes.push(make_number(s.string.charCodeAt(index))) - return bytes -} - -function string_char(_: Engine, ...chars: Variable[]): Variable[] -{ - const s = String.fromCharCode(...chars - .filter(x => x.number != undefined) - .map(c => c.number ?? 0)) - return [make_string(s)] -} - -function string_format(_: Engine, format: Variable, ...args: Variable[]): Variable[] -{ - if (format.string == undefined) - return [nil] - - let result = '' - let is_format = false - let arg_index = 0 - for (const char of format.string) - { - if (is_format) - { - switch (char) - { - case 'd': result += Math.floor(args[arg_index++].number ?? 0).toString(); break - case 'f': result += args[arg_index++].number?.toString(); break - case 's': result += variable_to_string(args[arg_index++]); break - default: - result += `%${ char }` - } - is_format = false - } - else - { - if (char == '%') - is_format = true - else - result += char - } - } - - return [make_string(result)] -} - -function string_find(_: Engine, s: Variable, pattern: Variable, init?: Variable, plain?: Variable): Variable[] -{ - if (s.string == undefined || pattern.string == undefined) - return [nil] - - const offset = init?.number ?? 1 - const str = s.string.slice(offset - 1) - - if (plain?.boolean == true) - return [make_number(str.indexOf(pattern.string) + 1)] - - const results = RegExp(pattern.string).exec(str) - if (results == null || results.length == 0) - return [nil] - - const index = s.string.indexOf(results[0]) - return [make_number(index + 1)] -} - -function string_len(_: Engine, s: Variable): Variable[] -{ - return [s.string == undefined ? nil : make_number(s.string.length)] -} - -function string_lower(_: Engine, s: Variable): Variable[] -{ - return [s.string == undefined ? nil : make_string(s.string.toLowerCase())] -} - -function string_rep(_: Engine, s: Variable, n: Variable, sep?: Variable): Variable[] -{ - if (s.string == undefined) - return [nil] - - return [make_string(new Array(n.number ?? 0) - .fill(s.string) - .join(sep?.string ?? ''))] -} - -function string_sub(_: Engine, s: Variable, i: Variable, j?: Variable): Variable[] -{ - if (s.string == undefined) - return [nil] - - const start = i.number ?? 1 - return [make_string(s.string.slice(start - 1, j?.number))] -} - -function string_upper(_: Engine, s: Variable): Variable[] -{ - return [s.string == undefined ? nil : make_string(s.string.toUpperCase())] -} - -function string_reverse(_: Engine, s: Variable): Variable[] -{ - return [s.string == undefined ? nil : - make_string(s.string.split('').reverse().join(''))] -} - -function table_concat(_: Engine, list: Variable, sep?: Variable, i?: Variable, j?: Variable): Variable[] -{ - if (list.table == undefined) - return [nil] - - const seporator = sep?.string ?? '' - const start = i?.number ?? 1 - const result = [...list.table.values()] - .slice(start - 1, j?.number) - .map(item => variable_to_string(item)) - .join(seporator) - return [make_string(result)] -} - -function table_insert(_: Engine, list: Variable, arg_a?: Variable, arg_b?: Variable): Variable[] -{ - if (list.table == undefined) - return [nil] - - const size = variable_size(list) ?? 0 - let pos = size + 1 - let value = arg_a ?? nil - if (arg_b != undefined) - { - pos = arg_a?.number ?? pos - value = arg_b ?? nil - } - - for (let i = size + 1; i > pos; --i) - list.table.set(i, list.table.get(i - 1) ?? nil) - list.table.set(pos, value) - return [nil] -} - -function table_move(_: Engine, a1: Variable, f: Variable, e: Variable, t: Variable, a2?: Variable): Variable[] -{ - a2 = a2 ?? a1 - if (a1.table == undefined || a2.table == undefined) - return [nil] - - if (f.number == undefined || e.number == undefined || t.number == undefined) - return [nil] - - const src_start = f.number - const src_end = e.number - const dest_start = t.number - const count = src_end - src_start - for (let index = 0; index <= count; index++) - a2.table.set(dest_start + index, a1.table.get(src_start + index) ?? nil) - - return [a2] -} - -function table_pack(_: Engine, ...args: Variable[]): Variable[] -{ - const elements = args - .map((item, i) => [i + 1, item] as [number | string, Variable]) - - return [{ - data_type: DataType.Table, - table: new Map([...elements, ['n', make_number(args.length)]]), - }] -} - -function table_remove(_: Engine, list: Variable, pos?: Variable): Variable[] -{ - if (list.table == undefined) - return [nil] - - const size = variable_size(list) ?? 0 - const remove_index = pos?.number ?? (size + 1) - const deleted_value = list.table.get(remove_index) ?? nil - - for (let index = remove_index; index < size; index++) - list.table.set(index, list.table.get(index + 1) ?? nil) - list.table.delete(size) - - return [deleted_value] -} - -function table_unpack(_: Engine, list: Variable, i?: Variable, j?: Variable): Variable[] -{ - if (list.table == undefined) - return [nil] - - const size = variable_size(list) ?? 0 - const start = i?.number ?? 1 - const end = j?.number ?? size - return [...list.table.values()].splice(start - 1, end) -} - -function math_abs(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.abs(x.number ?? 0))] -} - -function math_acos(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.acos(x.number ?? 0))] -} - -function math_asin(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.asin(x.number ?? 0))] -} - -function math_atan(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.atan(x.number ?? 0))] -} - -function math_ceil(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.ceil(x.number ?? 0))] -} - -function math_cos(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.cos(x.number ?? 0))] -} - -function math_deg(_: Engine, x: Variable): Variable[] -{ - return [make_number((x.number ?? 0.0) * (180.0/Math.PI))] -} - -function math_exp(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.exp(x.number ?? 0.0))] -} - -function math_floor(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.floor(x.number ?? 0.0))] -} - -function math_fmod(_: Engine, x: Variable, y: Variable): Variable[] -{ - return [make_number((x.number ?? 0) % (y.number ?? 0))] -} - -function math_log(_: Engine, x: Variable, base: Variable): Variable[] -{ - return [make_number(Math.log(x.number ?? 0) / Math.log(base.number ?? 0))] -} - -function math_max(_: Engine, ...args: Variable[]): Variable[] -{ - const max = args.reduce((acc, x) => - x.number == undefined ? acc : Math.max(acc, x.number), -Infinity) - return [make_number(max)] -} - -function math_min(_: Engine, ...args: Variable[]): Variable[] -{ - const min = args.reduce((acc, x) => - x.number == undefined ? acc : Math.min(acc, x.number), Infinity) - return [make_number(min)] -} - -function math_modf(_: Engine, x: Variable): Variable[] -{ - const n = x.number ?? 0 - const integral = Math.trunc(n) - return [make_number(integral), make_number(n - integral)] -} - -function math_rad(_: Engine, x: Variable): Variable[] -{ - return [make_number((x.number ?? 0.0) * (Math.PI/180.0))] -} - -function math_random(_: Engine, m?: Variable, n?: Variable): Variable[] -{ - if (m == undefined && n == undefined) - return [make_number(rand() / 0xFFFFFFFF)] - - if (n == undefined) - { - const max = m?.number ?? 0 - if (max == 0) - return [make_number(rand())] - - return [make_number(Math.floor(rand() % max) + 1)] - } - - const min = m?.number ?? 1 - const max = (n?.number ?? 1) - min - return [make_number(Math.floor(rand() % max) + min)] -} - -function xoroshiro(s: bigint[]) -{ - const rotl = (x: bigint, k: bigint) => - BigInt.asUintN(64, (x << k) | (x >> (BigInt(64) - k))) - - return () => - { - const result = rotl(BigInt.asUintN(64, s[0] + s[3]), BigInt(23)) + s[0] - const t = BigInt.asUintN(64, s[1] << BigInt(17)) - s[2] ^= s[0] - s[3] ^= s[1] - s[1] ^= s[2] - s[0] ^= s[3] - s[2] ^= t - s[3] = rotl(s[3], BigInt(45)) - - return Number(BigInt.asUintN(64, result) & BigInt(0xFFFFFFFF)) - } -} - -function math_randomseed(_: Engine, x?: Variable, y?: Variable): Variable[] -{ - const seed = [0, 0, 0, 0].map(_ => BigInt(Math.floor(Math.random() * 100))) - if (x?.number != undefined) - { - seed[0] = BigInt(x.number & 0xFFFFFFFF) - seed[1] = BigInt((x.number << 32) & 0xFFFFFFFF) - } - - if (y?.number != undefined) - { - seed[2] = BigInt(y.number & 0xFFFFFFFF) - seed[3] = BigInt((y.number << 32) & 0xFFFFFFFF) - } - - rand = xoroshiro(seed) - return [nil] -} - -function math_sin(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.sin(x.number ?? 0))] -} - -function math_sqrt(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.sqrt(x.number ?? 0))] -} - -function math_tan(_: Engine, x: Variable): Variable[] -{ - return [make_number(Math.tan(x.number ?? 0))] -} - -function math_tointeger(_: Engine, x: Variable): Variable[] -{ - switch (x.data_type) - { - case DataType.Number: - return [make_number(Math.floor(x.number ?? 0))] - case DataType.String: - return [make_number(Math.floor(parseFloat(x.string ?? '0')))] - default: - return [nil] - } -} - -function math_type(_: Engine, x: Variable): Variable[] -{ - if (x.number == undefined) - return [nil] - - if (Math.floor(x.number) == x.number) - return [make_string('integer')] - else - return [make_string('float')] -} - -function math_ult(_: Engine, m: Variable, n: Variable): Variable[] -{ - const buffer = Buffer.from(new ArrayBuffer(8)) - buffer.writeInt32LE(m.number ?? 0) - buffer.writeInt32LE(n.number ?? 0, 4) - - const m_unsigned = buffer.readUInt32LE() - const n_unsigned = buffer.readUInt32LE(4) - return [make_boolean(m_unsigned < n_unsigned)] -} - -export function std_lib(): Map -{ - const global: Map = new Map([ - ['print', { data_type: DataType.NativeFunction, native_function: print }], - ['type', { data_type: DataType.NativeFunction, native_function: type }], - ['ipairs', { data_type: DataType.NativeFunction, native_function: ipairs }], - ['pairs', { data_type: DataType.NativeFunction, native_function: pairs }], - ['next', { data_type: DataType.NativeFunction, native_function: next }], - ['range', { data_type: DataType.NativeFunction, native_function: range }], - ['random', { data_type: DataType.NativeFunction, native_function: random }], - ['isempty', { data_type: DataType.NativeFunction, native_function: is_empty }], - ['sort', { data_type: DataType.NativeFunction, native_function: sort }], - ['find', { data_type: DataType.NativeFunction, native_function: find }], - ['first', { data_type: DataType.NativeFunction, native_function: first }], - ['keys', { data_type: DataType.NativeFunction, native_function: keys }], - ['values', { data_type: DataType.NativeFunction, native_function: values }], - ['tonumber', { data_type: DataType.NativeFunction, native_function: to_number }], - ['tostring', { data_type: DataType.NativeFunction, native_function: to_string }], - ['assert', { data_type: DataType.NativeFunction, native_function: assert }], - ['error', { data_type: DataType.NativeFunction, native_function: error }], - ['warn', { data_type: DataType.NativeFunction, native_function: warn }], - ['select', { data_type: DataType.NativeFunction, native_function: select }], - ['string', { data_type: DataType.Table, table: new Map([ - ['byte', { data_type: DataType.NativeFunction, native_function: string_byte }], - ['char', { data_type: DataType.NativeFunction, native_function: string_char }], - ['format', { data_type: DataType.NativeFunction, native_function: string_format }], - ['find', { data_type: DataType.NativeFunction, native_function: string_find }], - ['len', { data_type: DataType.NativeFunction, native_function: string_len }], - ['lower', { data_type: DataType.NativeFunction, native_function: string_lower }], - ['rep', { data_type: DataType.NativeFunction, native_function: string_rep }], - ['reverse', { data_type: DataType.NativeFunction, native_function: string_reverse }], - ['sub', { data_type: DataType.NativeFunction, native_function: string_sub }], - ['upper', { data_type: DataType.NativeFunction, native_function: string_upper }], - ]) }], - ['table', { data_type: DataType.Table, table: new Map([ - ['concat', { data_type: DataType.NativeFunction, native_function: table_concat }], - ['insert', { data_type: DataType.NativeFunction, native_function: table_insert }], - ['move', { data_type: DataType.NativeFunction, native_function: table_move }], - ['pack', { data_type: DataType.NativeFunction, native_function: table_pack }], - ['remove', { data_type: DataType.NativeFunction, native_function: table_remove }], - ['unpack', { data_type: DataType.NativeFunction, native_function: table_unpack }], - ]) }], - ['math', { data_type: DataType.Table, table: new Map([ - ['abs', { data_type: DataType.NativeFunction, native_function: math_abs }], - ['acos', { data_type: DataType.NativeFunction, native_function: math_acos }], - ['asin', { data_type: DataType.NativeFunction, native_function: math_asin }], - ['atan', { data_type: DataType.NativeFunction, native_function: math_atan }], - ['ceil', { data_type: DataType.NativeFunction, native_function: math_ceil }], - ['cos', { data_type: DataType.NativeFunction, native_function: math_cos }], - ['deg', { data_type: DataType.NativeFunction, native_function: math_deg }], - ['exp', { data_type: DataType.NativeFunction, native_function: math_exp }], - ['floor', { data_type: DataType.NativeFunction, native_function: math_floor }], - ['fmod', { data_type: DataType.NativeFunction, native_function: math_fmod }], - ['log', { data_type: DataType.NativeFunction, native_function: math_log }], - ['max', { data_type: DataType.NativeFunction, native_function: math_max }], - ['min', { data_type: DataType.NativeFunction, native_function: math_min }], - ['modf', { data_type: DataType.NativeFunction, native_function: math_modf }], - ['rad', { data_type: DataType.NativeFunction, native_function: math_rad }], - ['random', { data_type: DataType.NativeFunction, native_function: math_random }], - ['randomseed', { data_type: DataType.NativeFunction, native_function: math_randomseed }], - ['sin', { data_type: DataType.NativeFunction, native_function: math_sin }], - ['sqrt', { data_type: DataType.NativeFunction, native_function: math_sqrt }], - ['tan', { data_type: DataType.NativeFunction, native_function: math_tan }], - ['tointeger', { data_type: DataType.NativeFunction, native_function: math_tointeger }], - ['type', { data_type: DataType.NativeFunction, native_function: math_type }], - ['ult', { data_type: DataType.NativeFunction, native_function: math_ult }], - - ['pi', { data_type: DataType.Number, number: Math.PI }], - ['maxinteger', { data_type: DataType.Number, number: 0xFFFFFFFF }], - ['mininteger', { data_type: DataType.Number, number: -(0xFFFFFFFF - 1) }], - ['huge', { data_type: DataType.Number, number: Infinity }], - ]) }], - ]) - - global.set('_G', { data_type: DataType.Table, table: global }) - return global -} - diff --git a/src/lib/print/print.mts b/src/lib/print/print.mts new file mode 100644 index 0000000..87bd42d --- /dev/null +++ b/src/lib/print/print.mts @@ -0,0 +1,19 @@ +import { type Variable, nil } from "../../_index.mjs"; +import type { Engine } from "../../engine.mjs"; +import { variable_to_string } from "../variable-to-string/variable-to-string.mjs"; + +// @ts-expect-error - engine is unused for now. +function print(engine: Engine, ...args: Array): Array +{ + // eslint-disable-next-line no-console + console.log(...args.map( + (arg): string => + { + return variable_to_string(arg); + } + )); + + return [nil]; +} + +export { print }; diff --git a/src/lib/table-size/table-size.mts b/src/lib/table-size/table-size.mts new file mode 100644 index 0000000..30dcfcc --- /dev/null +++ b/src/lib/table-size/table-size.mts @@ -0,0 +1,20 @@ +import type { VariableTable } from "../../_index.mjs"; + +function table_size(value: VariableTable): number +{ + let size: number = 0; + + for (let i: number = 1; i < value.table.size; ++i) + { + if (!value.table.has(i)) + { + return size; + } + + ++size; + } + + return size; +} + +export { table_size }; diff --git a/src/lib/type/type.mts b/src/lib/type/type.mts new file mode 100644 index 0000000..a830aad --- /dev/null +++ b/src/lib/type/type.mts @@ -0,0 +1,11 @@ +import type { Variable } from "../../_index.mjs"; +import type { Engine } from "../../engine.mjs"; +import { make_string } from "../../runtime/make-string/make-string.mjs"; + +// @ts-expect-error - engine is unused for now. +function type(engine: Engine, variable: Variable): Array +{ + return [make_string(variable.data_type)]; +} + +export { type }; diff --git a/src/lib/variable-to-string/variable-to-string.mts b/src/lib/variable-to-string/variable-to-string.mts new file mode 100644 index 0000000..eb79120 --- /dev/null +++ b/src/lib/variable-to-string/variable-to-string.mts @@ -0,0 +1,43 @@ +import type { Variable } from "../../_index.mjs"; +import { VariableKind } from "../../variable/definition/enum/variable-kind.enum.mjs"; + +function variable_to_string(variable: Variable, tables_done: Array = []): string +{ + switch (variable.data_type) + { + case VariableKind.Nil: + return "nil"; + case VariableKind.Boolean: + return variable.boolean ? "true" : "false"; + case VariableKind.Number: + return variable.number.toString(); + case VariableKind.String: + return variable.string; + case VariableKind.Function: + return ``; + case VariableKind.NativeFunction: + return ``; + case VariableKind.Table: + { + if (tables_done.includes(variable)) + { + return "..."; + } + + tables_done.push(variable); + + const items: Array = []; + + for (const [key, value] of variable.table.entries()) + { + const item: string = `${String(key)} = ${variable_to_string(value, tables_done)}`; + + items.push(item); + } + + return `{ ${items.join(", ")} }`; + } + } +} + +export { variable_to_string }; diff --git a/src/opcode.ts b/src/opcode.ts deleted file mode 100644 index 6ade1a3..0000000 --- a/src/opcode.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import type { Variable } from './runtime' -import type { Debug } from './lexer' - -export enum OpCode { - Load, - Store, - Push, - Pop, - Dup, - Swap, - - IterUpdateState, - IterNext, - IterJumpIfDone, - - NewTable, - LoadIndex, - StoreIndex, - - Add, - Subtract, - Multiply, - Divide, - FloorDivide, - Modulo, - Exponent, - Concat, - - BitAnd, - BitOr, - BitXOr, - BitNot, - BitShiftLeft, - BitShiftRight, - - Equals, - NotEquals, - LessThen, - LessThenEquals, - GreaterThen, - GreaterThenEquals, - And, - Or, - - Not, - Negate, - Length, - IsNotNil, - - StartBlock, - EndBlock, - MakeLocal, - Call, - Return, - Jump, - JumpIfNot, - JumpIf, - - StartStackChange, - EndStackChange, - ArgumentCount, - Break, -} - -export function op_code_name(op_code: OpCode): string -{ - switch (op_code) - { - case OpCode.Load: return 'Load' - case OpCode.Store: return 'Store' - case OpCode.Push: return 'Push' - case OpCode.Pop: return 'Pop' - case OpCode.Dup: return 'Dup' - case OpCode.Swap: return 'Swap' - case OpCode.IterUpdateState: return 'IterUpdateState' - case OpCode.IterNext: return 'IterNext' - case OpCode.IterJumpIfDone: return 'IterJumpIfDone' - case OpCode.NewTable: return 'NewTable' - case OpCode.LoadIndex: return 'LoadIndex' - case OpCode.StoreIndex: return 'StoreIndex' - case OpCode.Add: return 'Add' - case OpCode.Subtract: return 'Subtract' - case OpCode.Multiply: return 'Multiply' - case OpCode.Divide: return 'Divide' - case OpCode.FloorDivide: return 'FloorDivide' - case OpCode.Modulo: return 'Modulo' - case OpCode.Exponent: return 'Exponent' - case OpCode.Concat: return 'Concat' - case OpCode.BitAnd: return 'BitAnd' - case OpCode.BitOr: return 'BitOr' - case OpCode.BitXOr: return 'BitXOr' - case OpCode.BitNot: return 'BitNot' - case OpCode.BitShiftLeft: return 'BitShiftLeft' - case OpCode.BitShiftRight: return 'BitShiftRight' - case OpCode.Equals: return 'Equals' - case OpCode.NotEquals: return 'NotEquals' - case OpCode.LessThen: return 'LessThen' - case OpCode.LessThenEquals: return 'LessThenEquals' - case OpCode.GreaterThen: return 'GreaterThen' - case OpCode.GreaterThenEquals: return 'GreaterThenEquals' - case OpCode.And: return 'And' - case OpCode.Or: return 'Or' - case OpCode.Not: return 'Not' - case OpCode.Negate: return 'Negate' - case OpCode.Length: return 'Length' - case OpCode.IsNotNil: return 'IsNotNil' - case OpCode.StartBlock: return 'StartBlock' - case OpCode.EndBlock: return 'EndBlock' - case OpCode.MakeLocal: return 'MakeLocal' - case OpCode.Call: return 'Call' - case OpCode.Return: return 'Return' - case OpCode.Jump: return 'Jump' - case OpCode.JumpIfNot: return 'JumpIfNot' - case OpCode.JumpIf: return 'JumpIf' - case OpCode.StartStackChange: return 'StartStackChange' - case OpCode.EndStackChange: return 'EndStackChange' - case OpCode.ArgumentCount: return 'ArgumentCount' - case OpCode.Break: return 'Break[Debug]' - default: - throw new Error() - } -} - -export interface Op { - code: OpCode, - arg?: Variable, - debug: Debug, -} - -export interface Program { - code: Op[], - start: number, -} - diff --git a/src/opcode/_index.mts b/src/opcode/_index.mts new file mode 100644 index 0000000..e69de29 diff --git a/src/opcode/definition/enum/op-code.enum.mts b/src/opcode/definition/enum/op-code.enum.mts new file mode 100644 index 0000000..3606929 --- /dev/null +++ b/src/opcode/definition/enum/op-code.enum.mts @@ -0,0 +1,65 @@ +const OpCode = { + Load: "load", + Store: "store", + Push: "push", + Pop: "pop", + Dup: "dup", + Swap: "swap", + + IterUpdateState: "iter-update-state", + IterNext: "iter-next", + IterJumpIfDone: "iter-jump-if-done", + + NewTable: "new-table", + LoadIndex: "load-index", + StoreIndex: "store-index", + + Add: "add", + Subtract: "subtract", + Multiply: "multiply", + Divide: "divide", + FloorDivide: "floor-divide", + Modulo: "modulo", + Exponent: "exponent", + Concat: "concat", + + BitAnd: "bit-and", + BitOr: "bit-or", + BitXOr: "bit-xor", + BitNot: "bit-not", + BitShiftLeft: "bit-shift-left", + BitShiftRight: "bit-shift-right", + + Equals: "equals", + NotEquals: "not-equals", + LessThan: "less-than", + LessThanEquals: "less-than-equals", + GreaterThan: "greater-than", + GreaterThanEquals: "greater-than-equals", + + And: "and", + Or: "or", + Not: "not", + + Negate: "negate", + Length: "length", + IsNotNil: "is-not-nil", + + StartBlock: "start-block", + EndBlock: "end-block", + MakeLocal: "make-local", + Call: "call", + Return: "return", + Jump: "jump", + JumpIfNot: "jump-if-not", + JumpIf: "jump-if", + + StartStackChange: "start-stack-change", + EndStackChange: "end-stack-change", + ArgumentCount: "argument-count", + Break: "break", +} as const satisfies Record; + +type OpCodeEnum = typeof OpCode[keyof typeof OpCode]; + +export { OpCode, type OpCodeEnum }; diff --git a/src/opcode/definition/interface/op.interface.mts b/src/opcode/definition/interface/op.interface.mts new file mode 100644 index 0000000..be755fc --- /dev/null +++ b/src/opcode/definition/interface/op.interface.mts @@ -0,0 +1,11 @@ +import type { Variable } from "../../../_index.mjs"; +import type { DebugInterface } from "../../../lexer/definition/interface/debug.interface.mjs"; +import type { OpCodeEnum } from "../enum/op-code.enum.mjs"; + +interface OpInterface { + code: OpCodeEnum; + arg?: Variable; + debug: DebugInterface; +} + +export type { OpInterface }; diff --git a/src/opcode/definition/interface/program.interface.mts b/src/opcode/definition/interface/program.interface.mts new file mode 100644 index 0000000..27897ea --- /dev/null +++ b/src/opcode/definition/interface/program.interface.mts @@ -0,0 +1,8 @@ +import type { OpInterface } from "./op.interface.mjs"; + +interface ProgramInterface { + code: Array; + start: number; +} + +export type { ProgramInterface }; diff --git a/src/opcode/op-code-name/op-code-name.mts b/src/opcode/op-code-name/op-code-name.mts new file mode 100644 index 0000000..5eb9098 --- /dev/null +++ b/src/opcode/op-code-name/op-code-name.mts @@ -0,0 +1,111 @@ +import { OpCode, type OpCodeEnum } from "../definition/enum/op-code.enum.mjs"; + +// eslint-disable-next-line complexity, max-lines-per-function +function op_code_name(op_code: OpCodeEnum): string +{ + switch (op_code) + { + case OpCode.Load: + return "Load"; + case OpCode.Store: + return "Store"; + case OpCode.Push: + return "Push"; + case OpCode.Pop: + return "Pop"; + case OpCode.Dup: + return "Dup"; + case OpCode.Swap: + return "Swap"; + case OpCode.IterUpdateState: + return "IterUpdateState"; + case OpCode.IterNext: + return "IterNext"; + case OpCode.IterJumpIfDone: + return "IterJumpIfDone"; + case OpCode.NewTable: + return "NewTable"; + case OpCode.LoadIndex: + return "LoadIndex"; + case OpCode.StoreIndex: + return "StoreIndex"; + case OpCode.Add: + return "Add"; + case OpCode.Subtract: + return "Subtract"; + case OpCode.Multiply: + return "Multiply"; + case OpCode.Divide: + return "Divide"; + case OpCode.FloorDivide: + return "FloorDivide"; + case OpCode.Modulo: + return "Modulo"; + case OpCode.Exponent: + return "Exponent"; + case OpCode.Concat: + return "Concat"; + case OpCode.BitAnd: + return "BitAnd"; + case OpCode.BitOr: + return "BitOr"; + case OpCode.BitXOr: + return "BitXOr"; + case OpCode.BitNot: + return "BitNot"; + case OpCode.BitShiftLeft: + return "BitShiftLeft"; + case OpCode.BitShiftRight: + return "BitShiftRight"; + case OpCode.Equals: + return "Equals"; + case OpCode.NotEquals: + return "NotEquals"; + case OpCode.LessThan: + return "LessThan"; + case OpCode.LessThanEquals: + return "LessThanEquals"; + case OpCode.GreaterThan: + return "GreaterThan"; + case OpCode.GreaterThanEquals: + return "GreaterThanEquals"; + case OpCode.And: + return "And"; + case OpCode.Or: + return "Or"; + case OpCode.Not: + return "Not"; + case OpCode.Negate: + return "Negate"; + case OpCode.Length: + return "Length"; + case OpCode.IsNotNil: + return "IsNotNil"; + case OpCode.StartBlock: + return "StartBlock"; + case OpCode.EndBlock: + return "EndBlock"; + case OpCode.MakeLocal: + return "MakeLocal"; + case OpCode.Call: + return "Call"; + case OpCode.Return: + return "Return"; + case OpCode.Jump: + return "Jump"; + case OpCode.JumpIfNot: + return "JumpIfNot"; + case OpCode.JumpIf: + return "JumpIf"; + case OpCode.StartStackChange: + return "StartStackChange"; + case OpCode.EndStackChange: + return "EndStackChange"; + case OpCode.ArgumentCount: + return "ArgumentCount"; + case OpCode.Break: + return "Break[Debug]"; + } +} + +export { op_code_name }; diff --git a/src/optimizer.mts b/src/optimizer.mts new file mode 100644 index 0000000..94ebf1d --- /dev/null +++ b/src/optimizer.mts @@ -0,0 +1,638 @@ +import { isEnumValue } from "@vitruvius-labs/ts-predicate"; +import { ExpressionKind } from "./ast/definition/enum/expression-kind.enum.mjs"; +import { StatementKind } from "./ast/definition/enum/statement-kind.enum.mjs"; +import { ValueKind } from "./ast/definition/enum/value-kind.enum.mjs"; +import type { AssignmentInterface } from "./ast/definition/interface/assignment.interface.mjs"; +import type { ChunkInterface } from "./ast/definition/interface/chunk.interface.mjs"; +import type { ExpressionInterface } from "./ast/definition/interface/expression.interface.mjs"; +import type { ForInterface } from "./ast/definition/interface/for.interface.mjs"; +import type { IfBlockInterface } from "./ast/definition/interface/if-block.interface.mjs"; +import type { NumericForInterface } from "./ast/definition/interface/numeric-for.interface.mjs"; +import type { RepeatInterface } from "./ast/definition/interface/repeat.interface.mjs"; +import type { StatementInterface } from "./ast/definition/interface/statement.interface.mjs"; +import type { ValueInterface } from "./ast/definition/interface/value.interface.mjs"; +import type { WhileInterface } from "./ast/definition/interface/while.interface.mjs"; + +const CONSTANT_VALUES = [ + ValueKind.NilLiteral, + ValueKind.NumberLiteral, + ValueKind.BooleanLiteral, + ValueKind.StringLiteral, +]; + +function compute_arithmetic_operation( + expression: ExpressionInterface, + operation: (a: number, b: number) => number, + constants: Map +): ValueInterface | undefined +{ + const lhs = compute_constant_expression(expression.lhs, constants); + const rhs = compute_constant_expression(expression.rhs, constants); + + if (lhs === undefined || rhs === undefined) + { + return undefined; + } + + return { + kind: ValueKind.NumberLiteral, + number: operation(lhs.number ?? 0, rhs.number ?? 0), + token: expression.token, + }; +} + +function compute_comparison_operation( + expression: ExpressionInterface, + operation: (a: number | string, b: number | string) => boolean, + constants: Map +): ValueInterface | undefined +{ + const lhs = compute_constant_expression(expression.lhs, constants); + const rhs = compute_constant_expression(expression.rhs, constants); + + if (lhs === undefined || rhs === undefined) + { + return undefined; + } + + return { + kind: ValueKind.BooleanLiteral, + boolean: operation(lhs.number ?? 0, rhs.number ?? 0), + token: expression.token, + }; +} + +function compute_logical_operation( + expression: ExpressionInterface, + operation: typeof ExpressionKind.And | typeof ExpressionKind.Or, + constants: Map +): ValueInterface | undefined +{ + const lhs = compute_constant_expression(expression.lhs, constants); + const rhs = compute_constant_expression(expression.rhs, constants); + + if (lhs === undefined || rhs === undefined) + { + return undefined; + } + + const lhs_falsy: boolean = lhs.kind === ValueKind.NilLiteral || lhs.kind === ValueKind.BooleanLiteral && !(lhs.boolean ?? true); + + if (operation === ExpressionKind.And) + { + return lhs_falsy ? lhs : rhs; + } + + return lhs_falsy ? rhs : lhs; +} + +// @TODO: Fix complexity warning +// eslint-disable-next-line max-lines-per-function, complexity +function compute_constant_expression( + expression: ExpressionInterface | undefined, + constants: Map +): ValueInterface | undefined +{ + if (expression === undefined) + { + return undefined; + } + + switch (expression.kind) + { + case ExpressionKind.Value: + { + if (expression.value === undefined) + { + return undefined; + } + + const value = expression.value; + + if (isEnumValue(value.kind, CONSTANT_VALUES)) + { + return value; + } + + if (value.kind === ValueKind.Variable && constants.has(value.identifier ?? "")) + { + return constants.get(value.identifier ?? ""); + } + + return undefined; + } + + case ExpressionKind.Addition: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a + b; + }, + constants + ); + case ExpressionKind.Subtract: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a - b; + }, + constants + ); + case ExpressionKind.Multiplication: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a * b; + }, + constants + ); + case ExpressionKind.Division: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a / b; + }, + constants + ); + case ExpressionKind.FloorDivision: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return Math.floor(a / b); + }, + constants + ); + case ExpressionKind.Modulo: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a % b; + }, + constants + ); + case ExpressionKind.Exponent: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return Math.pow(a, b); + }, + constants + ); + case ExpressionKind.Concat: + return undefined; + + case ExpressionKind.BitAnd: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a & b; + }, + constants + ); + case ExpressionKind.BitOr: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a | b; + }, + constants + ); + case ExpressionKind.BitXOr: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a ^ b; + }, + constants + ); + case ExpressionKind.BitShiftLeft: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a << b; + }, + constants + ); + case ExpressionKind.BitShiftRight: + return compute_arithmetic_operation( + expression, + (a: number, b: number): number => + { + return a >> b; + }, + constants + ); + case ExpressionKind.BitNot: + return undefined; + + case ExpressionKind.Equals: + return compute_comparison_operation( + expression, + (a: unknown, b: unknown): boolean => + { + return a === b; + }, + constants + ); + case ExpressionKind.NotEquals: + return compute_comparison_operation( + expression, + (a: unknown, b: unknown): boolean => + { + return a !== b; + }, + constants + ); + case ExpressionKind.LessThan: + return compute_comparison_operation( + expression, + (a: number | string, b: number | string): boolean => + { + return a < b; + }, + constants + ); + case ExpressionKind.LessThanEquals: + return compute_comparison_operation( + expression, + (a: number | string, b: number | string): boolean => + { + return a <= b; + }, + constants + ); + case ExpressionKind.GreaterThan: + return compute_comparison_operation( + expression, + (a: number | string, b: number | string): boolean => + { + return a > b; + }, + constants + ); + case ExpressionKind.GreaterThanEquals: + return compute_comparison_operation( + expression, + (a: number | string, b: number | string): boolean => + { + return a >= b; + }, + constants + ); + case ExpressionKind.And: + return compute_logical_operation( + expression, + ExpressionKind.And, + constants + ); + case ExpressionKind.Or: + return compute_logical_operation( + expression, + ExpressionKind.Or, + constants + ); + + case ExpressionKind.Not: + { + const lhs = compute_constant_expression(expression.lhs, constants); + + if (lhs === undefined) + { + return undefined; + } + + return { + kind: ValueKind.BooleanLiteral, + boolean: !(lhs.boolean ?? false), + token: expression.token, + }; + } + + case ExpressionKind.Negate: + { + const lhs = compute_constant_expression(expression.lhs, constants); + + if (lhs === undefined) + { + return undefined; + } + + return { + kind: ValueKind.NumberLiteral, + number: -(lhs.number ?? 0), + token: expression.token, + }; + } + + case ExpressionKind.Length: + return undefined; + + case ExpressionKind.Call: + return undefined; + + case ExpressionKind.Index: + return undefined; + } +} + +function optimize_expression( + expression: ExpressionInterface | undefined, + constants: Map +): void +{ + if (expression === undefined) + { + return; + } + + if (expression.value?.function !== undefined) + { + optimize_chunk(expression.value.function.body, constants); + + return; + } + + const value = compute_constant_expression(expression, constants); + + if (value !== undefined) + { + expression.kind = ExpressionKind.Value; + expression.value = value; + + return; + } + + optimize_expression(expression.lhs, constants); + optimize_expression(expression.rhs, constants); + optimize_expression(expression.expression, constants); + optimize_expression(expression.index, constants); + + for (const argument of expression.arguments ?? []) + { + optimize_expression(argument, constants); + } +} + +function mark_local_constants(assignment: AssignmentInterface, constants: Map): void +{ + for (const [index, rhs] of assignment.rhs.entries()) + { + if (index >= assignment.lhs.length) + { + continue; + } + + const lhs = assignment.lhs[index]; + + if (lhs === undefined) + { + throw new Error(); + } + + if (lhs.kind !== ExpressionKind.Value) + { + continue; + } + + if (lhs.value?.identifier === undefined) + { + continue; + } + + const name = lhs.value.identifier; + const value = compute_constant_expression(rhs, constants); + + if (value === undefined) + { + continue; + } + + constants.set(name, value); + } +} + +function unmark_constants_if_reassigned(assignment: AssignmentInterface, constants: Map): void +{ + for (const lhs of assignment.lhs) + { + if (lhs.kind !== ExpressionKind.Value) + { + continue; + } + + if (lhs.value?.identifier === undefined) + { + continue; + } + + const name = lhs.value.identifier; + + constants.delete(name); + } +} + +function optimize_assignment( + assignment: AssignmentInterface | undefined, + constants: Map +): void +{ + if (assignment === undefined) + { + return; + } + + if (assignment.local) + { + mark_local_constants(assignment, constants); + } + else + { + unmark_constants_if_reassigned(assignment, constants); + } + + for (const rhs of assignment.rhs) + { + optimize_expression(rhs, constants); + } +} + +function remove_constant_local_assignments( + chunk: ChunkInterface, + constants: Map +): void +{ + for (const statement of chunk.statements) + { + if (statement.assignment === undefined || !statement.assignment.local) + { + continue; + } + + const assignment = statement.assignment; + + for (const name of constants.keys()) + { + const index = assignment.lhs.findIndex( + (x: ExpressionInterface): boolean => + { + return x.value?.identifier === name; + } + ); + + if (index < 0) + { + continue; + } + + assignment.lhs.splice(index, 1); + assignment.rhs.splice(index, 1); + } + } + + chunk.statements = chunk.statements.filter( + (x: StatementInterface): boolean => + { + return x.assignment === undefined || x.assignment.lhs.length > 0; + } + ); +} + +function optimize_if(if_block: IfBlockInterface | undefined, constants: Map): void +{ + if (if_block === undefined) + { + return; + } + + optimize_expression(if_block.condition, constants); + optimize_chunk(if_block.body, constants); +} + +function optimize_while(while_block: WhileInterface | undefined, constants: Map): void +{ + if (while_block === undefined) + { + return; + } + + optimize_expression(while_block.condition, constants); + optimize_chunk(while_block.body, constants); +} + +function optimize_for(for_block: ForInterface | undefined, constants: Map): void +{ + if (for_block === undefined) + { + return; + } + + optimize_expression(for_block.iterator, constants); + optimize_chunk(for_block.body, constants); +} + +function optimize_numeric_for(numeric_for_block: NumericForInterface | undefined, constants: Map): void +{ + if (numeric_for_block === undefined) + { + return; + } + + optimize_expression(numeric_for_block.start, constants); + optimize_expression(numeric_for_block.end, constants); + optimize_expression(numeric_for_block.step, constants); + optimize_chunk(numeric_for_block.body, constants); +} + +function optimize_repeat(repeat_block: RepeatInterface | undefined, constants: Map): void +{ + if (repeat_block === undefined) + { + return; + } + + optimize_expression(repeat_block.condition, constants); + optimize_chunk(repeat_block.body, constants); +} + +export function optimize_chunk(chunk: ChunkInterface, parent_constants?: Map): void +{ + const constants = new Map(parent_constants); + + for (const statement of chunk.statements) + { + switch (statement.kind) + { + case StatementKind.Assignment: + optimize_assignment(statement.assignment, constants); + break; + + case StatementKind.Expression: + optimize_expression(statement.expression, constants); + break; + + case StatementKind.If: + optimize_if(statement.if, constants); + break; + + case StatementKind.While: + optimize_while(statement.if, constants); + break; + + case StatementKind.For: + optimize_for(statement.for, constants); + break; + + case StatementKind.NumericFor: + optimize_numeric_for(statement.numeric_for, constants); + break; + + case StatementKind.Repeat: + optimize_repeat(statement.repeat, constants); + break; + + case StatementKind.Do: + if (statement.do !== undefined) + { + optimize_chunk(statement.do.body, constants); + } + + break; + + case StatementKind.Return: + for (const expression of statement.return?.values ?? []) + { + optimize_expression(expression, constants); + } + + break; + + case StatementKind.Local: + case StatementKind.Break: + break; + } + } + + if (parent_constants !== undefined) + { + for (const name of [...parent_constants.keys()]) + { + if (!constants.has(name)) + { + parent_constants.delete(name); + } + } + } + + remove_constant_local_assignments(chunk, constants); +} diff --git a/src/optimizer.ts b/src/optimizer.ts deleted file mode 100644 index 570f8d0..0000000 --- a/src/optimizer.ts +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import type { Chunk, Expression } from './ast' -import type { IfBlock, While, For, NumericFor, Repeat } from './ast' -import type { Assignment, Value } from './ast' -import { StatementKind, ExpressionKind, ValueKind } from './ast' - -const CONSTANT_VALUES = [ - ValueKind.NilLiteral, - ValueKind.NumberLiteral, - ValueKind.BooleanLiteral, - ValueKind.StringLiteral, -] - -function compule_arithmatic_operation(expression: Expression, - operation: (a: number, b: number) => number, - constants: Map): Value | undefined -{ - const lhs = compute_constant_expression(expression.lhs, constants) - const rhs = compute_constant_expression(expression.rhs, constants) - if (lhs == undefined || rhs == undefined) - return undefined - - return { - kind: ValueKind.NumberLiteral, - number: operation((lhs?.number ?? 0), (rhs?.number ?? 0)), - token: expression.token, - } -} - -function compule_comparison_operation(expression: Expression, - operation: (a: number, b: number) => boolean, - constants: Map): Value | undefined -{ - const lhs = compute_constant_expression(expression.lhs, constants) - const rhs = compute_constant_expression(expression.rhs, constants) - if (lhs == undefined || rhs == undefined) - return undefined - - return { - kind: ValueKind.BooleanLiteral, - boolean: operation((lhs?.number ?? 0), (rhs?.number ?? 0)), - token: expression.token, - } -} - -function compule_logical_operation(expression: Expression, - operation: (a: boolean, b: boolean) => boolean, - constants: Map): Value | undefined -{ - const lhs = compute_constant_expression(expression.lhs, constants) - const rhs = compute_constant_expression(expression.rhs, constants) - if (lhs == undefined || rhs == undefined) - return undefined - - return { - kind: ValueKind.BooleanLiteral, - boolean: operation((lhs?.boolean ?? false), (rhs?.boolean ?? false)), - token: expression.token, - } -} - -function compute_constant_expression(expression: Expression | undefined, - constants: Map): Value | undefined -{ - if (expression == undefined) - return undefined - - switch (expression.kind) - { - case ExpressionKind.Value: - { - if (expression.value == undefined) - return undefined - - const value = expression.value - if (CONSTANT_VALUES.includes(value.kind)) - return value - if (value.kind == ValueKind.Variable && constants.has(value.identifier ?? '')) - return constants.get(value.identifier ?? '') - return undefined - } - - case ExpressionKind.Addition: return compule_arithmatic_operation(expression, (a, b) => a + b, constants) - case ExpressionKind.Subtract: return compule_arithmatic_operation(expression, (a, b) => a - b, constants) - case ExpressionKind.Multiplication: return compule_arithmatic_operation(expression, (a, b) => a * b, constants) - case ExpressionKind.Division: return compule_arithmatic_operation(expression, (a, b) => a / b, constants) - case ExpressionKind.FloorDivision: return compule_arithmatic_operation(expression, (a, b) => Math.floor(a / b), constants) - case ExpressionKind.Modulo: return compule_arithmatic_operation(expression, (a, b) => a % b, constants) - case ExpressionKind.Exponent: return compule_arithmatic_operation(expression, (a, b) => Math.pow(a, b), constants) - case ExpressionKind.Concat: return undefined - - case ExpressionKind.BitAnd: return compule_arithmatic_operation(expression, (a, b) => a & b, constants) - case ExpressionKind.BitOr: return compule_arithmatic_operation(expression, (a, b) => a | b, constants) - case ExpressionKind.BitXOr: return compule_arithmatic_operation(expression, (a, b) => a ^ b, constants) - case ExpressionKind.BitShiftLeft: return compule_arithmatic_operation(expression, (a, b) => a << b, constants) - case ExpressionKind.BitShiftRight: return compule_arithmatic_operation(expression, (a, b) => a >> b, constants) - case ExpressionKind.BitNot: return undefined - - case ExpressionKind.Equals: return compule_comparison_operation(expression, (a, b) => a == b, constants) - case ExpressionKind.NotEquals: return compule_comparison_operation(expression, (a, b) => a != b, constants) - case ExpressionKind.LessThen: return compule_comparison_operation(expression, (a, b) => a < b, constants) - case ExpressionKind.LessThenEquals: return compule_comparison_operation(expression, (a, b) => a <= b, constants) - case ExpressionKind.GreaterThen: return compule_comparison_operation(expression, (a, b) => a > b, constants) - case ExpressionKind.GreaterThenEquals: return compule_comparison_operation(expression, (a, b) => a >= b, constants) - case ExpressionKind.And: return compule_logical_operation(expression, (a, b) => a && b, constants) - case ExpressionKind.Or: return compule_logical_operation(expression, (a, b) => a || b, constants) - - case ExpressionKind.Not: - { - const lhs = compute_constant_expression(expression.lhs, constants) - if (lhs == undefined) - return undefined - - return { - kind: ValueKind.BooleanLiteral, - boolean: !(lhs.boolean ?? false), - token: expression.token, - } - } - - case ExpressionKind.Negate: - { - const lhs = compute_constant_expression(expression.lhs, constants) - if (lhs == undefined) - return undefined - - return { - kind: ValueKind.NumberLiteral, - number: -(lhs.number ?? false), - token: expression.token, - } - } - - case ExpressionKind.Length: - return undefined - } -} - -function optimize_expression(expression: Expression | undefined, - constants: Map) -{ - if (expression == undefined) - return - - if (expression.value?.function != undefined) - { - optimize_chunk(expression.value.function.body, constants) - return - } - - const value = compute_constant_expression(expression, constants) - if (value != undefined) - { - expression.kind = ExpressionKind.Value - expression.value = value - return - } - - optimize_expression(expression.lhs, constants) - optimize_expression(expression.rhs, constants) - optimize_expression(expression.expression, constants) - optimize_expression(expression.index, constants) - - for (const argument of expression.arguments ?? []) - optimize_expression(argument, constants) -} - -function mark_local_constants(assignment: Assignment, constants: Map) -{ - for (const [index, rhs] of assignment.rhs.entries()) - { - if (index >= assignment.lhs.length) - continue - - const lhs = assignment.lhs[index] - if (lhs.kind != ExpressionKind.Value) - continue - if (lhs.value?.identifier == undefined) - continue - - const name = lhs.value.identifier - const value = compute_constant_expression(rhs, constants) - if (value == undefined) - continue - - constants.set(name, value) - } -} - -function unmark_constants_if_reassigned(assignment: Assignment, constants: Map) -{ - for (const lhs of assignment.lhs) - { - if (lhs.kind != ExpressionKind.Value) - continue - if (lhs.value?.identifier == undefined) - continue - - const name = lhs.value.identifier - constants.delete(name) - } -} - -function optimize_assignment(assignment: Assignment | undefined, - constants: Map) -{ - if (assignment == undefined) - return - - if (assignment.local) - mark_local_constants(assignment, constants) - else - unmark_constants_if_reassigned(assignment, constants) - - for (const rhs of assignment.rhs) - optimize_expression(rhs, constants) -} - -function remove_constant_local_assignments(chunk: Chunk, - constants: Map) -{ - for (const statement of chunk.statements) - { - if (statement.assignment == undefined || !statement.assignment.local) - continue - - const assignment = statement.assignment - for (const name of constants.keys()) - { - const index = assignment.lhs.findIndex( - x => x.value?.identifier == name) - - if (index < 0) - continue - assignment.lhs.splice(index, 1) - assignment.rhs.splice(index, 1) - } - } - - chunk.statements = chunk.statements - .filter(x => x.assignment == undefined || x.assignment.lhs.length > 0) -} - -function optimize_if(if_block: IfBlock | undefined, constants: Map) -{ - if (if_block == undefined) - return - - optimize_expression(if_block.condition, constants) - optimize_chunk(if_block.body, constants) -} - -function optimize_while(while_block: While | undefined, constants: Map) -{ - if (while_block == undefined) - return - - optimize_expression(while_block.condition, constants) - optimize_chunk(while_block.body, constants) -} - -function optimize_for(for_block: For | undefined, constants: Map) -{ - if (for_block == undefined) - return - - optimize_expression(for_block.itorator, constants) - optimize_chunk(for_block.body, constants) -} - -function optimize_numeric_for(numeric_for_block: NumericFor | undefined, constants: Map) -{ - if (numeric_for_block == undefined) - return - - optimize_expression(numeric_for_block.start, constants) - optimize_expression(numeric_for_block.end, constants) - optimize_expression(numeric_for_block.step, constants) - optimize_chunk(numeric_for_block.body, constants) -} - -function optimize_repeat(repeat_block: Repeat | undefined, constants: Map) -{ - if (repeat_block == undefined) - return - - optimize_expression(repeat_block.condition, constants) - optimize_chunk(repeat_block.body, constants) -} - -export function optimize_chunk(chunk: Chunk, parent_constants?: Map) -{ - const constants = new Map(parent_constants) - for (const statement of chunk.statements) - { - switch (statement.kind) - { - case StatementKind.Assignment: - optimize_assignment(statement.assignment, constants) - break - - case StatementKind.Expression: - optimize_expression(statement.expression, constants) - break - - case StatementKind.If: - optimize_if(statement.if, constants) - break - - case StatementKind.While: - optimize_while(statement.if, constants) - break - - case StatementKind.For: - optimize_for(statement.for, constants) - break - - case StatementKind.NumericFor: - optimize_numeric_for(statement.numeric_for, constants) - break - - case StatementKind.Repeat: - optimize_repeat(statement.repeat, constants) - break - - case StatementKind.Do: - if (statement.do != undefined) - optimize_chunk(statement.do.body, constants) - break - - case StatementKind.Return: - for (const expression of statement.return?.values ?? []) - optimize_expression(expression, constants) - break - - case StatementKind.Local: - case StatementKind.Break: - break - } - } - - if (parent_constants != undefined) - { - for (const name of [...parent_constants.keys()]) - { - if (!constants.has(name)) - parent_constants.delete(name) - } - } - - remove_constant_local_assignments(chunk, constants) -} - diff --git a/src/parser.mts b/src/parser.mts new file mode 100644 index 0000000..efd935b --- /dev/null +++ b/src/parser.mts @@ -0,0 +1,1015 @@ +import { isEnumValue } from "@vitruvius-labs/ts-predicate"; +import { ExpressionKind } from "./ast/definition/enum/expression-kind.enum.mjs"; +import { StatementKind } from "./ast/definition/enum/statement-kind.enum.mjs"; +import { ValueKind } from "./ast/definition/enum/value-kind.enum.mjs"; +import type { ChunkInterface } from "./ast/definition/interface/chunk.interface.mjs"; +import type { ElseIfBlockInterface } from "./ast/definition/interface/else-if-block.interface.mjs"; +import type { ExpressionInterface } from "./ast/definition/interface/expression.interface.mjs"; +import type { StatementInterface } from "./ast/definition/interface/statement.interface.mjs"; +import type { ValueInterface } from "./ast/definition/interface/value.interface.mjs"; +import type { TokenStream } from "./lexer.mjs"; +import { TokenKind } from "./lexer/definition/enum/token-kind.enum.mjs"; +import type { TokenInterface } from "./lexer/definition/interface/token.interface.mjs"; +import { token_kind_to_string } from "./lexer/token-kind-to-string/token-kind-to-string.mjs"; +import { consume } from "./parser/consume/consume.mjs"; +import { to_error } from "./parser/error/to-error.mjs"; +import { expect } from "./parser/expect/expect.mjs"; +import { operation_type_to_expression_kind } from "./parser/operation-type-to-expression-kind/operation-type-to-expression-kind.mjs"; +import { parse_function_params } from "./parser/parse-function-params/parse-function-params.mjs"; +import { parse_local_statement } from "./parser/parse-local-statement/parse-local-statement.mjs"; +import { parse } from "./parser/parse/parse.mjs"; +import { unary_type_to_expression_kind } from "./parser/unary-type-to-expression-kind/unary-type-to-expression-kind.mjs"; +import { getDebug } from "./lexer/utility/get-debug.mjs"; +import { isUnaryOperatorToken } from "./parser/is-unary-operator/is-unary-operator.mjs"; +import { get_orders } from "./parser/get-orders/get-orders.mjs"; + +function parse_table_key(stream: TokenStream): ExpressionInterface | Error +{ + if (consume(stream, TokenKind.OpenSquare)) + { + const element = parse_expression(stream); + + if (element instanceof Error) + { + return element; + } + + const close_square = expect(stream, TokenKind.CloseSquare); + + if (close_square instanceof Error) + { + return close_square; + } + + return element; + } + + const value = parse_value(stream); + + if (value instanceof Error) + { + return value; + } + + if (value.kind === ValueKind.Variable) + { + value.kind = ValueKind.StringLiteral; + value.string = value.identifier; + value.identifier = undefined; + } + + return { + kind: ExpressionKind.Value, + token: value.token, + value: value, + }; +} + +function parse_table(stream: TokenStream): ValueInterface | Error +{ + const squigly_open = expect(stream, TokenKind.SquiglyOpen); + + if (squigly_open instanceof Error) + { + return squigly_open; + } + + const elements: Map = new Map(); + let current_numeric_key = 1; + + while (stream.peek().kind !== TokenKind.SquiglyClose) + { + const element: ExpressionInterface | Error = parse_table_key(stream); + + if (element instanceof Error) + { + return element; + } + + const is_assign: boolean = consume(stream, TokenKind.Assign); + + if (is_assign) + { + const value = parse_expression(stream); + + if (value instanceof Error) + { + return value; + } + + elements.set(element, value); + } + + if (!is_assign) + { + const key_token = { + kind: TokenKind.NumberLiteral, + data: current_numeric_key.toString(), + debug: getDebug(stream.peek()), + }; + + const key = { + kind: ExpressionKind.Value, + token: key_token, + value: { + kind: ValueKind.NumberLiteral, + token: key_token, + number: current_numeric_key, + }, + }; + + current_numeric_key = current_numeric_key + 1; + elements.set(key, element); + } + + if (!consume(stream, TokenKind.Comma)) + { + break; + } + } + + const close_squigly = expect(stream, TokenKind.SquiglyClose); + + if (close_squigly instanceof Error) + { + return close_squigly; + } + + return { + kind: ValueKind.TableLiteral, + token: squigly_open, + table: elements, + }; +} + +function parse_value(stream: TokenStream): ValueInterface | Error +{ + const token = stream.peek(); + + // eslint-disable-next-line @ts/switch-exhaustiveness-check + switch (token.kind) + { + case TokenKind.NumberLiteral: + return { kind: ValueKind.NumberLiteral, token: stream.next(), number: parseFloat(token.data) }; + case TokenKind.BooleanLiteral: + return { kind: ValueKind.BooleanLiteral, token: stream.next(), boolean: token.data === "true" }; + case TokenKind.StringLiteral: + return { kind: ValueKind.StringLiteral, token: stream.next(), string: token.data }; + case TokenKind.NilLiteral: + return { kind: ValueKind.NilLiteral, token: stream.next() }; + case TokenKind.Identifier: + return { kind: ValueKind.Variable, token: stream.next(), identifier: token.data }; + + case TokenKind.SquiglyOpen: + return parse_table(stream); + case TokenKind.FunctionLike: + return parse_function_value(stream.next(), stream); + + default: + return to_error(token, `Expected value, got ${token_kind_to_string(token.kind)} instead`); + } +} + +function parse_unary_operator(stream: TokenStream): ExpressionInterface | Error +{ + const operator_token = stream.next(); + const operator = unary_type_to_expression_kind(operator_token.kind); + + const expression = parse_expression(stream); + + if (expression instanceof Error) + { + return expression; + } + + return { + kind: operator, + token: operator_token, + expression: expression, + }; +} + +function parse_value_expression(stream: TokenStream): ExpressionInterface | Error +{ + if (consume(stream, TokenKind.OpenBrace)) + { + const sub_expression = parse_expression(stream); + + if (sub_expression instanceof Error) + { + return sub_expression; + } + + const close_brace = expect(stream, TokenKind.CloseBrace); + + if (close_brace instanceof Error) + { + return close_brace; + } + + return sub_expression; + } + + if (isUnaryOperatorToken(stream.peek().kind)) + { + return parse_unary_operator(stream); + } + + const value = parse_value(stream); + + if (value instanceof Error) + { + return value; + } + + const expression_value = { + kind: ExpressionKind.Value, + token: value.token, + value: value, + }; + + return parse_access_expression(expression_value, stream); +} + +function parse_call(callable: ExpressionInterface, stream: TokenStream): ExpressionInterface | Error +{ + const open_brace: TokenInterface = stream.next(); + const args: Array = []; + + while (stream.peek().kind !== TokenKind.CloseBrace) + { + const argument = parse_expression(stream); + + if (argument instanceof Error) + { + break; + } + + args.push(argument); + + if (!consume(stream, TokenKind.Comma)) + { + break; + } + } + + const close_brace = expect(stream, TokenKind.CloseBrace); + + if (close_brace instanceof Error) + { + return close_brace; + } + + return parse_access_expression({ + kind: ExpressionKind.Call, + token: open_brace, + expression: callable, + arguments: args, + }, stream); +} + +function parse_index(table: ExpressionInterface, stream: TokenStream): ExpressionInterface | Error +{ + const open_square = stream.next(); + const index = parse_expression(stream); + + if (index instanceof Error) + { + return index; + } + + const close_square = expect(stream, TokenKind.CloseSquare); + + if (close_square instanceof Error) + { + return close_square; + } + + return parse_access_expression( + { + kind: ExpressionKind.Index, + token: open_square, + expression: table, + index: index, + }, + stream + ); +} + +function parse_dot(table: ExpressionInterface, stream: TokenStream): ExpressionInterface | Error +{ + const dot = stream.next(); + const index = expect(stream, TokenKind.Identifier); + + if (index instanceof Error) + { + return index; + } + + return parse_access_expression({ + kind: ExpressionKind.Index, + expression: table, + token: dot, + index: { + kind: ExpressionKind.Value, + token: index, + value: { + kind: ValueKind.StringLiteral, + token: index, + string: index.data, + }, + }, + }, stream); +} + +function parse_single_argument_call(callable: ExpressionInterface, stream: TokenStream): ExpressionInterface | Error +{ + const argument = parse_expression(stream); + + if (argument instanceof Error) + { + return argument; + } + + return { + kind: ExpressionKind.Call, + token: callable.token, + expression: callable, + arguments: [argument], + }; +} + +function parse_access_expression(expression: ExpressionInterface, stream: TokenStream): ExpressionInterface | Error +{ + // eslint-disable-next-line @ts/switch-exhaustiveness-check + switch (stream.peek().kind) + { + case TokenKind.OpenBrace: + return parse_call(expression, stream); + + case TokenKind.OpenSquare: + return parse_index(expression, stream); + + case TokenKind.Dot: + return parse_dot(expression, stream); + + case TokenKind.SquiglyOpen: + case TokenKind.StringLiteral: + return parse_single_argument_call(expression, stream); + } + + return expression; +} + +function parse_operation( + stream: TokenStream, + order: number +): ExpressionInterface | Error +{ + if (order >= get_orders().length) + { + return parse_value_expression(stream); + } + + let lhs = parse_operation(stream, order + 1); + + if (lhs instanceof Error) + { + return lhs; + } + + const orders_order = get_orders()[order]; + + if (orders_order === undefined) + { + throw new Error(); + } + + while (isEnumValue(stream.peek().kind, orders_order)) + { + const operation_type = stream.next(); + const rhs = parse_operation(stream, order + 1); + + if (rhs instanceof Error) + { + return rhs; + } + + const expression_kind = operation_type_to_expression_kind(operation_type.kind); + + lhs = { + kind: expression_kind, + token: operation_type, + lhs: lhs, + rhs: rhs, + }; + } + + return lhs; +} + +function parse_expression(stream: TokenStream): ExpressionInterface | Error +{ + if (stream.peek().kind === TokenKind.BitXOrNot) + { + return parse_unary_operator(stream); + } + + return parse_operation(stream, 0); +} + +function parse_assign_or_expression(stream: TokenStream): StatementInterface | Error +{ + const local = expect(stream, TokenKind.Local); + const lhs: Array = []; + + while (lhs.length === 0 || consume(stream, TokenKind.Comma)) + { + const lvalue = parse_expression(stream); + + if (lvalue instanceof Error) + { + return lvalue; + } + + lhs.push(lvalue); + } + + const assign = expect(stream, TokenKind.Assign); + + if (assign instanceof Error) + { + if (local instanceof Error) + { + // @TODO: Investigate as lhs[0] seems to always be undefined + return { kind: StatementKind.Expression, expression: lhs[0] }; + } + + return parse_local_statement(local, lhs); + } + + const rhs: Array = []; + + while (rhs.length === 0 || consume(stream, TokenKind.Comma)) + { + const rvalue = parse_expression(stream); + + if (rvalue instanceof Error) + { + return rvalue; + } + + rhs.push(rvalue); + } + + return { + kind: StatementKind.Assignment, + assignment: { + local: !(local instanceof Error), + lhs: lhs.reverse(), + rhs: rhs, + token: assign, + }, + }; +} + +function parse_return(stream: TokenStream): StatementInterface | Error +{ + const ret = expect(stream, TokenKind.Return); + + if (ret instanceof Error) + { + return ret; + } + + const values: Array = []; + + while (values.length === 0 || consume(stream, TokenKind.Comma)) + { + const value = parse_expression(stream); + + if (value instanceof Error) + { + if (values.length > 0) + { + return value; + } + + break; + } + + values.push(value); + } + + if (values.length === 0) + { + values.push({ + kind: ExpressionKind.Value, + token: ret, + value: { + kind: ValueKind.NilLiteral, + token: ret, + }, + }); + } + + return { + kind: StatementKind.Return, + return: { + values: values, + token: ret, + }, + }; +} + +function parse_if(stream: TokenStream): StatementInterface | Error +{ + const if_token = expect(stream, TokenKind.If); + + if (if_token instanceof Error) + { + return if_token; + } + + const condition = parse_expression(stream); + + if (condition instanceof Error) + { + return condition; + } + + const then = expect(stream, TokenKind.Then); + + if (then instanceof Error) + { + return then; + } + + const body = parse(stream, TokenKind.Else, TokenKind.ElseIf, TokenKind.End); + + if (body instanceof Error) + { + return body; + } + + const else_if_bodies: Array = []; + let else_body: ChunkInterface | undefined = undefined; + + while (consume(stream, TokenKind.ElseIf)) + { + const condition = parse_expression(stream); + + if (condition instanceof Error) + { + return condition; + } + + const then = expect(stream, TokenKind.Then); + + if (then instanceof Error) + { + return then; + } + + const chunk = parse(stream, TokenKind.End, TokenKind.ElseIf, TokenKind.Else); + + if (chunk instanceof Error) + { + return chunk; + } + + else_if_bodies.push({ + body: chunk, + condition: condition, + token: then, + }); + } + + if (consume(stream, TokenKind.Else)) + { + const chunk = parse(stream, TokenKind.End); + + if (chunk instanceof Error) + { + return chunk; + } + + else_body = chunk; + } + + const end = expect(stream, TokenKind.End); + + if (end instanceof Error) + { + return end; + } + + return { + kind: StatementKind.If, + if: { + condition: condition, + body: body, + else_if_bodies: else_if_bodies, + else_body: else_body, + token: if_token, + }, + }; +} + +function parse_while(stream: TokenStream): StatementInterface | Error +{ + const while_token = expect(stream, TokenKind.While); + + if (while_token instanceof Error) + { + return while_token; + } + + const condition = parse_expression(stream); + + if (condition instanceof Error) + { + return condition; + } + + const do_token = expect(stream, TokenKind.Do); + + if (do_token instanceof Error) + { + return do_token; + } + + const body = parse(stream, TokenKind.End); + + if (body instanceof Error) + { + return body; + } + + consume(stream, TokenKind.End); + + return { + kind: StatementKind.While, + while: { + condition: condition, + body: body, + token: while_token, + }, + }; +} + +function parse_numeric_for(index: TokenInterface, stream: TokenStream): StatementInterface | Error +{ + const start = parse_expression(stream); + + if (start instanceof Error) + { + return start; + } + + const comma = expect(stream, TokenKind.Comma); + + if (comma instanceof Error) + { + return comma; + } + + const end = parse_expression(stream); + + if (end instanceof Error) + { + return end; + } + + let step: ExpressionInterface | undefined = undefined; + + if (consume(stream, TokenKind.Comma)) + { + const expression = parse_expression(stream); + + if (expression instanceof Error) + { + return expression; + } + + step = expression; + } + + const do_token = expect(stream, TokenKind.Do); + + if (do_token instanceof Error) + { + return do_token; + } + + const body = parse(stream, TokenKind.End); + + if (body instanceof Error) + { + return body; + } + + consume(stream, TokenKind.End); + + return { + kind: StatementKind.NumericFor, + numeric_for: { + index: index, + start: start, + end: end, + step: step, + body: body, + }, + }; +} + +function parse_for(stream: TokenStream): StatementInterface | Error +{ + const for_token = expect(stream, TokenKind.For); + + if (for_token instanceof Error) + { + return for_token; + } + + const items: Array = []; + + while (items.length === 0 || consume(stream, TokenKind.Comma)) + { + const item = expect(stream, TokenKind.Identifier); + + if (item instanceof Error) + { + return item; + } + + items.push(item); + } + + if (consume(stream, TokenKind.Assign)) + { + const token = items[0]; + + if (token === undefined) + { + throw new Error(); + } + + return parse_numeric_for(token, stream); + } + + const in_token = expect(stream, TokenKind.In); + + if (in_token instanceof Error) + { + return in_token; + } + + const iterator = parse_expression(stream); + + if (iterator instanceof Error) + { + return iterator; + } + + const do_token = expect(stream, TokenKind.Do); + + if (do_token instanceof Error) + { + return do_token; + } + + const body = parse(stream, TokenKind.End); + + if (body instanceof Error) + { + return body; + } + + consume(stream, TokenKind.End); + + return { + kind: StatementKind.For, + for: { + items: items, + iterator: iterator, + body: body, + token: for_token, + }, + }; +} + +function parse_repeat(stream: TokenStream): StatementInterface | Error +{ + const repeat_token = expect(stream, TokenKind.Repeat); + + if (repeat_token instanceof Error) + { + return repeat_token; + } + + const body = parse(stream, TokenKind.Until); + + if (body instanceof Error) + { + return body; + } + + const until_token = expect(stream, TokenKind.Until); + + if (until_token instanceof Error) + { + return until_token; + } + + const condition = parse_expression(stream); + + if (condition instanceof Error) + { + return condition; + } + + return { + kind: StatementKind.Repeat, + repeat: { + body: body, + condition: condition, + token: repeat_token, + }, + }; +} + +function parse_do(stream: TokenStream): StatementInterface | Error +{ + const do_token = expect(stream, TokenKind.Do); + + if (do_token instanceof Error) + { + return do_token; + } + + const body = parse(stream, TokenKind.End); + + if (body instanceof Error) + { + return body; + } + + const end_token = expect(stream, TokenKind.End); + + if (end_token instanceof Error) + { + return end_token; + } + + return { + kind: StatementKind.Do, + do: { + body: body, + token: do_token, + }, + }; +} + +function parse_function_value(function_token: TokenInterface, stream: TokenStream): ValueInterface | Error +{ + const params = parse_function_params(stream); + + if (params instanceof Error) + { + return params; + } + + const body = parse(stream, TokenKind.End); + + if (body instanceof Error) + { + return body; + } + + consume(stream, TokenKind.End); + + return { + kind: ValueKind.FunctionLike, + token: function_token, + function: { + parameters: params, + body: body, + }, + }; +} + +function parse_local_function(table_name: TokenInterface, stream: TokenStream): StatementInterface | Error +{ + const local_name = expect(stream, TokenKind.Identifier); + + if (local_name instanceof Error) + { + return local_name; + } + + const function_value = parse_function_value(local_name, stream); + + if (function_value instanceof Error) + { + return function_value; + } + + return { + kind: StatementKind.Assignment, + assignment: { + token: table_name, + local: false, + lhs: [{ + kind: ExpressionKind.Index, + token: table_name, + expression: { + kind: ExpressionKind.Value, + token: table_name, + value: { + kind: ValueKind.Variable, + token: table_name, + identifier: table_name.data, + }, + }, + index: { + kind: ExpressionKind.Value, + token: local_name, + value: { + kind: ValueKind.StringLiteral, + token: local_name, + string: local_name.data, + }, + }, + }], + rhs: [{ + kind: ExpressionKind.Value, + token: local_name, + value: function_value, + }], + }, + }; +} + +function parse_function(stream: TokenStream): StatementInterface | Error +{ + const function_token = expect(stream, TokenKind.FunctionLike); + + if (function_token instanceof Error) + { + return function_token; + } + + const name = expect(stream, TokenKind.Identifier); + + if (name instanceof Error) + { + return name; + } + + if (consume(stream, TokenKind.Dot)) + { + return parse_local_function(name, stream); + } + + const function_value = parse_function_value(name, stream); + + if (function_value instanceof Error) + { + return function_value; + } + + return { + kind: StatementKind.Assignment, + assignment: { + token: name, + local: false, + lhs: [{ + kind: ExpressionKind.Value, + token: name, + value: { + kind: ValueKind.Variable, + token: name, + identifier: name.data, + }, + }], + rhs: [{ + kind: ExpressionKind.Value, + token: name, + value: function_value, + }], + }, + }; +} + +export { + parse, + parse_assign_or_expression, + parse_return, + parse_if, + parse_while, + parse_for, + parse_repeat, + parse_do, + parse_function, +}; diff --git a/src/parser.ts b/src/parser.ts deleted file mode 100644 index aa01cb9..0000000 --- a/src/parser.ts +++ /dev/null @@ -1,953 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import type { Chunk, IfElseBlock } from './ast' -import type { Expression, Statement, Value } from './ast' -import type { Token } from './lexer' - -import { ExpressionKind, StatementKind, ValueKind } from './ast' -import { TokenKind, TokenStream, token_kind_to_string } from './lexer' - -const UNARY = [ - TokenKind.Not, - TokenKind.Subtract, - TokenKind.Hash, -] - -const ORDERS = [ - [TokenKind.Or], - [TokenKind.And], - [TokenKind.LessThen, TokenKind.LessThenEquals, TokenKind.GreaterThen, TokenKind.GreaterThenEquals, TokenKind.Equals, TokenKind.NotEquals], - [TokenKind.BitOr], - [TokenKind.BitAnd], - [TokenKind.BitXOrNot], - [TokenKind.BitShiftLeft, TokenKind.BitShiftRight], - [TokenKind.Concat], - [TokenKind.Addition, TokenKind.Subtract], - [TokenKind.Multiply, TokenKind.Division, TokenKind.FloorDivision, TokenKind.Modulo], - [TokenKind.Exponent], -] - -function error(token: Token, message: string): Error -{ - return new Error( - `${ token.debug.line }:${ token.debug.column }: ${ message }`) -} - -function expect(stream: TokenStream, kind: TokenKind): Token | Error -{ - const token = stream.peek() - if (token.kind != kind) - { - return error(token, - `expected '${ token_kind_to_string(kind) }', ` + - `got '${ token_kind_to_string(token.kind) }' instead`) - } - - return stream.next() -} - -function consume(stream: TokenStream, kind: TokenKind): boolean -{ - const token = stream.peek() - if (token.kind != kind) - return false - - stream.next() - return true -} - -function parse_table_key(stream: TokenStream): Expression | Error -{ - if (consume(stream, TokenKind.OpenSquare)) - { - const element = parse_expression(stream) - if (element instanceof Error) - return element - - const close_square = expect(stream, TokenKind.CloseSquare) - if (close_square instanceof Error) - return close_square - - return element - } - - const value = parse_value(stream) - if (value instanceof Error) - return value - - if (value.kind == ValueKind.Variable) - { - value.kind = ValueKind.StringLiteral - value.string = value.identifier - value.identifier = undefined - } - - return { - kind: ExpressionKind.Value, - token: value.token, - value: value, - } -} - -function parse_table(stream: TokenStream): Value | Error -{ - const squigly_open = expect(stream, TokenKind.SquiglyOpen) - if (squigly_open instanceof Error) - return squigly_open - - const elements: Map = new Map() - let current_numeric_key = 1 - while (stream.peek().kind != TokenKind.SquiglyClose) - { - const element = parse_table_key(stream) - if (element instanceof Error) - return element - - if (consume(stream, TokenKind.Assign)) - { - const value = parse_expression(stream) - if (value instanceof Error) - return value - - elements.set(element, value) - } - else - { - const key_token = { - kind: TokenKind.NumberLiteral, - data: current_numeric_key.toString(), - debug: element.token.debug, - } - - const key = { - kind: ExpressionKind.Value, - token: key_token, - value: { - kind: ValueKind.NumberLiteral, - token: key_token, - number: current_numeric_key, - }, - } - - current_numeric_key += 1 - elements.set(key, element) - } - - if (!consume(stream, TokenKind.Comma)) - break - } - - const close_squigly = expect(stream, TokenKind.SquiglyClose) - if (close_squigly instanceof Error) - return close_squigly - - return { - kind: ValueKind.TableLiteral, - token: squigly_open, - table: elements, - } -} - -function parse_value(stream: TokenStream): Value | Error -{ - const token = stream.peek() - switch (token.kind) - { - case TokenKind.NumberLiteral: - return { kind: ValueKind.NumberLiteral, token: stream.next(), number: parseFloat(token.data) } - case TokenKind.BooleanLiteral: - return { kind: ValueKind.BooleanLiteral, token: stream.next(), boolean: token.data == 'true' } - case TokenKind.StringLiteral: - return { kind: ValueKind.StringLiteral, token: stream.next(), string: token.data } - case TokenKind.NilLiteral: - return { kind: ValueKind.NilLiteral, token: stream.next() } - case TokenKind.Identifier: - return { kind: ValueKind.Variable, token: stream.next(), identifier: token.data } - - case TokenKind.SquiglyOpen: - return parse_table(stream) - case TokenKind.Function: - return parse_function_value(stream.next(), stream) - - default: - return error(token, `Expected value, got ${ token_kind_to_string(token.kind) } instead`) - } -} - -function unary_type_to_expression_kind(kind: TokenKind): ExpressionKind -{ - switch (kind) - { - case TokenKind.Not: return ExpressionKind.Not - case TokenKind.Subtract: return ExpressionKind.Negate - case TokenKind.Hash: return ExpressionKind.Length - case TokenKind.BitXOrNot: return ExpressionKind.BitNot - default: - throw new Error() - } -} - -function parse_unary_operator(stream: TokenStream): Expression | Error -{ - const operator_token = stream.next() - const operator = unary_type_to_expression_kind(operator_token.kind) - - const expression = parse_expression(stream) - if (expression instanceof Error) - return expression - - return { - kind: operator, - token: operator_token, - expression: expression, - } -} - -function parse_value_expression(stream: TokenStream): Expression | Error -{ - if (consume(stream, TokenKind.OpenBrace)) - { - const sub_expression = parse_expression(stream) - if (sub_expression instanceof Error) - return sub_expression - - const close_brace = expect(stream, TokenKind.CloseBrace) - if (close_brace instanceof Error) - return close_brace - - return sub_expression - } - - if (UNARY.includes(stream.peek().kind)) - return parse_unary_operator(stream) - - const value = parse_value(stream) - if (value instanceof Error) - return value - - const expression_value = { - kind: ExpressionKind.Value, - token: value.token, - value: value, - } - - return parse_access_expression(expression_value, stream) -} - -function parse_call(func: Expression, stream: TokenStream): Expression | Error -{ - const open_brace = stream.next() - const args: Expression[] = [] - while (stream.peek().kind != TokenKind.CloseBrace) - { - const argument = parse_expression(stream) - if (argument instanceof Error) - break - - args.push(argument) - if (!consume(stream, TokenKind.Comma)) - break - } - - const close_brace = expect(stream, TokenKind.CloseBrace) - if (close_brace instanceof Error) - return close_brace - - return parse_access_expression({ - kind: ExpressionKind.Call, - token: open_brace, - expression: func, - arguments: args, - }, stream) -} - -function parse_index(table: Expression, stream: TokenStream): Expression | Error -{ - const open_square = stream.next() - const index = parse_expression(stream) - if (index instanceof Error) - return index - - const close_square = expect(stream, TokenKind.CloseSquare) - if (close_square instanceof Error) - return close_square - - return parse_access_expression({ - kind: ExpressionKind.Index, - token: open_square, - expression: table, - index: index, - }, stream) -} - -function parse_dot(table: Expression, stream: TokenStream): Expression | Error -{ - const dot = stream.next() - const index = expect(stream, TokenKind.Identifier) - if (index instanceof Error) - return index - - return parse_access_expression({ - kind: ExpressionKind.Index, - expression: table, - token: dot, - index: { - kind: ExpressionKind.Value, - token: index, - value: { - kind: ValueKind.StringLiteral, - token: index, - string: index.data, - }, - }, - }, stream) -} - -function parse_single_argument_call(func: Expression, stream: TokenStream): Expression | Error -{ - const argument = parse_expression(stream) - if (argument instanceof Error) - return argument - - return { - kind: ExpressionKind.Call, - token: func.token, - expression: func, - arguments: [argument], - } -} - -function parse_access_expression(expression: Expression, stream: TokenStream): Expression | Error -{ - switch (stream.peek().kind) - { - case TokenKind.OpenBrace: - return parse_call(expression, stream) - - case TokenKind.OpenSquare: - return parse_index(expression, stream) - - case TokenKind.Dot: - return parse_dot(expression, stream) - - case TokenKind.SquiglyOpen: - case TokenKind.StringLiteral: - return parse_single_argument_call(expression, stream) - } - - return expression -} - -function operation_type_to_expression_kind( - operation_type: TokenKind): ExpressionKind -{ - switch (operation_type) - { - case TokenKind.Addition: return ExpressionKind.Addition - case TokenKind.Subtract: return ExpressionKind.Subtract - case TokenKind.Multiply: return ExpressionKind.Multiplication - case TokenKind.Division: return ExpressionKind.Division - case TokenKind.FloorDivision: return ExpressionKind.FloorDivision - case TokenKind.Modulo: return ExpressionKind.Modulo - case TokenKind.Exponent: return ExpressionKind.Exponent - case TokenKind.Concat: return ExpressionKind.Concat - case TokenKind.BitAnd: return ExpressionKind.BitAnd - case TokenKind.BitOr: return ExpressionKind.BitOr - case TokenKind.BitXOrNot: return ExpressionKind.BitXOr - case TokenKind.BitShiftLeft: return ExpressionKind.BitShiftLeft - case TokenKind.BitShiftRight: return ExpressionKind.BitShiftRight - case TokenKind.LessThen: return ExpressionKind.LessThen - case TokenKind.LessThenEquals: return ExpressionKind.LessThenEquals - case TokenKind.GreaterThen: return ExpressionKind.GreaterThen - case TokenKind.GreaterThenEquals: return ExpressionKind.GreaterThenEquals - case TokenKind.Equals: return ExpressionKind.Equals - case TokenKind.NotEquals: return ExpressionKind.NotEquals - case TokenKind.And: return ExpressionKind.And - case TokenKind.Or: return ExpressionKind.Or - default: - throw new Error() - } -} - -function parse_operation(stream: TokenStream, - order: number): Expression | Error -{ - if (order >= ORDERS.length) - return parse_value_expression(stream) - - let lhs = parse_operation(stream, order + 1) - if (lhs instanceof Error) - return lhs - - while (ORDERS[order].includes(stream.peek().kind)) - { - const operation_type = stream.next() - const rhs = parse_operation(stream, order + 1) - if (rhs instanceof Error) - return rhs - - const expression_kind = operation_type_to_expression_kind(operation_type.kind) - lhs = { - kind: expression_kind, - token: operation_type, - lhs: lhs, - rhs: rhs, - } - } - - return lhs -} - -function parse_expression(stream: TokenStream): Expression | Error -{ - if (stream.peek().kind == TokenKind.BitXOrNot) - return parse_unary_operator(stream) - - return parse_operation(stream, 0) -} - -function parse_local_statement(local: Token, values: Expression[]): Statement | Error -{ - const names: Token[] = [] - for (const expression of values) - { - const value = expression.value - if (value == undefined || value.kind != ValueKind.Variable) - return error(expression.token, 'Invalid local name') - names.push(value.token) - } - - return { - kind: StatementKind.Local, - local: { - token: local, - names: names, - }, - } -} - -function parse_assign_or_expression(stream: TokenStream): Statement | Error -{ - const local = expect(stream, TokenKind.Local) - const lhs: Expression[] = [] - while (lhs.length == 0 || consume(stream, TokenKind.Comma)) - { - const lvalue = parse_expression(stream) - if (lvalue instanceof Error) - return lvalue - lhs.push(lvalue) - } - - const assign = expect(stream, TokenKind.Assign) - if (assign instanceof Error) - { - if (!(local instanceof Error)) - return parse_local_statement(local, lhs) - else - return { kind: StatementKind.Expression, expression: lhs[0] } - } - - const rhs: Expression[] = [] - while (rhs.length == 0 || consume(stream, TokenKind.Comma)) - { - const rvalue = parse_expression(stream) - if (rvalue instanceof Error) - return rvalue - rhs.push(rvalue) - } - - return { - kind: StatementKind.Assignment, - assignment: { - local: !(local instanceof Error), - lhs: lhs.reverse(), - rhs: rhs, - token: assign, - }, - } -} - -function parse_return(stream: TokenStream): Statement | Error -{ - const ret = expect(stream, TokenKind.Return) - if (ret instanceof Error) - return ret - - const values: Expression[] = [] - while (values.length == 0 || consume(stream, TokenKind.Comma)) - { - const value = parse_expression(stream) - if (value instanceof Error) - { - if (values.length > 0) - return value - break - } - - values.push(value) - } - - if (values.length == 0) - { - values.push({ - kind: ExpressionKind.Value, - token: ret, - value: { - kind: ValueKind.NilLiteral, - token: ret, - }, - }) - } - - return { - kind: StatementKind.Return, - return: { - values: values, - token: ret, - }, - } -} - -function parse_break(stream: TokenStream): Statement | Error -{ - const break_token = expect(stream, TokenKind.Break) - if (break_token instanceof Error) - return break_token - - return { - kind: StatementKind.Break, - } -} - -function parse_if(stream: TokenStream): Statement | Error -{ - const if_token = expect(stream, TokenKind.If) - if (if_token instanceof Error) - return if_token - - const condition = parse_expression(stream) - if (condition instanceof Error) - return condition - - const then = expect(stream, TokenKind.Then) - if (then instanceof Error) - return then - - const body = parse(stream, TokenKind.Else, TokenKind.ElseIf, TokenKind.End) - if (body instanceof Error) - return body - - const else_if_bodies: IfElseBlock[] = [] - let else_body: Chunk | undefined = undefined - while (consume(stream, TokenKind.ElseIf)) - { - const condition = parse_expression(stream) - if (condition instanceof Error) - return condition - - const then = expect(stream, TokenKind.Then) - if (then instanceof Error) - return then - - const chunk = parse(stream, TokenKind.End, TokenKind.ElseIf, TokenKind.Else) - if (chunk instanceof Error) - return chunk - - else_if_bodies.push({ - body: chunk, - condition: condition, - token: then, - }) - } - - if (consume(stream, TokenKind.Else)) - { - const chunk = parse(stream, TokenKind.End) - if (chunk instanceof Error) - return chunk - else_body = chunk - } - - const end = expect(stream, TokenKind.End) - if (end instanceof Error) - return end - - return { - kind: StatementKind.If, - if: { - condition: condition, - body: body, - else_if_bodies: else_if_bodies, - else_body: else_body, - token: if_token, - }, - } -} - -function parse_while(stream: TokenStream): Statement | Error -{ - const while_token = expect(stream, TokenKind.While) - if (while_token instanceof Error) - return while_token - - const condition = parse_expression(stream) - if (condition instanceof Error) - return condition - - const do_token = expect(stream, TokenKind.Do) - if (do_token instanceof Error) - return do_token - - const body = parse(stream, TokenKind.End) - if (body instanceof Error) - return body - - consume(stream, TokenKind.End) - return { - kind: StatementKind.While, - while: { - condition: condition, - body: body, - token: while_token, - }, - } -} - -function parse_numeric_for(index: Token, stream: TokenStream): Statement | Error -{ - const start = parse_expression(stream) - if (start instanceof Error) - return start - - const comma = expect(stream, TokenKind.Comma) - if (comma instanceof Error) - return comma - - const end = parse_expression(stream) - if (end instanceof Error) - return end - - let step: Expression | undefined = undefined - if (consume(stream, TokenKind.Comma)) - { - const expression = parse_expression(stream) - if (expression instanceof Error) - return expression - - step = expression - } - - const do_token = expect(stream, TokenKind.Do) - if (do_token instanceof Error) - return do_token - - const body = parse(stream, TokenKind.End) - if (body instanceof Error) - return body - - consume(stream, TokenKind.End) - return { - kind: StatementKind.NumericFor, - numeric_for: { - index: index, - start: start, - end: end, - step: step, - body: body, - }, - } -} - -function parse_for(stream: TokenStream): Statement | Error -{ - const for_token = expect(stream, TokenKind.For) - if (for_token instanceof Error) - return for_token - - const items: Token[] = [] - while (items.length == 0 || consume(stream, TokenKind.Comma)) - { - const item = expect(stream, TokenKind.Identifier) - if (item instanceof Error) - return item - items.push(item) - } - - if (consume(stream, TokenKind.Assign)) - return parse_numeric_for(items[0], stream) - - const in_token = expect(stream, TokenKind.In) - if (in_token instanceof Error) - return in_token - - const itorator = parse_expression(stream) - if (itorator instanceof Error) - return itorator - - const do_token = expect(stream, TokenKind.Do) - if (do_token instanceof Error) - return do_token - - const body = parse(stream, TokenKind.End) - if (body instanceof Error) - return body - - consume(stream, TokenKind.End) - return { - kind: StatementKind.For, - for: { - items: items, - itorator: itorator, - body: body, - token: for_token, - }, - } -} - -function parse_repeat(stream: TokenStream): Statement | Error -{ - const repeat_token = expect(stream, TokenKind.Repeat) - if (repeat_token instanceof Error) - return repeat_token - - const body = parse(stream, TokenKind.Until) - if (body instanceof Error) - return body - - const until_token = expect(stream, TokenKind.Until) - if (until_token instanceof Error) - return until_token - - const condition = parse_expression(stream) - if (condition instanceof Error) - return condition - - return { - kind: StatementKind.Repeat, - repeat: { - body: body, - condition: condition, - token: repeat_token, - }, - } -} - -function parse_do(stream: TokenStream): Statement | Error -{ - const do_token = expect(stream, TokenKind.Do) - if (do_token instanceof Error) - return do_token - - const body = parse(stream, TokenKind.End) - if (body instanceof Error) - return body - - const end_token = expect(stream, TokenKind.End) - if (end_token instanceof Error) - return end_token - - return { - kind: StatementKind.Do, - do: { - body: body, - token: do_token, - }, - } -} - -function parse_function_params(stream: TokenStream): Token[] | Error -{ - const open_brace = expect(stream, TokenKind.OpenBrace) - if (open_brace instanceof Error) - return open_brace - - const params: Token[] = [] - while (stream.peek().kind != TokenKind.CloseBrace) - { - const param = expect(stream, TokenKind.Identifier) - if (param instanceof Error) - break - - params.push(param) - if (!consume(stream, TokenKind.Comma)) - break - } - - const close_brace = expect(stream, TokenKind.CloseBrace) - if (close_brace instanceof Error) - return close_brace - - return params -} - -function parse_function_value(function_token: Token, stream: TokenStream): Value | Error -{ - const params = parse_function_params(stream) - if (params instanceof Error) - return params - - const body = parse(stream, TokenKind.End) - if (body instanceof Error) - return body - - consume(stream, TokenKind.End) - return { - kind: ValueKind.Function, - token: function_token, - function: { - parameters: params, - body: body, - }, - } -} - -function parse_local_function(table_name: Token, stream: TokenStream): Statement | Error -{ - const local_name = expect(stream, TokenKind.Identifier) - if (local_name instanceof Error) - return local_name - - const function_value = parse_function_value(local_name, stream) - if (function_value instanceof Error) - return function_value - - return { - kind: StatementKind.Assignment, - assignment: { - token: table_name, - local: false, - lhs: [{ - kind: ExpressionKind.Index, - token: table_name, - expression: { - kind: ExpressionKind.Value, - token: table_name, - value: { - kind: ValueKind.Variable, - token: table_name, - identifier: table_name.data, - }, - }, - index: { - kind: ExpressionKind.Value, - token: local_name, - value: { - kind: ValueKind.StringLiteral, - token: local_name, - string: local_name.data, - }, - }, - }], - rhs: [{ - kind: ExpressionKind.Value, - token: local_name, - value: function_value, - }], - }, - } -} - -function parse_function(stream: TokenStream): Statement | Error -{ - const function_token = expect(stream, TokenKind.Function) - if (function_token instanceof Error) - return function_token - - const name = expect(stream, TokenKind.Identifier) - if (name instanceof Error) - return name - - if (consume(stream, TokenKind.Dot)) - return parse_local_function(name, stream) - - const function_value = parse_function_value(name, stream) - if (function_value instanceof Error) - return function_value - - return { - kind: StatementKind.Assignment, - assignment: { - token: name, - local: false, - lhs: [{ - kind: ExpressionKind.Value, - token: name, - value: { - kind: ValueKind.Variable, - token: name, - identifier: name.data, - }, - }], - rhs: [{ - kind: ExpressionKind.Value, - token: name, - value: function_value, - }], - }, - } -} - -function parse_statement(stream: TokenStream, end_tokens: TokenKind[]): Statement | Error | undefined -{ - const token = stream.peek() - switch (token.kind) - { - case TokenKind.Identifier: - case TokenKind.NilLiteral: - case TokenKind.StringLiteral: - case TokenKind.NumberLiteral: - case TokenKind.BooleanLiteral: - case TokenKind.SquiglyOpen: - case TokenKind.Local: - return parse_assign_or_expression(stream) - case TokenKind.Return: - return parse_return(stream) - case TokenKind.Break: - return parse_break(stream) - case TokenKind.If: - return parse_if(stream) - case TokenKind.While: - return parse_while(stream) - case TokenKind.For: - return parse_for(stream) - case TokenKind.Repeat: - return parse_repeat(stream) - case TokenKind.Do: - return parse_do(stream) - case TokenKind.Function: - return parse_function(stream) - case TokenKind.Semicolon: - stream.next() - return { kind: StatementKind.Empty } - default: - if (end_tokens.includes(token.kind)) - return undefined - return error(token, `Missing '${ token_kind_to_string(end_tokens[0]) }', ` + - `got '${ token_kind_to_string(token.kind) }' instead`) - } -} - -export function parse(stream: TokenStream, ...end_tokens: TokenKind[]): Chunk | Error -{ - const chunk: Chunk = { statements: [] } - if (end_tokens.length == 0) - end_tokens.push(TokenKind.EOF) - - while (true) - { - const statement = parse_statement(stream, end_tokens) - if (statement == undefined) - break - if (statement instanceof Error) - return statement - - chunk.statements.push(statement) - } - - return chunk -} - diff --git a/src/parser/consume/consume.mts b/src/parser/consume/consume.mts new file mode 100644 index 0000000..ebb9f23 --- /dev/null +++ b/src/parser/consume/consume.mts @@ -0,0 +1,19 @@ +import type { TokenStream } from "../../lexer.mjs"; +import type { TokenKindEnum } from "../../lexer/definition/enum/token-kind.enum.mjs"; +import type { TokenInterface } from "../../lexer/definition/interface/token.interface.mjs"; + +function consume(stream: TokenStream, kind: TokenKindEnum): boolean +{ + const token: TokenInterface = stream.peek(); + + if (token.kind !== kind) + { + return false; + } + + stream.next(); + + return true; +} + +export { consume }; diff --git a/src/parser/error/to-error.mts b/src/parser/error/to-error.mts new file mode 100644 index 0000000..009ca96 --- /dev/null +++ b/src/parser/error/to-error.mts @@ -0,0 +1,11 @@ +import type { TokenStream } from "../../lexer.mjs"; +import type { TokenInterface } from "../../lexer/definition/interface/token.interface.mjs"; + +function to_error(token: TokenInterface | TokenStream, message: string): Error +{ + return new Error( + `${token.debug.line.toFixed(0)}:${token.debug.column.toFixed(0)}: ${message}` + ); +} + +export { to_error }; diff --git a/src/parser/expect/expect.mts b/src/parser/expect/expect.mts new file mode 100644 index 0000000..8356a69 --- /dev/null +++ b/src/parser/expect/expect.mts @@ -0,0 +1,22 @@ +import type { TokenStream } from "../../lexer.mjs"; +import type { TokenKindEnum } from "../../lexer/definition/enum/token-kind.enum.mjs"; +import type { TokenInterface } from "../../lexer/definition/interface/token.interface.mjs"; +import { token_kind_to_string } from "../../lexer/token-kind-to-string/token-kind-to-string.mjs"; +import { to_error } from "../error/to-error.mjs"; + +function expect(stream: TokenStream, kind: TokenKindEnum): TokenInterface | Error +{ + const token = stream.peek(); + + if (token.kind !== kind) + { + return to_error( + token, + `expected '${token_kind_to_string(kind)}', got '${token_kind_to_string(token.kind)}' instead` + ); + } + + return stream.next(); +} + +export { expect }; diff --git a/src/parser/get-orders/definition/type/order-tuple.type.mts b/src/parser/get-orders/definition/type/order-tuple.type.mts new file mode 100644 index 0000000..eede8a4 --- /dev/null +++ b/src/parser/get-orders/definition/type/order-tuple.type.mts @@ -0,0 +1,17 @@ +import type { TokenKind } from "../../../../lexer/definition/enum/token-kind.enum.mjs"; + +type OrderTupleType = [ + [typeof TokenKind["Or"]], + [typeof TokenKind["And"]], + [typeof TokenKind["LessThan"], typeof TokenKind["LessThanEquals"], typeof TokenKind["GreaterThan"], typeof TokenKind["GreaterThanEquals"], typeof TokenKind["Equals"], typeof TokenKind["NotEquals"]], + [typeof TokenKind["BitOr"]], + [typeof TokenKind["BitAnd"]], + [typeof TokenKind["BitXOrNot"]], + [typeof TokenKind["BitShiftLeft"], typeof TokenKind["BitShiftRight"]], + [typeof TokenKind["Concat"]], + [typeof TokenKind["Addition"], typeof TokenKind["Subtract"]], + [typeof TokenKind["Multiply"], typeof TokenKind["Division"], typeof TokenKind["FloorDivision"], typeof TokenKind["Modulo"]], + [typeof TokenKind["Exponent"]], +]; + +export { type OrderTupleType }; diff --git a/src/parser/get-orders/get-orders.mts b/src/parser/get-orders/get-orders.mts new file mode 100644 index 0000000..96a64eb --- /dev/null +++ b/src/parser/get-orders/get-orders.mts @@ -0,0 +1,21 @@ +import { TokenKind } from "../../lexer/definition/enum/token-kind.enum.mjs"; +import type { OrderTupleType } from "./definition/type/order-tuple.type.mjs"; + +function get_orders(): OrderTupleType +{ + return [ + [TokenKind.Or], + [TokenKind.And], + [TokenKind.LessThan, TokenKind.LessThanEquals, TokenKind.GreaterThan, TokenKind.GreaterThanEquals, TokenKind.Equals, TokenKind.NotEquals], + [TokenKind.BitOr], + [TokenKind.BitAnd], + [TokenKind.BitXOrNot], + [TokenKind.BitShiftLeft, TokenKind.BitShiftRight], + [TokenKind.Concat], + [TokenKind.Addition, TokenKind.Subtract], + [TokenKind.Multiply, TokenKind.Division, TokenKind.FloorDivision, TokenKind.Modulo], + [TokenKind.Exponent], + ]; +} + +export { get_orders }; diff --git a/src/parser/is-unary-operator/is-unary-operator.mts b/src/parser/is-unary-operator/is-unary-operator.mts new file mode 100644 index 0000000..1b29861 --- /dev/null +++ b/src/parser/is-unary-operator/is-unary-operator.mts @@ -0,0 +1,20 @@ +import { isEnumValue } from "@vitruvius-labs/ts-predicate"; +import { TokenKind, type TokenKindEnum } from "../../lexer/definition/enum/token-kind.enum.mjs"; + +type UnaryOperatorTokenKind = ( + | typeof TokenKind.Not + | typeof TokenKind.Subtract + | typeof TokenKind.Hash +); + +function isUnaryOperatorToken(token: TokenKindEnum): token is UnaryOperatorTokenKind +{ + return isEnumValue(token, [ + TokenKind.Not, + TokenKind.Subtract, + TokenKind.Hash, + ]); +} + +export { isUnaryOperatorToken }; + diff --git a/src/parser/operation-type-to-expression-kind/operation-type-to-expression-kind.mts b/src/parser/operation-type-to-expression-kind/operation-type-to-expression-kind.mts new file mode 100644 index 0000000..d840b99 --- /dev/null +++ b/src/parser/operation-type-to-expression-kind/operation-type-to-expression-kind.mts @@ -0,0 +1,59 @@ +import { ExpressionKind, type ExpressionKindEnum } from "../../ast/definition/enum/expression-kind.enum.mjs"; +import { TokenKind, type TokenKindEnum } from "../../lexer/definition/enum/token-kind.enum.mjs"; + +function operation_type_to_expression_kind( + operation_type: TokenKindEnum +): ExpressionKindEnum +{ + // eslint-disable-next-line @ts/switch-exhaustiveness-check -- Not all operations are expressions + switch (operation_type) + { + case TokenKind.Addition: + return ExpressionKind.Addition; + case TokenKind.Subtract: + return ExpressionKind.Subtract; + case TokenKind.Multiply: + return ExpressionKind.Multiplication; + case TokenKind.Division: + return ExpressionKind.Division; + case TokenKind.FloorDivision: + return ExpressionKind.FloorDivision; + case TokenKind.Modulo: + return ExpressionKind.Modulo; + case TokenKind.Exponent: + return ExpressionKind.Exponent; + case TokenKind.Concat: + return ExpressionKind.Concat; + case TokenKind.BitAnd: + return ExpressionKind.BitAnd; + case TokenKind.BitOr: + return ExpressionKind.BitOr; + case TokenKind.BitXOrNot: + return ExpressionKind.BitXOr; + case TokenKind.BitShiftLeft: + return ExpressionKind.BitShiftLeft; + case TokenKind.BitShiftRight: + return ExpressionKind.BitShiftRight; + case TokenKind.LessThan: + return ExpressionKind.LessThan; + case TokenKind.LessThanEquals: + return ExpressionKind.LessThanEquals; + case TokenKind.GreaterThan: + return ExpressionKind.GreaterThan; + case TokenKind.GreaterThanEquals: + return ExpressionKind.GreaterThanEquals; + case TokenKind.Equals: + return ExpressionKind.Equals; + case TokenKind.NotEquals: + return ExpressionKind.NotEquals; + case TokenKind.And: + return ExpressionKind.And; + case TokenKind.Or: + return ExpressionKind.Or; + + default: + throw new Error(`"${operation_type}" is not an expression operation.`); + } +} + +export { operation_type_to_expression_kind }; diff --git a/src/parser/parse-function-params/parse-function-params.mts b/src/parser/parse-function-params/parse-function-params.mts new file mode 100644 index 0000000..00ed941 --- /dev/null +++ b/src/parser/parse-function-params/parse-function-params.mts @@ -0,0 +1,45 @@ +import type { TokenStream } from "../../lexer.mjs"; +import { TokenKind } from "../../lexer/definition/enum/token-kind.enum.mjs"; +import type { TokenInterface } from "../../lexer/definition/interface/token.interface.mjs"; +import { consume } from "../consume/consume.mjs"; +import { expect } from "../expect/expect.mjs"; + +function parse_function_params(stream: TokenStream): Array | Error +{ + const open_brace = expect(stream, TokenKind.OpenBrace); + + if (open_brace instanceof Error) + { + return open_brace; + } + + const params: Array = []; + + while (stream.peek().kind !== TokenKind.CloseBrace) + { + const param = expect(stream, TokenKind.Identifier); + + if (param instanceof Error) + { + break; + } + + params.push(param); + + if (!consume(stream, TokenKind.Comma)) + { + break; + } + } + + const close_brace = expect(stream, TokenKind.CloseBrace); + + if (close_brace instanceof Error) + { + return close_brace; + } + + return params; +} + +export { parse_function_params }; diff --git a/src/parser/parse-local-statement/parse-local-statement.mts b/src/parser/parse-local-statement/parse-local-statement.mts new file mode 100644 index 0000000..1dd5acf --- /dev/null +++ b/src/parser/parse-local-statement/parse-local-statement.mts @@ -0,0 +1,34 @@ +import { StatementKind } from "../../ast/definition/enum/statement-kind.enum.mjs"; +import { ValueKind } from "../../ast/definition/enum/value-kind.enum.mjs"; +import type { ExpressionInterface } from "../../ast/definition/interface/expression.interface.mjs"; +import type { StatementInterface } from "../../ast/definition/interface/statement.interface.mjs"; +import type { TokenStream } from "../../lexer.mjs"; +import type { TokenInterface } from "../../lexer/definition/interface/token.interface.mjs"; +import { to_error } from "../error/to-error.mjs"; + +function parse_local_statement(local: TokenInterface, values: Array): StatementInterface | Error +{ + const names: Array = []; + + for (const expression of values) + { + const value = expression.value; + + if (value === undefined || value.kind !== ValueKind.Variable) + { + return to_error(expression.token, "Invalid local name"); + } + + names.push(value.token); + } + + return { + kind: StatementKind.Local, + local: { + token: local, + names: names, + }, + }; +} + +export { parse_local_statement }; diff --git a/src/parser/parse-statement/parse-statement.mts b/src/parser/parse-statement/parse-statement.mts new file mode 100644 index 0000000..fc6ffe1 --- /dev/null +++ b/src/parser/parse-statement/parse-statement.mts @@ -0,0 +1,68 @@ +import { StatementKind } from "../../ast/definition/enum/statement-kind.enum.mjs"; +import type { StatementInterface } from "../../ast/definition/interface/statement.interface.mjs"; +import type { TokenStream } from "../../lexer.mjs"; +import { TokenKind, type TokenKindEnum } from "../../lexer/definition/enum/token-kind.enum.mjs"; +import { token_kind_to_string } from "../../lexer/token-kind-to-string/token-kind-to-string.mjs"; +import { parse_assign_or_expression, parse_do, parse_for, parse_function, parse_if, parse_repeat, parse_return, parse_while } from "../../parser.mjs"; +import { to_error } from "../error/to-error.mjs"; +import { parse_break } from "../parse_break/parse-break.mjs"; + +function parse_statement(stream: TokenStream, end_tokens: Array): StatementInterface | Error | undefined +{ + const token = stream.peek(); + + // eslint-disable-next-line @ts/switch-exhaustiveness-check + switch (token.kind) + { + case TokenKind.Identifier: + case TokenKind.NilLiteral: + case TokenKind.StringLiteral: + case TokenKind.NumberLiteral: + case TokenKind.BooleanLiteral: + case TokenKind.SquiglyOpen: + case TokenKind.Local: + return parse_assign_or_expression(stream); + case TokenKind.Return: + return parse_return(stream); + case TokenKind.Break: + return parse_break(stream); + case TokenKind.If: + return parse_if(stream); + case TokenKind.While: + return parse_while(stream); + case TokenKind.For: + return parse_for(stream); + case TokenKind.Repeat: + return parse_repeat(stream); + case TokenKind.Do: + return parse_do(stream); + case TokenKind.FunctionLike: + return parse_function(stream); + case TokenKind.Semicolon: + stream.next(); + + return { kind: StatementKind.Empty }; + + default: + { + if (end_tokens.includes(token.kind)) + { + return undefined; + } + + const first_end_token = end_tokens[0]; + + if (first_end_token === undefined) + { + throw new Error(); + } + + return to_error( + token, + `Missing '${token_kind_to_string(first_end_token)}', got '${token_kind_to_string(token.kind)}' instead` + ); + } + } +} + +export { parse_statement }; diff --git a/src/parser/parse/parse.mts b/src/parser/parse/parse.mts new file mode 100644 index 0000000..2db8d81 --- /dev/null +++ b/src/parser/parse/parse.mts @@ -0,0 +1,35 @@ +import type { ChunkInterface } from "../../ast/definition/interface/chunk.interface.mjs"; +import type { TokenStream } from "../../lexer.mjs"; +import { TokenKind, type TokenKindEnum } from "../../lexer/definition/enum/token-kind.enum.mjs"; +import { parse_statement } from "../parse-statement/parse-statement.mjs"; + +function parse(stream: TokenStream, ...end_tokens: Array): ChunkInterface | Error +{ + const chunk: ChunkInterface = { statements: [] }; + + if (end_tokens.length === 0) + { + end_tokens.push(TokenKind.EOF); + } + + for (;;) + { + const statement = parse_statement(stream, end_tokens); + + if (statement === undefined) + { + break; + } + + if (statement instanceof Error) + { + return statement; + } + + chunk.statements.push(statement); + } + + return chunk; +} + +export { parse }; diff --git a/src/parser/parse_break/parse-break.mts b/src/parser/parse_break/parse-break.mts new file mode 100644 index 0000000..e6977de --- /dev/null +++ b/src/parser/parse_break/parse-break.mts @@ -0,0 +1,21 @@ +import { StatementKind } from "../../ast/definition/enum/statement-kind.enum.mjs"; +import type { StatementInterface } from "../../ast/definition/interface/statement.interface.mjs"; +import type { TokenStream } from "../../lexer.mjs"; +import { TokenKind } from "../../lexer/definition/enum/token-kind.enum.mjs"; +import { expect } from "../expect/expect.mjs"; + +function parse_break(stream: TokenStream): StatementInterface | Error +{ + const break_token = expect(stream, TokenKind.Break); + + if (break_token instanceof Error) + { + return break_token; + } + + return { + kind: StatementKind.Break, + }; +} + +export { parse_break }; diff --git a/src/parser/unary-type-to-expression-kind/unary-type-to-expression-kind.mts b/src/parser/unary-type-to-expression-kind/unary-type-to-expression-kind.mts new file mode 100644 index 0000000..3e63d00 --- /dev/null +++ b/src/parser/unary-type-to-expression-kind/unary-type-to-expression-kind.mts @@ -0,0 +1,23 @@ +import { ExpressionKind, type ExpressionKindEnum } from "../../ast/definition/enum/expression-kind.enum.mjs"; +import { TokenKind, type TokenKindEnum } from "../../lexer/definition/enum/token-kind.enum.mjs"; + +function unary_type_to_expression_kind(kind: TokenKindEnum): ExpressionKindEnum +{ + // eslint-disable-next-line @ts/switch-exhaustiveness-check -- Only unary expressions + switch (kind) + { + case TokenKind.Not: + return ExpressionKind.Not; + case TokenKind.Subtract: + return ExpressionKind.Negate; + case TokenKind.Hash: + return ExpressionKind.Length; + case TokenKind.BitXOrNot: + return ExpressionKind.BitNot; + + default: + throw new Error(); + } +} + +export { unary_type_to_expression_kind }; diff --git a/src/runtime-error.mts b/src/runtime-error.mts new file mode 100644 index 0000000..7e4568d --- /dev/null +++ b/src/runtime-error.mts @@ -0,0 +1,38 @@ +import type { DebugInterface } from "./lexer/definition/interface/debug.interface.mjs"; + +class RuntimeError extends Error +{ + protected readonly line: number | undefined = undefined; + protected readonly column: number | undefined = undefined; + + public constructor(message: string, options?: ErrorOptions, debug?: DebugInterface) + { + super(message, options); + + this.name = "RuntimeError"; + this.line = debug?.line; + this.column = debug?.column; + } + + public getLine(): number | undefined + { + return this.line; + } + + public getColumn(): number | undefined + { + return this.column; + } + + public getMessage(): string + { + if (this.line === undefined || this.column === undefined) + { + return this.message; + } + + return `${this.line.toFixed(0)}:${this.column.toFixed(0)}: ${this.message}`; + } +} + +export { RuntimeError }; diff --git a/src/runtime.mts b/src/runtime.mts new file mode 100644 index 0000000..c0079c3 --- /dev/null +++ b/src/runtime.mts @@ -0,0 +1,117 @@ +import { + isArray, + isBoolean, + isCallable, + isInstanceOf, + isNullish, + isNumber, + isString, +} from "@vitruvius-labs/ts-predicate"; + +import type { Variable } from "./variable/definition/type/variable.type.mjs"; +import type { VariableTable } from "./variable/definition/interface/variable-table.interface.mjs"; +import type { TableInputType } from "./boundary/definition/type/table-input.type.mjs"; +import type { VariableTableMapType } from "./variable/definition/type/variable-table-map.type.mjs"; +import { VariableKind } from "./variable/definition/enum/variable-kind.enum.mjs"; +import { isVariable } from "./variable/predicate/is-variable.mjs"; +import { nil } from "./variable/nil.mjs"; +import { isTableInputType } from "./boundary/predicate/is-table-input-type.mjs"; +import { isTableMapKeyType } from "./boundary/predicate/is-table-map-key-type.mjs"; +import { make_boolean } from "./runtime/make-boolean/make-boolean.mjs"; +import { make_number } from "./runtime/make-number/make-number.mjs"; +import { make_string } from "./runtime/make-string/make-string.mjs"; + +export function make_table(input?: TableInputType): VariableTable +{ + const table_content: VariableTableMapType = new Map(); + + const table_variable: Variable = { + data_type: VariableKind.Table, + table: table_content, + }; + + if (input === undefined) + { + return table_variable; + } + + if (isArray(input)) + { + for (let i = 0; i < input.length; ++i) + { + table_content.set(i + 1, make_variable(input.at(i))); + } + + return table_variable; + } + + if (isInstanceOf(input, Map)) + { + for (const [key, value] of input.entries()) + { + if (isTableMapKeyType(key)) + { + table_content.set(key, make_variable(value)); + } + } + + return table_variable; + } + + for (const [key, value] of Object.entries(input)) + { + const variable: Variable = make_variable(value); + const numeric_key: number = Number(key); + + if (isNumber(numeric_key)) + { + table_content.set(numeric_key, variable); + + continue; + } + + table_content.set(key, variable); + } + + return table_variable; +} + +export function make_variable(input: unknown): Variable +{ + if (isVariable(input)) + { + return input; + } + + if (isNullish(input)) + { + return nil; + } + + if (isBoolean(input)) + { + return make_boolean(input); + } + + if (isNumber(input)) + { + return make_number(input); + } + + if (isString(input)) + { + return make_string(input); + } + + if (isTableInputType(input)) + { + return make_table(input); + } + + if (isCallable(input)) + { + throw new Error("Functions cannot be converted into a variable automatically, please use make_function instead"); + } + + throw new Error("Cannot be converted into a variable"); +} diff --git a/src/runtime.ts b/src/runtime.ts deleted file mode 100644 index 1cb104b..0000000 --- a/src/runtime.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022, Ben Jilks - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -import { Engine } from './engine' - -export enum DataType { - Nil, - Boolean, - Number, - String, - Function, - NativeFunction, - Table, -} - -export type NativeFunction = (engine: Engine, ...args: Variable[]) => Variable[] -export interface Variable { - data_type: DataType, - boolean?: boolean, - number?: number, - string?: string, - native_function?: NativeFunction, - table?: Map, - - function_id?: number, - locals?: Map[], -} - -export const nil: Variable = { data_type: DataType.Nil } - -export function make_boolean(boolean: boolean): Variable -{ - return { data_type: DataType.Boolean, boolean: boolean } -} - -export function make_number(number: number): Variable -{ - return { data_type: DataType.Number, number: number } -} - -export function make_string(string: string): Variable -{ - return { data_type: DataType.String, string: string } -} - diff --git a/src/runtime/make-boolean/make-boolean.mts b/src/runtime/make-boolean/make-boolean.mts new file mode 100644 index 0000000..aabe5d2 --- /dev/null +++ b/src/runtime/make-boolean/make-boolean.mts @@ -0,0 +1,8 @@ +import { type VariableBoolean, VariableKind } from "../../_index.mjs"; + +function make_boolean(boolean: boolean): VariableBoolean +{ + return { data_type: VariableKind.Boolean, boolean: boolean }; +} + +export { make_boolean }; diff --git a/src/runtime/make-number/make-number.mts b/src/runtime/make-number/make-number.mts new file mode 100644 index 0000000..686a434 --- /dev/null +++ b/src/runtime/make-number/make-number.mts @@ -0,0 +1,8 @@ +import { VariableKind, type VariableNumber } from "../../_index.mjs"; + +function make_number(number: number): VariableNumber +{ + return { data_type: VariableKind.Number, number: number }; +} + +export { make_number }; diff --git a/src/runtime/make-string/make-string.mts b/src/runtime/make-string/make-string.mts new file mode 100644 index 0000000..b69d97a --- /dev/null +++ b/src/runtime/make-string/make-string.mts @@ -0,0 +1,8 @@ +import { VariableKind, type VariableString } from "../../_index.mjs"; + +function make_string(string: string): VariableString +{ + return { data_type: VariableKind.String, string: string }; +} + +export { make_string }; diff --git a/src/variable/_index.mts b/src/variable/_index.mts new file mode 100644 index 0000000..b756c99 --- /dev/null +++ b/src/variable/_index.mts @@ -0,0 +1,5 @@ +export * from "./definition/_index.mjs"; +export * from "./predicate/_index.mjs"; +export * from "./nil.mjs"; +export * from "./equals.mjs"; +export * from "./unwrap-variable.mjs"; diff --git a/src/variable/definition/_index.mts b/src/variable/definition/_index.mts new file mode 100644 index 0000000..b0660d6 --- /dev/null +++ b/src/variable/definition/_index.mts @@ -0,0 +1,3 @@ +export type * from "./interface/_index.mjs"; +export type * from "./type/_index.mjs"; +export * from "./enum/_index.mjs"; diff --git a/src/variable/definition/enum/_index.mts b/src/variable/definition/enum/_index.mts new file mode 100644 index 0000000..df1d95e --- /dev/null +++ b/src/variable/definition/enum/_index.mts @@ -0,0 +1 @@ +export * from "./variable-kind.enum.mjs"; diff --git a/src/variable/definition/enum/variable-kind.enum.mts b/src/variable/definition/enum/variable-kind.enum.mts new file mode 100644 index 0000000..58bc4f6 --- /dev/null +++ b/src/variable/definition/enum/variable-kind.enum.mts @@ -0,0 +1,13 @@ +const VariableKind = { + Nil: "nil", + Boolean: "boolean", + Number: "number", + String: "string", + Table: "table", + Function: "function", + NativeFunction: "native-function", +} as const satisfies Record; + +type VariableKindEnum = typeof VariableKind[keyof typeof VariableKind]; + +export { VariableKind, type VariableKindEnum }; diff --git a/src/variable/definition/interface/_index.mts b/src/variable/definition/interface/_index.mts new file mode 100644 index 0000000..a54a875 --- /dev/null +++ b/src/variable/definition/interface/_index.mts @@ -0,0 +1,8 @@ +export type * from "./base-variable.interface.mjs"; +export type * from "./variable-boolean.interface.mjs"; +export type * from "./variable-function.interface.mjs"; +export type * from "./variable-native-function.interface.mjs"; +export type * from "./variable-nil.interface.mjs"; +export type * from "./variable-number.interface.mjs"; +export type * from "./variable-string.interface.mjs"; +export type * from "./variable-table.interface.mjs"; diff --git a/src/variable/definition/interface/base-variable.interface.mts b/src/variable/definition/interface/base-variable.interface.mts new file mode 100644 index 0000000..8487ec9 --- /dev/null +++ b/src/variable/definition/interface/base-variable.interface.mts @@ -0,0 +1,8 @@ +import type { Variable } from "../type/variable.type.mjs"; + +interface BaseVariable +{ + locals?: Array>; +} + +export type { BaseVariable }; diff --git a/src/variable/definition/interface/variable-boolean.interface.mts b/src/variable/definition/interface/variable-boolean.interface.mts new file mode 100644 index 0000000..f476be1 --- /dev/null +++ b/src/variable/definition/interface/variable-boolean.interface.mts @@ -0,0 +1,10 @@ +import type { BaseVariable } from "./base-variable.interface.mjs"; +import type { VariableKind } from "../enum/variable-kind.enum.mjs"; + +interface VariableBoolean extends BaseVariable +{ + data_type: typeof VariableKind.Boolean; + boolean: boolean; +} + +export type { VariableBoolean }; diff --git a/src/variable/definition/interface/variable-function.interface.mts b/src/variable/definition/interface/variable-function.interface.mts new file mode 100644 index 0000000..15d64e3 --- /dev/null +++ b/src/variable/definition/interface/variable-function.interface.mts @@ -0,0 +1,10 @@ +import type { BaseVariable } from "./base-variable.interface.mjs"; +import type { VariableKind } from "../enum/variable-kind.enum.mjs"; + +interface VariableFunction extends BaseVariable +{ + data_type: typeof VariableKind.Function; + function_id: number; +} + +export type { VariableFunction }; diff --git a/src/variable/definition/interface/variable-native-function.interface.mts b/src/variable/definition/interface/variable-native-function.interface.mts new file mode 100644 index 0000000..2f6e1d2 --- /dev/null +++ b/src/variable/definition/interface/variable-native-function.interface.mts @@ -0,0 +1,11 @@ +import type { NativeFunction } from "../../../boundary/definition/type/native-function.type.mjs"; +import type { BaseVariable } from "./base-variable.interface.mjs"; +import type { VariableKind } from "../enum/variable-kind.enum.mjs"; + +interface VariableNativeFunction extends BaseVariable +{ + data_type: typeof VariableKind.NativeFunction; + native_function: NativeFunction; +} + +export type { VariableNativeFunction }; diff --git a/src/variable/definition/interface/variable-nil.interface.mts b/src/variable/definition/interface/variable-nil.interface.mts new file mode 100644 index 0000000..646e824 --- /dev/null +++ b/src/variable/definition/interface/variable-nil.interface.mts @@ -0,0 +1,9 @@ +import type { BaseVariable } from "./base-variable.interface.mjs"; +import type { VariableKind } from "../enum/variable-kind.enum.mjs"; + +interface VariableNil extends BaseVariable +{ + data_type: typeof VariableKind.Nil; +} + +export type { VariableNil }; diff --git a/src/variable/definition/interface/variable-number.interface.mts b/src/variable/definition/interface/variable-number.interface.mts new file mode 100644 index 0000000..e3efdd0 --- /dev/null +++ b/src/variable/definition/interface/variable-number.interface.mts @@ -0,0 +1,10 @@ +import type { BaseVariable } from "./base-variable.interface.mjs"; +import type { VariableKind } from "../enum/variable-kind.enum.mjs"; + +interface VariableNumber extends BaseVariable +{ + data_type: typeof VariableKind.Number; + number: number; +} + +export type { VariableNumber }; diff --git a/src/variable/definition/interface/variable-string.interface.mts b/src/variable/definition/interface/variable-string.interface.mts new file mode 100644 index 0000000..b0939dd --- /dev/null +++ b/src/variable/definition/interface/variable-string.interface.mts @@ -0,0 +1,10 @@ +import type { BaseVariable } from "./base-variable.interface.mjs"; +import type { VariableKind } from "../enum/variable-kind.enum.mjs"; + +interface VariableString extends BaseVariable +{ + data_type: typeof VariableKind.String; + string: string; +} + +export type { VariableString }; diff --git a/src/variable/definition/interface/variable-table.interface.mts b/src/variable/definition/interface/variable-table.interface.mts new file mode 100644 index 0000000..57ffd74 --- /dev/null +++ b/src/variable/definition/interface/variable-table.interface.mts @@ -0,0 +1,11 @@ +import type { VariableTableMapType } from "../type/variable-table-map.type.mjs"; +import type { BaseVariable } from "./base-variable.interface.mjs"; +import type { VariableKind } from "../enum/variable-kind.enum.mjs"; + +interface VariableTable extends BaseVariable +{ + data_type: typeof VariableKind.Table; + table: VariableTableMapType; +} + +export type { VariableTable }; diff --git a/src/variable/definition/type/_index.mts b/src/variable/definition/type/_index.mts new file mode 100644 index 0000000..24f36f5 --- /dev/null +++ b/src/variable/definition/type/_index.mts @@ -0,0 +1,3 @@ +export type * from "./variable-table-map.type.mjs"; +export type * from "./variable.type.mjs"; +export type * from "./variable-value.type.mjs"; diff --git a/src/variable/definition/type/variable-table-map.type.mts b/src/variable/definition/type/variable-table-map.type.mts new file mode 100644 index 0000000..a83c2d2 --- /dev/null +++ b/src/variable/definition/type/variable-table-map.type.mts @@ -0,0 +1,5 @@ +import type { Variable } from "./variable.type.mjs"; + +type VariableTableMapType = Map; + +export type { VariableTableMapType }; diff --git a/src/variable/definition/type/variable-value.type.mts b/src/variable/definition/type/variable-value.type.mts new file mode 100644 index 0000000..eb1c23d --- /dev/null +++ b/src/variable/definition/type/variable-value.type.mts @@ -0,0 +1,15 @@ +import type { NativeFunction } from "../../../boundary/definition/type/native-function.type.mjs"; +import type { VariableTableMapType } from "./variable-table-map.type.mjs"; +import type { VariableKind, VariableKindEnum } from "../enum/variable-kind.enum.mjs"; + +type VariableValueType + = K extends typeof VariableKind.Nil ? undefined + : K extends typeof VariableKind.Boolean ? boolean + : K extends typeof VariableKind.Number ? number + : K extends typeof VariableKind.String ? string + : K extends typeof VariableKind.Table ? VariableTableMapType + : K extends typeof VariableKind.Function ? number + : K extends typeof VariableKind.NativeFunction ? NativeFunction + : never; + +export type { VariableValueType }; diff --git a/src/variable/definition/type/variable.type.mts b/src/variable/definition/type/variable.type.mts new file mode 100644 index 0000000..b422225 --- /dev/null +++ b/src/variable/definition/type/variable.type.mts @@ -0,0 +1,28 @@ +import type { VariableBoolean } from "../interface/variable-boolean.interface.mjs"; +import type { VariableFunction } from "../interface/variable-function.interface.mjs"; +import type { VariableNativeFunction } from "../interface/variable-native-function.interface.mjs"; +import type { VariableNil } from "../interface/variable-nil.interface.mjs"; +import type { VariableNumber } from "../interface/variable-number.interface.mjs"; +import type { VariableString } from "../interface/variable-string.interface.mjs"; +import type { VariableTable } from "../interface/variable-table.interface.mjs"; + +type Variable = ( + | VariableNil + | VariableBoolean + | VariableNumber + | VariableString + | VariableTable + | VariableFunction + | VariableNativeFunction +); + +export type { + Variable, + VariableBoolean, + VariableFunction, + VariableNativeFunction, + VariableNil, + VariableNumber, + VariableString, + VariableTable, +}; diff --git a/src/variable/equals.mts b/src/variable/equals.mts new file mode 100644 index 0000000..12feeec --- /dev/null +++ b/src/variable/equals.mts @@ -0,0 +1,57 @@ +import type { Variable } from "./definition/type/variable.type.mjs"; +import { VariableKind } from "./definition/enum/variable-kind.enum.mjs"; +import { isVariableKind } from "./predicate/is-variable-kind.mjs"; +import { isNullish } from "@vitruvius-labs/ts-predicate"; + +function equals(a: Variable | undefined, b: Variable | undefined): boolean +{ + if (a === b) + { + return true; + } + + if (isNullish(a) || isNullish(b)) + { + return false; + } + + switch (a.data_type) + { + case VariableKind.Nil: + return true; + case VariableKind.Boolean: + return isVariableKind(b, VariableKind.Boolean) && a.boolean === b.boolean; + case VariableKind.Number: + return isVariableKind(b, VariableKind.Number) && a.number === b.number; + case VariableKind.String: + return isVariableKind(b, VariableKind.String) && a.string === b.string; + case VariableKind.Function: + return isVariableKind(b, VariableKind.Function) && a.function_id === b.function_id; + case VariableKind.NativeFunction: + return isVariableKind(b, VariableKind.NativeFunction) && a.native_function === b.native_function; + case VariableKind.Table: + { + if (!isVariableKind(b, VariableKind.Table)) + { + return false; + } + + if (a.table.size !== b.table.size) + { + return false; + } + + for (const key of a.table.keys()) + { + if (!equals(a.table.get(key), b.table.get(key))) + { + return false; + } + } + + return true; + } + } +} + +export { equals }; diff --git a/src/variable/nil.mts b/src/variable/nil.mts new file mode 100644 index 0000000..3b910e5 --- /dev/null +++ b/src/variable/nil.mts @@ -0,0 +1,6 @@ +import type { VariableNil } from "./definition/interface/variable-nil.interface.mjs"; +import { VariableKind } from "./definition/enum/variable-kind.enum.mjs"; + +const nil: VariableNil = Object.freeze({ data_type: VariableKind.Nil }); + +export { nil }; diff --git a/src/variable/predicate/_index.mts b/src/variable/predicate/_index.mts new file mode 100644 index 0000000..deafca5 --- /dev/null +++ b/src/variable/predicate/_index.mts @@ -0,0 +1,6 @@ +export * from "./is-variable-kind-enum.mjs"; +export * from "./is-variable.mjs"; +export * from "./is-variable-kind.mjs"; +export * from "./is-nil.mjs"; +export * from "./assert-variable.mjs"; +export * from "./assert-variable-kind.mjs"; diff --git a/src/variable/predicate/assert-variable-kind.mts b/src/variable/predicate/assert-variable-kind.mts new file mode 100644 index 0000000..40e0d9f --- /dev/null +++ b/src/variable/predicate/assert-variable-kind.mts @@ -0,0 +1,16 @@ +import { ValidationError } from "@vitruvius-labs/ts-predicate"; +import type { VariableKindEnum } from "../definition/enum/variable-kind.enum.mjs"; +import type { Variable } from "../definition/type/variable.type.mjs"; +import { assertVariable } from "./assert-variable.mjs"; + +function assertVariableKind(variable: unknown, kind: K): asserts variable is Variable & { data_type: K } +{ + assertVariable(variable); + + if (variable.data_type !== kind) + { + throw new ValidationError(`Expected ${kind}, got ${variable.data_type}`); + } +} + +export { assertVariableKind }; diff --git a/src/variable/predicate/assert-variable.mts b/src/variable/predicate/assert-variable.mts new file mode 100644 index 0000000..70deed3 --- /dev/null +++ b/src/variable/predicate/assert-variable.mts @@ -0,0 +1,13 @@ +import { ValidationError } from "@vitruvius-labs/ts-predicate"; +import type { Variable } from "../definition/type/variable.type.mjs"; +import { isVariable } from "./is-variable.mjs"; + +function assertVariable(variable: unknown): asserts variable is Variable +{ + if (!isVariable(variable)) + { + throw new ValidationError("Expected Variable"); + } +} + +export { assertVariable }; diff --git a/src/variable/predicate/is-nil.mts b/src/variable/predicate/is-nil.mts new file mode 100644 index 0000000..56814aa --- /dev/null +++ b/src/variable/predicate/is-nil.mts @@ -0,0 +1,12 @@ +import type { Variable } from "../definition/type/variable.type.mjs"; +import type { VariableNil } from "../definition/interface/variable-nil.interface.mjs"; +import { isVariableKind } from "./is-variable-kind.mjs"; +import { VariableKind } from "../definition/enum/variable-kind.enum.mjs"; +import { isNullish } from "@vitruvius-labs/ts-predicate"; + +function isNil(parameter: Variable | undefined): parameter is undefined | VariableNil +{ + return isNullish(parameter) || isVariableKind(parameter, VariableKind.Nil); +} + +export { isNil }; diff --git a/src/variable/predicate/is-variable-kind-enum.mts b/src/variable/predicate/is-variable-kind-enum.mts new file mode 100644 index 0000000..0f14db8 --- /dev/null +++ b/src/variable/predicate/is-variable-kind-enum.mts @@ -0,0 +1,17 @@ +import { isEnumValue } from "@vitruvius-labs/ts-predicate"; +import { VariableKind, type VariableKindEnum } from "../definition/enum/variable-kind.enum.mjs"; + +function isVariableKindEnum(value: unknown): value is VariableKindEnum +{ + return isEnumValue(value, [ + VariableKind.Nil, + VariableKind.Boolean, + VariableKind.Number, + VariableKind.String, + VariableKind.Table, + VariableKind.Function, + VariableKind.NativeFunction, + ]); +} + +export { isVariableKindEnum }; diff --git a/src/variable/predicate/is-variable-kind.mts b/src/variable/predicate/is-variable-kind.mts new file mode 100644 index 0000000..bbe2b94 --- /dev/null +++ b/src/variable/predicate/is-variable-kind.mts @@ -0,0 +1,10 @@ +import type { VariableKindEnum } from "../definition/enum/variable-kind.enum.mjs"; +import type { Variable } from "../definition/type/variable.type.mjs"; +import { isVariable } from "./is-variable.mjs"; + +function isVariableKind(variable: unknown, kind: K): variable is Variable & { data_type: K } +{ + return isVariable(variable) && variable.data_type === kind; +} + +export { isVariableKind }; diff --git a/src/variable/predicate/is-variable.mts b/src/variable/predicate/is-variable.mts new file mode 100644 index 0000000..70cdce2 --- /dev/null +++ b/src/variable/predicate/is-variable.mts @@ -0,0 +1,18 @@ +import { isStructuredData } from "@vitruvius-labs/ts-predicate"; +import type { Variable } from "../definition/type/variable.type.mjs"; +import { isVariableKindEnum } from "./is-variable-kind-enum.mjs"; + +function isVariable(value: unknown): value is Variable +{ + return isStructuredData( + value, + { + data_type: isVariableKindEnum, + }, + { + allowExtraneousProperties: true, + } + ); +} + +export { isVariable }; diff --git a/src/variable/unwrap-variable.mts b/src/variable/unwrap-variable.mts new file mode 100644 index 0000000..23cd57e --- /dev/null +++ b/src/variable/unwrap-variable.mts @@ -0,0 +1,87 @@ +import type { Variable } from "./definition/type/variable.type.mjs"; +import type { VariableBoolean } from "./definition/interface/variable-boolean.interface.mjs"; +import type { VariableFunction } from "./definition/interface/variable-function.interface.mjs"; +import type { VariableNativeFunction } from "./definition/interface/variable-native-function.interface.mjs"; +import type { VariableNil } from "./definition/interface/variable-nil.interface.mjs"; +import type { VariableNumber } from "./definition/interface/variable-number.interface.mjs"; +import type { VariableString } from "./definition/interface/variable-string.interface.mjs"; +import type { VariableTable } from "./definition/interface/variable-table.interface.mjs"; +import type { NativeFunction } from "../_index.mjs"; +import type { TableType } from "../boundary/definition/type/table.type.mjs"; +import type { TableMapType } from "../boundary/definition/type/table-map.type.mjs"; +import type { FunctionReferenceType } from "../boundary/definition/type/function-reference.type.mjs"; +import type { ValueType } from "../boundary/definition/type/value.type.mjs"; +import { VariableKind } from "./definition/enum/variable-kind.enum.mjs"; +import { isTableMapKeyType } from "../boundary/predicate/is-table-map-key-type.mjs"; + +class VariableUnwrapUtility +{ + public static unwrap(this: void, input: VariableNil): undefined; + public static unwrap(this: void, input: VariableBoolean): boolean; + public static unwrap(this: void, input: VariableNumber): number; + public static unwrap(this: void, input: VariableString): string; + public static unwrap(this: void, input: VariableTable): TableType; + public static unwrap(this: void, input: VariableFunction): FunctionReferenceType; + public static unwrap(this: void, input: VariableNativeFunction): NativeFunction; + public static unwrap(this: void, input: Variable): ValueType; + + public static unwrap(this: void, input: Variable): ValueType + { + switch (input.data_type) + { + case VariableKind.Nil: + return undefined; + case VariableKind.Boolean: + return input.boolean; + case VariableKind.Number: + return input.number; + case VariableKind.String: + return input.string; + case VariableKind.Table: + return VariableUnwrapUtility.unwrapTable(input); + case VariableKind.Function: + return { function_id: input.function_id }; + case VariableKind.NativeFunction: + return input.native_function; + } + } + + public static unwrapTable(this: void, input: VariableTable): TableType + { + const output: Array = []; + + for (let i = 1; i <= input.table.size; ++i) + { + const item: Variable | undefined = input.table.get(i); + + if (item === undefined) + { + // Not a table that can be unwrapped as an array + return VariableUnwrapUtility.unwrapTableGeneric(input); + } + + output.push(VariableUnwrapUtility.unwrap(item)); + } + + return output; + } + + protected static unwrapTableGeneric(this: void, input: VariableTable): TableMapType + { + const output: TableMapType = new Map(); + + for (const [key, value] of input.table.entries()) + { + if (!isTableMapKeyType(key)) + { + continue; + } + + output.set(key, VariableUnwrapUtility.unwrap(value)); + } + + return output; + } +} + +export { VariableUnwrapUtility }; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..093cc41 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./build/esm", + "declarationDir": "./build/types", + "rootDir": "./src", + "declaration": true, + "declarationMap": false, + "removeComments": false, + "sourceMap": false, + "noEmit": false, + "listFiles": false, + "listEmittedFiles": false + }, + "include": [ + "./src" + ] +} diff --git a/tsconfig.json b/tsconfig.json index eae2e17..550f3cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,53 @@ { - "compilerOptions": - { - "module": "commonjs", - "esModuleInterop": true, - "moduleResolution": "node", - "target": "es2017", - - "noImplicitAny": true, - "removeComments": true, - "allowUnreachableCode": false, - "strictNullChecks": true, - "incremental": true - }, - - "lib": - [ - "es2017" - ], - - "files": - [ - "index.ts" - ] + "$schema": "https://json.schemastore.org/tsconfig", + "compileOnSave": false, + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "pretty": true, + "declaration": false, + "importHelpers": true, + "newLine": "LF", + "preserveConstEnums": true, + "sourceMap": false, + "allowJs": false, + "checkJs": false, + "noStrictGenericChecks": false, + "allowSyntheticDefaultImports": false, + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "noErrorTruncation": true, + "target": "ESNext", + "lib": [ + "ESNext" + ], + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "useUnknownInCatchVariables": true + }, + "include": [ + "./src" + ], + "exclude": [ + "./node_modules" + ] } - diff --git a/vitruvius-labs-lua-engine.code-workspace b/vitruvius-labs-lua-engine.code-workspace new file mode 100644 index 0000000..63a4bb5 --- /dev/null +++ b/vitruvius-labs-lua-engine.code-workspace @@ -0,0 +1,64 @@ +{ + "folders": [ + { + "name": "pnpm-workspace", + "path": "." + }, + ], + "settings": { + // Formatting + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "editor.detectIndentation": false, + "editor.indentSize": "tabSize", + "editor.insertSpaces": false, + // Formatting overrides + "[yaml]": { + "editor.insertSpaces": true + }, + "[shellscript]": { + "editor.insertSpaces": true + }, + // Linters & prettier extensions settings + "eslint.validate": [ + "javascript", + "typescript" + ], + "eslint.useFlatConfig": true, + "eslint.options": { + "overrideConfigFile": "./eslint.config.mjs" + }, + "eslint.workingDirectories": [ + "./packages/architectura", + "./packages/aws-s3", + "./packages/aws-signature-v4", + "./packages/aws-sqs", + "./packages/functional", + "./packages/mockingbird", + "./packages/testing-ground", + "./packages/toolbox", + "./packages/ts-predicate" + ], + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.fixAll.tslint": "never" + }, + "eslint.format.enable": true, + // Code spell checker extension settings + "cSpell.enabled": true, + // UX + "explorer.fileNesting.enabled": true, + "search.exclude": { + "**/dist": true, + "**/lib": true, + "**/.yarn": true + }, + // TypeScript + "js/ts.implicitProjectConfig.target": "ESNext", + "typescript.tsdk": "./node_modules/typescript/bin", + "typescript.tsserver.maxTsServerMemory": 4096, + "typescript.preferences.quoteStyle": "double", + "prettier.enable": false, + } +}