@malloydata/malloy 0.0.392 → 0.0.394

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,6 +42,7 @@ import { ExprCompareContext } from "./MalloyParser";
42
42
  import { ExprWarnLikeContext } from "./MalloyParser";
43
43
  import { ExprNullCheckContext } from "./MalloyParser";
44
44
  import { ExprWarnInContext } from "./MalloyParser";
45
+ import { ExprInGivenContext } from "./MalloyParser";
45
46
  import { ExprApplyContext } from "./MalloyParser";
46
47
  import { ExprNotContext } from "./MalloyParser";
47
48
  import { ExprLogicalAndContext } from "./MalloyParser";
@@ -104,6 +105,7 @@ import { DefineQueryContext } from "./MalloyParser";
104
105
  import { DefineGivenStatementContext } from "./MalloyParser";
105
106
  import { GivenDefListContext } from "./MalloyParser";
106
107
  import { GivenDefContext } from "./MalloyParser";
108
+ import { GivenModifierContext } from "./MalloyParser";
107
109
  import { GivenNameDefContext } from "./MalloyParser";
108
110
  import { GivenTypeContext } from "./MalloyParser";
109
111
  import { TopLevelAnonQueryDefContext } from "./MalloyParser";
@@ -792,6 +794,18 @@ export interface MalloyParserListener extends ParseTreeListener {
792
794
  * @param ctx the parse tree
793
795
  */
794
796
  exitExprWarnIn?: (ctx: ExprWarnInContext) => void;
797
+ /**
798
+ * Enter a parse tree produced by the `exprInGiven`
799
+ * labeled alternative in `MalloyParser.fieldExpr`.
800
+ * @param ctx the parse tree
801
+ */
802
+ enterExprInGiven?: (ctx: ExprInGivenContext) => void;
803
+ /**
804
+ * Exit a parse tree produced by the `exprInGiven`
805
+ * labeled alternative in `MalloyParser.fieldExpr`.
806
+ * @param ctx the parse tree
807
+ */
808
+ exitExprInGiven?: (ctx: ExprInGivenContext) => void;
795
809
  /**
796
810
  * Enter a parse tree produced by the `exprApply`
797
811
  * labeled alternative in `MalloyParser.fieldExpr`.
@@ -1506,6 +1520,16 @@ export interface MalloyParserListener extends ParseTreeListener {
1506
1520
  * @param ctx the parse tree
1507
1521
  */
1508
1522
  exitGivenDef?: (ctx: GivenDefContext) => void;
1523
+ /**
1524
+ * Enter a parse tree produced by `MalloyParser.givenModifier`.
1525
+ * @param ctx the parse tree
1526
+ */
1527
+ enterGivenModifier?: (ctx: GivenModifierContext) => void;
1528
+ /**
1529
+ * Exit a parse tree produced by `MalloyParser.givenModifier`.
1530
+ * @param ctx the parse tree
1531
+ */
1532
+ exitGivenModifier?: (ctx: GivenModifierContext) => void;
1509
1533
  /**
1510
1534
  * Enter a parse tree produced by `MalloyParser.givenNameDef`.
1511
1535
  * @param ctx the parse tree
@@ -42,6 +42,7 @@ import { ExprCompareContext } from "./MalloyParser";
42
42
  import { ExprWarnLikeContext } from "./MalloyParser";
43
43
  import { ExprNullCheckContext } from "./MalloyParser";
44
44
  import { ExprWarnInContext } from "./MalloyParser";
45
+ import { ExprInGivenContext } from "./MalloyParser";
45
46
  import { ExprApplyContext } from "./MalloyParser";
46
47
  import { ExprNotContext } from "./MalloyParser";
47
48
  import { ExprLogicalAndContext } from "./MalloyParser";
@@ -104,6 +105,7 @@ import { DefineQueryContext } from "./MalloyParser";
104
105
  import { DefineGivenStatementContext } from "./MalloyParser";
105
106
  import { GivenDefListContext } from "./MalloyParser";
106
107
  import { GivenDefContext } from "./MalloyParser";
108
+ import { GivenModifierContext } from "./MalloyParser";
107
109
  import { GivenNameDefContext } from "./MalloyParser";
108
110
  import { GivenTypeContext } from "./MalloyParser";
109
111
  import { TopLevelAnonQueryDefContext } from "./MalloyParser";
@@ -580,6 +582,13 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
580
582
  * @return the visitor result
581
583
  */
582
584
  visitExprWarnIn?: (ctx: ExprWarnInContext) => Result;
585
+ /**
586
+ * Visit a parse tree produced by the `exprInGiven`
587
+ * labeled alternative in `MalloyParser.fieldExpr`.
588
+ * @param ctx the parse tree
589
+ * @return the visitor result
590
+ */
591
+ visitExprInGiven?: (ctx: ExprInGivenContext) => Result;
583
592
  /**
584
593
  * Visit a parse tree produced by the `exprApply`
585
594
  * labeled alternative in `MalloyParser.fieldExpr`.
@@ -999,6 +1008,12 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
999
1008
  * @return the visitor result
1000
1009
  */
1001
1010
  visitGivenDef?: (ctx: GivenDefContext) => Result;
1011
+ /**
1012
+ * Visit a parse tree produced by `MalloyParser.givenModifier`.
1013
+ * @param ctx the parse tree
1014
+ * @return the visitor result
1015
+ */
1016
+ visitGivenModifier?: (ctx: GivenModifierContext) => Result;
1002
1017
  /**
1003
1018
  * Visit a parse tree produced by `MalloyParser.givenNameDef`.
1004
1019
  * @param ctx the parse tree
@@ -1,5 +1,5 @@
1
1
  import { ParserRuleContext } from 'antlr4ts';
2
- import type { ParseTree } from 'antlr4ts/tree';
2
+ import type { ParseTree, TerminalNode } from 'antlr4ts/tree';
3
3
  import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
4
4
  import type { MalloyParserVisitor } from './lib/Malloy/MalloyParserVisitor';
5
5
  import type * as parse from './lib/Malloy/MalloyParser';
@@ -72,7 +72,13 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
72
72
  protected getFieldName(cx: HasID): ast.FieldName;
73
73
  protected getModelEntryName(cx: HasID): ast.ModelEntryReference;
74
74
  defaultResult(): ast.MalloyElement;
75
- protected astAt<MT extends ast.MalloyElement>(el: MT, cx: ParserRuleContext): MT;
75
+ /**
76
+ * Attach a source location to an AST element and return it. The range
77
+ * is taken from the supplied parse-tree node — either a
78
+ * `ParserRuleContext` (entire rule's token span) or a `TerminalNode`
79
+ * (just that one token).
80
+ */
81
+ protected astAt<MT extends ast.MalloyElement>(el: MT, cx: ParserRuleContext | TerminalNode): MT;
76
82
  protected getSourceCode(cx: ParserRuleContext): string;
77
83
  protected getFilterElement(cx: parse.FieldExprContext): ast.FilterElement;
78
84
  protected getFieldDefs(cxList: parse.FieldDefContext[], makeFieldDef: ast.FieldDeclarationConstructor): ast.AtomicFieldDeclaration[];
@@ -275,6 +281,7 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
275
281
  visitExprArrayLiteral(pcx: parse.ExprArrayLiteralContext): ast.ArrayLiteral;
276
282
  visitExprWarnLike(pcx: parse.ExprWarnLikeContext): ast.ExprCompare;
277
283
  visitExprNullCheck(pcx: parse.ExprNullCheckContext): ast.ExprIsNull;
284
+ visitExprInGiven(pcx: parse.ExprInGivenContext): ast.ExprInGiven;
278
285
  visitExprWarnIn(pcx: parse.ExprWarnInContext): ast.ExprLegacyIn;
279
286
  visitTickFilterString(pcx: parse.TickFilterStringContext): ast.ExprFilterExpression;
280
287
  visitTripFilterString(pcx: parse.TripFilterStringContext): ast.ExprFilterExpression;
@@ -195,10 +195,19 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
195
195
  defaultResult() {
196
196
  return new ast.Unimplemented();
197
197
  }
198
+ /**
199
+ * Attach a source location to an AST element and return it. The range
200
+ * is taken from the supplied parse-tree node — either a
201
+ * `ParserRuleContext` (entire rule's token span) or a `TerminalNode`
202
+ * (just that one token).
203
+ */
198
204
  astAt(el, cx) {
205
+ const range = cx instanceof antlr4ts_1.ParserRuleContext
206
+ ? this.rangeFromContext(cx)
207
+ : (0, utils_1.rangeFromToken)(this.parseInfo.sourceInfo, cx.symbol);
199
208
  el.location = {
200
209
  url: this.parseInfo.sourceURL,
201
- range: this.rangeFromContext(cx),
210
+ range,
202
211
  };
203
212
  return el;
204
213
  }
@@ -317,7 +326,25 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
317
326
  this.contextError(afterIs, 'given-no-tags-after-is', 'Annotations are not allowed between `is` and the default value of a given; place them above the declaration');
318
327
  }
