Skip to content

Commit

Permalink
allow implies keys in arrys of records (#2048)
Browse files Browse the repository at this point in the history
  • Loading branch information
mtoy-googly-moogly authored Dec 13, 2024
1 parent f989eda commit cdd3586
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {ExprValue, computedExprValue} from '../types/expr-value';
import {ExpressionDef} from '../types/expression-def';
import {FieldSpace} from '../types/field-space';
import * as TDU from '../typedesc-utils';
import {RecordLiteral} from './expr-record-literal';

export class ArrayLiteral extends ExpressionDef {
elementType = 'array literal';
Expand All @@ -24,7 +25,10 @@ export class ArrayLiteral extends ExpressionDef {
let firstValue: ExprValue | undefined = undefined;
if (this.elements.length > 0) {
for (const nextElement of this.elements) {
const v = nextElement.getExpression(fs);
const v =
firstValue && nextElement instanceof RecordLiteral
? nextElement.getNextElement(fs, firstValue)
: nextElement.getExpression(fs);
fromValues.push(v);
if (v.type === 'error') {
continue;
Expand Down
52 changes: 44 additions & 8 deletions packages/malloy/src/lang/ast/expressions/expr-record-literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,29 @@ import {ExpressionDef} from '../types/expression-def';
import {FieldSpace} from '../types/field-space';
import {MalloyElement} from '../types/malloy-element';
import * as TDU from '../typedesc-utils';
import {ExprIdReference} from './expr-id-reference';

export type ElementDetails =
| {path: ExprIdReference}
| {key?: string; value: ExpressionDef};
export class RecordElement extends MalloyElement {
elementType = 'record element';
constructor(
readonly key: string,
readonly value: ExpressionDef
) {
value: ExpressionDef;
key?: string;
constructor(val: ElementDetails) {
super();
this.has({value});
if ('value' in val) {
this.value = val.value;
this.has({value: val.value});
if (val.key) {
this.key = val.key;
}
} else {
this.has({path: val.path});
this.value = val.path;
const parts = val.path.fieldReference.path;
this.key = parts[parts.length - 1];
}
}
}

Expand All @@ -31,6 +45,10 @@ export class RecordLiteral extends ExpressionDef {
}

getExpression(fs: FieldSpace): ExprValue {
return this.getRecord(fs, []);
}

getRecord(fs: FieldSpace, kidNames: string[]): ExprValue {
const recLit: RecordLiteralNode = {
node: 'recordLiteral',
kids: {},
Expand All @@ -40,14 +58,24 @@ export class RecordLiteral extends ExpressionDef {
},
};
const dependents: ExprValue[] = [];
let kidIndex = 0;
for (const el of this.pairs) {
const key = el.key ?? kidNames[kidIndex];
kidIndex += 1;
if (key === undefined) {
el.logError(
'record-literal-needs-keys',
'Anonymous record element not legal here'
);
continue;
}
const xVal = el.value.getExpression(fs);
if (TD.isAtomic(xVal)) {
dependents.push(xVal);
recLit.kids[el.key] = xVal.value;
recLit.typeDef.fields.push(mkFieldDef(TDU.atomicDef(xVal), el.key));
recLit.kids[key] = xVal.value;
recLit.typeDef.fields.push(mkFieldDef(TDU.atomicDef(xVal), key));
} else {
this.logError(
el.value.logError(
'illegal-record-property-type',
`Record property '${el.key} is type '${xVal.type}', which is not a legal property value type`
);
Expand All @@ -59,4 +87,12 @@ export class RecordLiteral extends ExpressionDef {
from: dependents,
});
}

getNextElement(fs: FieldSpace, headValue: ExprValue): ExprValue {
const recLit = headValue.value;
if (recLit.node === 'recordLiteral') {
return this.getRecord(fs, Object.keys(recLit.kids));
}
return this.getRecord(fs, []);
}
}
4 changes: 2 additions & 2 deletions packages/malloy/src/lang/grammar/MalloyParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -664,8 +664,8 @@ caseWhen

recordKey: id;
recordElement
: fieldPath # recordRef
| recordKey IS fieldExpr # recordExpr
: fieldPath # recordRef
| (recordKey IS)? fieldExpr # recordExpr
;

argumentList
Expand Down
24 changes: 8 additions & 16 deletions packages/malloy/src/lang/malloy-to-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2083,32 +2083,24 @@ export class MalloyToAST
}

visitRecordRef(pcx: parse.RecordRefContext) {
const pathCx = pcx.fieldPath();
const tailEl = pathCx.fieldName().at(-1);
if (tailEl) {
const elementKey = getId(tailEl);
const idRef = new ast.ExprIdReference(
this.getFieldPath(pathCx, ast.ExpressionFieldReference)
);
return new ast.RecordElement(elementKey, idRef);
}
throw this.internalError(
pathCx,
'IMPOSSIBLY A PATH CONTAINED ZERO ELEMENTS'
const idRef = new ast.ExprIdReference(
this.getFieldPath(pcx.fieldPath(), ast.ExpressionFieldReference)
);
return this.astAt(new ast.RecordElement({path: idRef}), pcx);
}

visitRecordExpr(pcx: parse.RecordExprContext) {
const elementKey = getId(pcx.recordKey());
const elementVal = this.getFieldExpr(pcx.fieldExpr());
return new ast.RecordElement(elementKey, elementVal);
const value = this.getFieldExpr(pcx.fieldExpr());
const keyCx = pcx.recordKey();
const recInit = keyCx ? {key: getId(keyCx), value} : {value};
return this.astAt(new ast.RecordElement(recInit), pcx);
}

visitExprLiteralRecord(pcx: parse.ExprLiteralRecordContext) {
const els = this.only<ast.RecordElement>(
pcx.recordElement().map(elCx => this.astAt(this.visit(elCx), elCx)),
visited => visited instanceof ast.RecordElement && visited,
'a key value pair'
'a legal record property description'
);
return new ast.RecordLiteral(els);
}
Expand Down
1 change: 1 addition & 0 deletions packages/malloy/src/lang/parse-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ type MessageParameterTypes = {
'sql-is-not-null': string;
'sql-is-null': string;
'illegal-record-property-type': string;
'record-literal-needs-keys': string;
'not-yet-implemented': string;
'sql-case': string;
'case-then-type-does-not-match': {
Expand Down
18 changes: 18 additions & 0 deletions packages/malloy/src/lang/test/literals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,22 @@ describe('literals', () => {
});
test('a string containing a tab', () => expect(expr`'\t'`).toParse());
});
describe('compound literals', () => {
test('simple record literal', () => {
expect('{answer is 42}').compilesTo('{answer:42}');
});
test('record literal with path', () => {
expect('{aninline.column}').compilesTo('{column:aninline.column}');
});
test('array of records with same schema', () => {
expect(
'[{name is "one", val is 1},{name is "two", val is 2}]'
).compilesTo('[{name:"one", val:1}, {name:"two", val:2}]');
});
test('array of records with head schema', () => {
expect('[{name is "one", val is 1},{"two", 2}]').compilesTo(
'[{name:"one", val:1}, {name:"two", val:2}]'
);
});
});
});
11 changes: 11 additions & 0 deletions packages/malloy/src/lang/test/parse-expects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,17 @@ function eToStr(e: Expr, symbols: ESymbols): string {
return `"${e.literal}"`;
case 'timeLiteral':
return `@${e.literal}`;
case 'recordLiteral': {
const parts: string[] = [];
for (const [name, val] of Object.entries(e.kids)) {
parts.push(`${name}:${subExpr(val)}`);
}
return `{${parts.join(', ')}}`;
}
case 'arrayLiteral': {
const parts = e.kids.values.map(k => subExpr(k));
return `[${parts.join(', ')}]`;
}
case 'regexpLiteral':
return `/${e.literal}/`;
case 'trunc':
Expand Down

0 comments on commit cdd3586

Please sign in to comment.