diff --git a/src/config.lsc b/src/config.lsc index 283df0f..410a45b 100644 --- a/src/config.lsc +++ b/src/config.lsc @@ -19,6 +19,11 @@ export getMetadata() -> { defaultValue: "default" stage: "1" } + catchExpression: { + description: "Catch and transform errors while evaluating an expression." + valueType: "boolean" + stage: "0" + } bangCall: { description: "Call functions with paren-free syntax using `!`" valueType: "boolean" @@ -106,6 +111,7 @@ export getParserOpts(pluginOpts, initialParserOpts) -> if pluginOpts?.placeholderArgs: plugins.push("syntacticPlaceholder") if pluginOpts?.placeholder: parserOpts.placeholder = pluginOpts.placeholder + if pluginOpts.catchExpression: plugins.push("catchExpression") // TODO: watch upstream on pattern matching; default to their syntax when complete // patternMatchingVersion = pluginOpts?.patternMatching or "v4" diff --git a/src/lscNodeTypes.lsc b/src/lscNodeTypes.lsc index 021716c..dc6ddfd 100644 --- a/src/lscNodeTypes.lsc +++ b/src/lscNodeTypes.lsc @@ -286,3 +286,37 @@ export registerLightscriptNodeTypes(t): void -> }, }, }); + + if not t.hasType("CatchExpression"): + definePluginType("CatchExpression", { + builder: ["expression", "cases"], + visitor: ["expression", "cases"], + aliases: ["Expression"], + fields: { + expression: { + validate: assertNodeType("Expression") + }, + cases: { + validate: chain(assertValueType("array"), assertEach(assertNodeType("CatchCase"))) + } + } + }); + + if not t.hasType("CatchCase"): + definePluginType("CatchCase", { + builder: ["atoms", "binding", "consequent"], + visitor: ["atoms", "binding", "consequent"], + fields: { + atoms: { + validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression"))) + optional: true + } + binding: { + validate: assertNodeType("Identifier", "ArrayPattern", "ObjectPattern") + optional: true + } + consequent: { + validate: assertNodeType("Expression", "Statement") + } + } + }); diff --git a/src/transforms/catchExpression.lsc b/src/transforms/catchExpression.lsc new file mode 100644 index 0000000..eb4b466 --- /dev/null +++ b/src/transforms/catchExpression.lsc @@ -0,0 +1,40 @@ +import t, { isa } from '../types' + +import { + getLoc, placeAtLoc as atLoc, placeAtNode as atNode, + getSurroundingLoc, span, traverse, + placeTreeAtLocWhenUnplaced as allAtLoc +} from 'ast-loc-utils' + +import { getMatchInfo, transformMatchCases } from './match' + +transformPessimizedCatchExpression(path, isLinter): void -> + { node } = path + + argRef = path.scope.generateUidIdentifier("err")~atLoc(getLoc(node)~span(1)) + catchBody = getMatchInfo(path, argRef, isLinter)~transformMatchCases(path.get("cases")) + + iife = t.callExpression( + t.arrowFunctionExpression( + [] + t.blockStatement([ + t.tryStatement( + t.blockStatement([ + t.returnStatement(node.expression) + ]) + t.catchClause( + argRef + t.blockStatement([catchBody]) + ) + ) + ]) + node.expression~isa("AwaitExpression") // async + ) + [] + ) + + path.replaceWith(iife) + +export transformCatchExpression(path, isLinter): void -> + transformPessimizedCatchExpression(path, isLinter) + diff --git a/src/visitors/main.lsc b/src/visitors/main.lsc index 1f2d931..0fe7790 100644 --- a/src/visitors/main.lsc +++ b/src/visitors/main.lsc @@ -15,6 +15,7 @@ import { maybeTransformArrayWithSpreadLoops, maybeTransformObjectWithSpreadLoops import { transformExistentialExpression, transformSafeSpreadElement } from "../transforms/safe" import { maybeReplaceWithInlinedOperator } from "../transforms/inlinedOperators" import { transformForInArrayStatement, transformForInObjectStatement, lintForInArrayStatement, lintForInObjectStatement } from "../transforms/for" +import { transformCatchExpression } from "../transforms/catchExpression" import { markIdentifier } from "../state/stdlib" @@ -221,6 +222,9 @@ export default mainPass(compilerState, programPath): void -> MatchStatement(path): void -> matching.transformMatchStatement(path, opts.__linter) + CatchExpression(path): void -> + transformCatchExpression(path, opts.__linter) + ForOfStatement(path): void -> // Auto-const { node } = path; { left } = node diff --git a/test/fixtures/catch-expression/basic/actual.js b/test/fixtures/catch-expression/basic/actual.js new file mode 100644 index 0000000..9ab2efb --- /dev/null +++ b/test/fixtures/catch-expression/basic/actual.js @@ -0,0 +1,2 @@ +a = b() + catch Error: panic() diff --git a/test/fixtures/catch-expression/basic/expected.js b/test/fixtures/catch-expression/basic/expected.js new file mode 100644 index 0000000..30824e0 --- /dev/null +++ b/test/fixtures/catch-expression/basic/expected.js @@ -0,0 +1,9 @@ +import _isMatch from "@oigroup/lightscript-runtime/isMatch";const a = (() => { + try { + return b(); + } catch (_err) { + if (_isMatch(Error, _err)) { + return panic(); + } + } +})(); \ No newline at end of file diff --git a/test/fixtures/catch-expression/options.json b/test/fixtures/catch-expression/options.json new file mode 100644 index 0000000..672c3ec --- /dev/null +++ b/test/fixtures/catch-expression/options.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + ["lightscript", { + "isLightScript": true, + "catchExpression": true + }] + ] +} +