319
328
  }
320
- const decl = new ast.GivenDeclaration(name, typeDef, defVal);
329
+ // The modifier slot accepts any identifier and rejects anything but
330
+ // `inline` at AST-build — this keeps `inline` from being a reserved
331
+ // word elsewhere in the language (fields, sources, view names, etc.
332
+ // can still be called `inline`). The cost: a slightly later error
333
+ // surface if someone misspells the modifier.
334
+ // Case-insensitive match: Malloy keywords are case-insensitive in
335
+ // the lexer, so accept `inline`, `INLINE`, `Inline`, etc.
336
+ const modCx = pcx.givenModifier();
337
+ let inline = false;
338
+ if (modCx) {
339
+ const modText = (0, parse_utils_1.getId)(modCx);
340
+ if (modText.toLowerCase() === 'inline') {
341
+ inline = true;
342
+ }
343
+ else {
344
+ this.contextError(modCx, 'invalid-given-modifier', { modifier: modText });
345
+ }
346
+ }
347
+ const decl = new ast.GivenDeclaration(name, typeDef, defVal, inline);
321
348
  decl.extendNote({ notes: this.getNotes(pcx.tags()) });
322
349
  return this.astAt(decl, pcx);
323
350
  }
@@ -1766,6 +1793,14 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
1766
1793
  const expr = pcx.fieldExpr();
1767
1794
  return this.astAt(new ast.ExprIsNull(this.getFieldExpr(expr), pcx.NOT() ? '!=' : '='), pcx);
1768
1795
  }
1796
+ visitExprInGiven(pcx) {
1797
+ this.inExperiment('givens', pcx);
1798
+ const lhs = this.getFieldExpr(pcx.fieldExpr());
1799
+ const isNot = !!pcx.NOT();
1800
+ const givenName = pcx.GIVEN_REF().text.slice(1);
1801
+ const givenRef = this.astAt(new ast.GivenReference(givenName), pcx.GIVEN_REF());
1802
+ return this.astAt(new ast.ExprInGiven(lhs, isNot, givenRef), pcx);
1803
+ }
1769
1804
  visitExprWarnIn(pcx) {
1770
1805
  const expr = this.getFieldExpr(pcx.fieldExpr());
1771
1806
  const isNot = !!pcx.NOT();
@@ -292,6 +292,28 @@ type MessageParameterTypes = {
292
292
  'given-reference-not-implemented': string;
293
293
  'given-not-found': string;
294
294
  'given-no-tags-after-is': string;
295
+ 'in-given-rhs-not-array': {
296
+ givenName: string;
297
+ actualType: string;
298
+ };
299
+ 'in-given-rhs-not-basic-array': {
300
+ givenName: string;
301
+ elementType: string;
302
+ };
303
+ 'in-given-type-mismatch': {
304
+ lhsType: string;
305
+ elementType: string;
306
+ };
307
+ 'inline-no-default': {
308
+ name: string;
309
+ };
310
+ 'inline-bad-operator': {
311
+ name: string;
312
+ operators: string;
313
+ };
314
+ 'invalid-given-modifier': {
315
+ modifier: string;
316
+ };
295
317
  'illegal-filter-type': string;
296
318
  'invalid-source-from-given': string;
297
319
  'aggregate-analytic-in-select': string;
@@ -95,6 +95,12 @@ exports.MESSAGE_FORMATTERS = {
95
95
  'case-else-type-does-not-match': e => `Case else type ${e.elseType} does not match return type ${e.returnType}`,
96
96
  'case-when-must-be-boolean': e => `Case when expression must be boolean, not ${e.whenType}`,
97
97
  'case-when-type-does-not-match': e => `Case when type ${e.whenType} does not match value type ${e.valueType}`,
98
+ 'in-given-rhs-not-array': e => `\`in $${e.givenName}\` requires \`${e.givenName}\` to be an array , but it is \`${e.actualType}\``,
99
+ 'in-given-rhs-not-basic-array': e => `\`in $${e.givenName}\` requires \`${e.givenName}\` to be an array of a basic type (string, number, etc.), but its element type is \`${e.elementType}\``,
100
+ 'in-given-type-mismatch': e => `\`in\` left-hand side type \`${e.lhsType}\` does not match the array element type \`${e.elementType}\``,
101
+ 'inline-no-default': e => `inline given \`${e.name}\` must have a value — there is nothing to inline without one`,
102
+ 'inline-bad-operator': e => `inline given \`${e.name}\` uses operator(s) not allowed in inline expressions: ${e.operators}`,
103
+ 'invalid-given-modifier': e => `Unknown modifier \`${e.modifier}\` on \`given:\` declaration; the only modifier allowed here is \`inline\``,
98
104
  };
99
105
  function makeLogMessage(code, parameters, options) {
100
106
  var _a, _b, _c, _d, _e;
@@ -82,6 +82,9 @@ function exprToStr(e, symbols) {
82
82
  .map(o => `${subExpr(o)}`)
83
83
  .join(',')}}}`;
84
84
  }
85
+ case 'inGiven': {
86
+ return `{${subExpr(e.e)} ${e.not ? 'not in' : 'in'} $${e.givenRef.refName}}`;
87
+ }
85
88
  case 'genericSQLExpr': {
86
89
  let sql = '';
87
90
  let i = 0;
@@ -185,6 +185,23 @@ function compileExpr(resultSet, context, expr, state = new utils_1.GenerateState
185
185
  const oneOf = expr.kids.oneOf.map(o => o.sql).join(',');
186
186
  return `${expr.kids.e.sql} ${expr.not ? 'NOT IN' : 'IN'} (${oneOf})`;
187
187
  }
188
+ case 'inGiven': {
189
+ const bound = resolveGivenBoundExpr(context, expr.givenRef.id, expr.givenRef.refName);
190
+ // null binding collapses to empty-set semantics — not the SQL
191
+ // `IN (NULL)` shape, which has confusing NULL-membership rules.
192
+ if (bound.node === 'null') {
193
+ return expr.not ? 'TRUE' : 'FALSE';
194
+ }
195
+ if (bound.node !== 'arrayLiteral') {
196
+ throw new Error(`Internal compiler error: 'inGiven' bound to '${bound.node}', expected 'arrayLiteral'. The translator should have rejected a non-array given here.`);
197
+ }
198
+ if (bound.kids.values.length === 0) {
199
+ return expr.not ? 'TRUE' : 'FALSE';
200
+ }
201
+ const elemSqls = bound.kids.values.map(v => exprToSQL(resultSet, context, v, state));
202
+ const verb = expr.not ? 'NOT IN' : 'IN';
203
+ return `${expr.e.sql} ${verb} (${elemSqls.join(',')})`;
204
+ }
188
205
  case 'like':
189
206
  case '!like': {
190
207
  const likeIt = expr.node === 'like' ? 'LIKE' : 'NOT LIKE';
@@ -599,20 +616,30 @@ function generateParameterFragment(resultSet, context, expr, state) {
599
616
  }
600
617
  throw new Error(`Can't generate SQL, no value for ${expr.path}`);
601
618
  }
602
- function generateGivenFragment(resultSet, context, expr, state) {
619
+ /**
620
+ * Resolve a given to the Expr that should stand in for it at SQL emit:
621
+ * supplied value if the caller bound one, otherwise the declaration's
622
+ * default. Throws when neither is available — that case is a query the
623
+ * compiler can't satisfy.
624
+ *
625
+ * Shared by `generateGivenFragment` ($NAME directly in an expression)
626
+ * and the `'inGiven'` SQL-emit case ($ARR in `expr in $ARR`).
627
+ */
628
+ function resolveGivenBoundExpr(context, id, refName) {
603
629
  var _a, _b;
604
- const id = expr.id;
605
630
  const supplied = (_b = (_a = context.prepareResultOptions) === null || _a === void 0 ? void 0 : _a.resolvedGivens) === null || _b === void 0 ? void 0 : _b.get(id);
606
- if (supplied !== undefined) {
607
- return exprToSQL(resultSet, context, supplied, state);
608
- }
609
- // The default may itself be a `$OTHER`-bearing expression — recursive
610
- // compile handles default chains.
631
+ if (supplied !== undefined)
632
+ return supplied;
611
633
  const decl = context.getModel().givens[id];
612
- if ((decl === null || decl === void 0 ? void 0 : decl.default) !== undefined) {
613
- return exprToSQL(resultSet, context, decl.default, state);
614
- }
615
- throw new Error(unsatisfiedGivenMessage(expr.refName));
634
+ if ((decl === null || decl === void 0 ? void 0 : decl.default) !== undefined)
635
+ return decl.default;
636
+ throw new Error(unsatisfiedGivenMessage(refName));
637
+ }
638
+ function generateGivenFragment(resultSet, context, expr, state) {
639
+ // The bound expr may itself be a `$OTHER`-bearing expression; recursive
640
+ // compile handles default chains.
641
+ const bound = resolveGivenBoundExpr(context, expr.id, expr.refName);
642
+ return exprToSQL(resultSet, context, bound, state);
616
643
  }
617
644
  function unsatisfiedGivenMessage(refName) {
618
645
  return (`Given '${refName}' has no value and no default. ` +
@@ -1,2 +1,17 @@
1
1
  import type { Expr, GivenID, GivenValue, ModelDef } from './malloy_types';
2
2
  export declare function resolveSuppliedGivens(supplied: Record<string, GivenValue> | undefined, modelDef: ModelDef | undefined): Map<GivenID, Expr>;
3
+ /**
4
+ * Evaluate every `inline` given's default to a literal Expr and add it
5
+ * to the bound map. Mutates and returns `bound` for convenience.
6
+ *
7
+ * Givens already in `bound` (caller supplied a value) are left alone —
8
+ * the caller's value wins over the inline default. Inline givens with
9
+ * no default are skipped here; the translator already logged the
10
+ * `inline-no-default` error at declaration time.
11
+ *
12
+ * Iteration follows `modelDef.contents` insertion order, which (by
13
+ * Malloy's no-forward-refs rule) is also topological order: if inline
14
+ * A's default references inline B, B's declaration came first and is
15
+ * already in the map by the time A runs.
16
+ */
17
+ export declare function evaluateInlineGivens(bound: Map<GivenID, Expr>, modelDef: ModelDef | undefined): Map<GivenID, Expr>;
@@ -5,8 +5,10 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.resolveSuppliedGivens = resolveSuppliedGivens;
8
+ exports.evaluateInlineGivens = evaluateInlineGivens;
8
9
  const luxon_1 = require("luxon");
9
10
  const closest_match_1 = require("../util/closest_match");
11
+ const inline_expr_1 = require("./inline_expr");
10
12
  const malloy_types_1 = require("./malloy_types");
11
13
  function resolveSuppliedGivens(supplied, modelDef) {
12
14
  var _a;
@@ -43,6 +45,39 @@ function resolveSuppliedGivens(supplied, modelDef) {
43
45
  }
44
46
  return out;
45
47
  }
48
+ /**
49
+ * Evaluate every `inline` given's default to a literal Expr and add it
50
+ * to the bound map. Mutates and returns `bound` for convenience.
51
+ *
52
+ * Givens already in `bound` (caller supplied a value) are left alone —
53
+ * the caller's value wins over the inline default. Inline givens with
54
+ * no default are skipped here; the translator already logged the
55
+ * `inline-no-default` error at declaration time.
56
+ *
57
+ * Iteration follows `modelDef.contents` insertion order, which (by
58
+ * Malloy's no-forward-refs rule) is also topological order: if inline
59
+ * A's default references inline B, B's declaration came first and is
60
+ * already in the map by the time A runs.
61
+ */
62
+ function evaluateInlineGivens(bound, modelDef) {
63
+ var _a;
64
+ if (!modelDef)
65
+ return bound;
66
+ const givens = (_a = modelDef.givens) !== null && _a !== void 0 ? _a : {};
67
+ for (const [, entry] of Object.entries(modelDef.contents)) {
68
+ if (entry.type !== 'given')
69
+ continue;
70
+ if (bound.has(entry.id))
71
+ continue;
72
+ const decl = givens[entry.id];
73
+ if (!(decl === null || decl === void 0 ? void 0 : decl.inline))
74
+ continue;
75
+ if (decl.default === undefined)
76
+ continue;
77
+ bound.set(entry.id, (0, inline_expr_1.inlineExpr)(decl.default, bound));
78
+ }
79
+ return bound;
80
+ }
46
81
  function valueToExpr(path, type, value) {
47
82
  var _a, _b;
48
83
  if (value === null) {
@@ -0,0 +1,30 @@
1
+ import type { Expr, GivenID } from './malloy_types';
2
+ /**
3
+ * Operator node types allowed inside an `inline` given's default. Single
4
+ * source of truth — both the translator-side validator
5
+ * (`GivenDeclaration.execute`) and the bind-time evaluator below read
6
+ * this set when deciding whether an expression is reducible to a
7
+ * literal at bind time.
8
+ *
9
+ * Add a new operator here AND a corresponding `case` to `inlineExpr`'s
10
+ * switch in the same commit; the two are intentionally co-located so
11
+ * they can't drift apart.
12
+ */
13
+ export declare const INLINE_OPS: ReadonlySet<string>;
14
+ /**
15
+ * Leaf-shaped node types that are valid inside an inline expression but
16
+ * aren't "operators." Literals are returned as-is by the evaluator;
17
+ * `given` nodes resolve through the bound values map.
18
+ */
19
+ export declare const INLINE_LEAVES: ReadonlySet<string>;
20
+ /**
21
+ * Bind-time evaluator for `inline` given defaults. Walks the Expr tree,
22
+ * recursing on bound values for given-refs, and returns a literal Expr
23
+ * (string/number/boolean/null/arrayLiteral).
24
+ *
25
+ * Throws on any node outside `INLINE_OPS ∪ INLINE_LEAVES`. The
26
+ * translator's pre-flight check should have rejected such defaults
27
+ * already, so a throw here flags a compiler bug rather than a caller
28
+ * error.
29
+ */
30
+ export declare function inlineExpr(e: Expr, bound: Map<GivenID, Expr>): Expr;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.INLINE_LEAVES = exports.INLINE_OPS = void 0;
8
+ exports.inlineExpr = inlineExpr;
9
+ /**
10
+ * Operator node types allowed inside an `inline` given's default. Single
11
+ * source of truth — both the translator-side validator
12
+ * (`GivenDeclaration.execute`) and the bind-time evaluator below read
13
+ * this set when deciding whether an expression is reducible to a
14
+ * literal at bind time.
15
+ *
16
+ * Add a new operator here AND a corresponding `case` to `inlineExpr`'s
17
+ * switch in the same commit; the two are intentionally co-located so
18
+ * they can't drift apart.
19
+ */
20
+ exports.INLINE_OPS = new Set([
21
+ 'and',
22
+ 'or',
23
+ 'not',
24
+ '=',
25
+ '!=',
26
+ '>',
27
+ '<',
28
+ '>=',
29
+ '<=',
30
+ 'inGiven',
31
+ '()',
32
+ ]);
33
+ /**
34
+ * Leaf-shaped node types that are valid inside an inline expression but
35
+ * aren't "operators." Literals are returned as-is by the evaluator;
36
+ * `given` nodes resolve through the bound values map.
37
+ */
38
+ exports.INLINE_LEAVES = new Set([
39
+ 'stringLiteral',
40
+ 'numberLiteral',
41
+ 'true',
42
+ 'false',
43
+ 'null',
44
+ 'arrayLiteral',
45
+ 'given',
46
+ ]);
47
+ /**
48
+ * Bind-time evaluator for `inline` given defaults. Walks the Expr tree,
49
+ * recursing on bound values for given-refs, and returns a literal Expr
50
+ * (string/number/boolean/null/arrayLiteral).
51
+ *
52
+ * Throws on any node outside `INLINE_OPS ∪ INLINE_LEAVES`. The
53
+ * translator's pre-flight check should have rejected such defaults
54
+ * already, so a throw here flags a compiler bug rather than a caller
55
+ * error.
56
+ */
57
+ function inlineExpr(e, bound) {
58
+ switch (e.node) {
59
+ case 'stringLiteral':
60
+ case 'numberLiteral':
61
+ case 'true':
62
+ case 'false':
63
+ case 'null':
64
+ case 'arrayLiteral':
65
+ return e;
66
+ case 'given': {
67
+ const v = bound.get(e.id);
68
+ if (v === undefined) {
69
+ throw new Error(`inlineExpr: given '${e.refName}' has no bound value and no default — translator should have caught this earlier`);
70
+ }
71
+ return inlineExpr(v, bound);
72
+ }
73
+ case '()':
74
+ return inlineExpr(e.e, bound);
75
+ case 'not': {
76
+ const inner = inlineExpr(e.e, bound);
77
+ return toBoolLiteral(!exprAsBool(inner));
78
+ }
79
+ case 'and':
80
+ case 'or': {
81
+ const left = exprAsBool(inlineExpr(e.kids.left, bound));
82
+ const right = exprAsBool(inlineExpr(e.kids.right, bound));
83
+ return toBoolLiteral(e.node === 'and' ? left && right : left || right);
84
+ }
85
+ case '=':
86
+ case '!=':
87
+ case '>':
88
+ case '<':
89
+ case '>=':
90
+ case '<=': {
91
+ const left = inlineExpr(e.kids.left, bound);
92
+ const right = inlineExpr(e.kids.right, bound);
93
+ return toBoolLiteral(compareLiterals(e.node, left, right));
94
+ }
95
+ case 'inGiven': {
96
+ const lhs = inlineExpr(e.e, bound);
97
+ const arrBound = bound.get(e.givenRef.id);
98
+ if (arrBound === undefined) {
99
+ throw new Error(`inlineExpr: given '${e.givenRef.refName}' has no bound value and no default — translator should have caught this earlier`);
100
+ }
101
+ if (arrBound.node === 'null') {
102
+ return toBoolLiteral(e.not);
103
+ }
104
+ if (arrBound.node !== 'arrayLiteral') {
105
+ throw new Error(`inlineExpr: 'inGiven' bound to '${arrBound.node}', expected 'arrayLiteral'`);
106
+ }
107
+ const found = arrBound.kids.values.some(v => compareLiterals('=', lhs, inlineExpr(v, bound)));
108
+ return toBoolLiteral(e.not ? !found : found);
109
+ }
110
+ default:
111
+ throw new Error(`inlineExpr: unexpected node '${e.node}' — translator should have rejected this`);
112
+ }
113
+ }
114
+ function toBoolLiteral(b) {
115
+ return { node: b ? 'true' : 'false' };
116
+ }
117
+ /**
118
+ * Read a literal Expr as a JS boolean. The evaluator only ever produces
119
+ * 'true' / 'false' / 'null' / scalar literals here; anything else means
120
+ * we tried to use a non-boolean where a boolean was expected, which is a
121
+ * translator type-check bug.
122
+ */
123
+ function exprAsBool(e) {
124
+ if (e.node === 'true')
125
+ return true;
126
+ if (e.node === 'false')
127
+ return false;
128
+ // `null` in boolean position is treated as false — matches the
129
+ // implicit SQL coalesce that Malloy does for filter expressions.
130
+ if (e.node === 'null')
131
+ return false;
132
+ throw new Error(`inlineExpr: expected boolean literal, got '${e.node}' — translator should have type-checked this`);
133
+ }
134
+ /**
135
+ * JS-side comparison of two literal Exprs. Only the literal nodes
136
+ * produced by the evaluator are supported.
137
+ *
138
+ * SQL nullability: any operand being null produces false (matching
139
+ * Malloy's COALESCE-to-true / COALESCE-to-false behavior elsewhere).
140
+ */
141
+ function compareLiterals(op, left, right) {
142
+ if (left.node === 'null' || right.node === 'null') {
143
+ // Mirror SQL: comparisons with NULL are unknown; treat as false for
144
+ // = / > / < / >= / <=, true for !=.
145
+ return op === '!=';
146
+ }
147
+ const l = literalToJS(left);
148
+ const r = literalToJS(right);
149
+ switch (op) {
150
+ case '=':
151
+ return l === r;
152
+ case '!=':
153
+ return l !== r;
154
+ case '>':
155
+ return l > r;
156
+ case '<':
157
+ return l < r;
158
+ case '>=':
159
+ return l >= r;
160
+ case '<=':
161
+ return l <= r;
162
+ }
163
+ }
164
+ /**
165
+ * Convert a literal Expr to a comparable JS value. Numbers come through
166
+ * `numberLiteral` as a string (full precision preserved); we parse to a
167
+ * JS number for ordering — bind-time precision loss here is fine
168
+ * because the result of an inline expression is a boolean for SQL.
169
+ */
170
+ function literalToJS(e) {
171
+ switch (e.node) {
172
+ case 'stringLiteral':
173
+ return e.literal;
174
+ case 'numberLiteral':
175
+ return Number(e.literal);
176
+ case 'true':
177
+ return true;
178
+ case 'false':
179
+ return false;
180
+ default:
181
+ throw new Error(`inlineExpr: cannot compare non-literal node '${e.node}'`);
182
+ }
183
+ }
184
+ //# sourceMappingURL=inline_expr.js.map
@@ -36,7 +36,7 @@ export type AnyExpr = ExprE | ExprOptionalE | ExprWithKids | ExprLeaf;
36
36
  export declare function exprHasKids(e: AnyExpr): e is ExprWithKids;
37
37
  export declare function exprHasE(e: AnyExpr): e is ExprE;
38
38
  export declare function exprIsLeaf(e: AnyExpr): boolean;
39
- export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | GivenRefNode | NowNode | MeasureTimeExpr | TimeExtractExpr | TimeDeltaExpr | TimeTruncExpr | DateLiteralNode | TimestampLiteralNode | TimestamptzLiteralNode | TypecastExpr | RegexMatchExpr | RegexLiteralNode | FilterMatchExpr | FilterLiteralExpr | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | CaseExpr | InCompareExpr | CompositeFieldExpr | ErrorNode;
39
+ export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | GivenRefNode | NowNode | MeasureTimeExpr | TimeExtractExpr | TimeDeltaExpr | TimeTruncExpr | DateLiteralNode | TimestampLiteralNode | TimestamptzLiteralNode | TypecastExpr | RegexMatchExpr | RegexLiteralNode | FilterMatchExpr | FilterLiteralExpr | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | CaseExpr | InCompareExpr | InGivenExpr | CompositeFieldExpr | ErrorNode;
40
40
  export type BinaryOperator = '+' | '-' | '*' | '%' | '/' | 'and' | 'or' | '=' | '!=' | '>' | '<' | '>=' | '<=' | 'coalesce' | 'like' | '!like';
41
41
  export interface BinaryExpr extends ExprWithKids {
42
42
  node: BinaryOperator;
@@ -289,6 +289,19 @@ export interface InCompareExpr extends ExprWithKids {
289
289
  oneOf: Expr[];
290
290
  };
291
291
  }
292
+ /**
293
+ * Test against a runtime-bound array given: `expr in $ARRAY_GIVEN`.
294
+ *
295
+ * Uses ExprE (one child `e`, the LHS). The given reference is embedded
296
+ * as a top-level field rather than a kid so the auto-visitor in
297
+ * `compileExpr` doesn't descend into it; resolution and per-element
298
+ * SQL emission happen in the `case 'inGiven':` handler.
299
+ */
300
+ export interface InGivenExpr extends ExprE {
301
+ node: 'inGiven';
302
+ not: boolean;
303
+ givenRef: GivenRefNode;
304
+ }
292
305
  export type ExpressionType = 'scalar' | 'aggregate' | 'scalar_analytic' | 'aggregate_analytic' | 'ungrouped_aggregate';
293
306
  export interface Expression {
294
307
  e?: Expr;
@@ -341,6 +354,11 @@ export interface Given extends HasLocation, HasAnnotation {
341
354
  * its own default. Empty/undefined when the default is a closed
342
355
  * literal. */
343
356
  givenUsage?: GivenUsage;
357
+ /** Marked with the `inline` modifier — the default is eager-evaluated
358
+ * to a literal at bind time and substituted as that literal in SQL.
359
+ * Translator validates the default is eager-evaluable; bind-time
360
+ * evaluator (`model/inline_expr.ts`) performs the reduction. */
361
+ inline?: boolean;
344
362
  }
345
363
  export interface GivenEntry {
346
364
  type: 'given';
@@ -103,7 +103,7 @@ export declare class QueryQuery extends QueryField {
103
103
  generateTurtlePipelineSQL(fi: FieldInstanceResult, stageWriter: StageWriter, sourceSQLExpression: string): {
104
104
  structDef: QueryResultDef;
105
105
  pipeOut: any;
106
- repeatedResultType: "nested" | "inline_all_numbers" | "inline";
106
+ repeatedResultType: "inline" | "nested" | "inline_all_numbers";
107
107
  };
108
108
  generateComplexSQL(stageWriter: StageWriter): string;
109
109
  generateSQL(stageWriter: StageWriter): string;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.392";
1
+ export declare const MALLOY_VERSION = "0.0.394";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MALLOY_VERSION = void 0;
4
4
  // generated with 'generate-version-file' script; do not edit manually
5
- exports.MALLOY_VERSION = '0.0.392';
5
+ exports.MALLOY_VERSION = '0.0.394';
6
6
  //# sourceMappingURL=version.js.map