@aeriajs/compiler 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ast.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Property, AccessCondition, CollectionActions, SearchOptions, DescriptionPreset, Icon, OwnershipMode, Layout, LayoutOptions } from '@aeriajs/types';
1
+ import type { Property, AccessCondition, CollectionActions, SearchOptions, DescriptionPreset, Icon, OwnershipMode, Layout, LayoutOptions, FormLayout, Description, FormLayoutField } from '@aeriajs/types';
2
2
  import type { ArrayProperties } from './utils.js';
3
3
  export declare const LOCATION_SYMBOL: unique symbol;
4
4
  export declare const PropertyType: {
@@ -26,6 +26,16 @@ export type LayoutNode = NodeBase<'layout'> & Layout & {
26
26
  };
27
27
  };
28
28
  };
29
+ export type FormLayoutNode = NodeBase<'formLayout'> & FormLayout<Description> & {
30
+ [LOCATION_SYMBOL]: {
31
+ fields: {
32
+ [P in string]: {
33
+ name: symbol;
34
+ field: FormLayoutField<Description>;
35
+ };
36
+ };
37
+ };
38
+ };
29
39
  export type PropertyNode = NodeBase<'property'> & {
30
40
  modifier?: keyof typeof PropertyModifiers;
31
41
  property: Property & {
@@ -61,6 +71,7 @@ export type CollectionNode = NodeBase<'collection'> & {
61
71
  filters?: string[];
62
72
  search?: SearchOptions<any>;
63
73
  layout?: LayoutNode;
74
+ formLayout?: FormLayoutNode;
64
75
  [LOCATION_SYMBOL]: {
65
76
  arrays: {
66
77
  [P in ArrayProperties<CollectionNode>]?: symbol[];
@@ -87,4 +98,3 @@ export type ProgramNode = NodeBase<'program'> & {
87
98
  functionsets: FunctionSetNode[];
88
99
  };
89
100
  export type Node = CollectionNode | ContractNode | FunctionSetNode;
90
- export type NoteKind = Node['kind'];
@@ -80,6 +80,9 @@ const makeJSCollectionSchema = (collectionNode, collectionId) => {
80
80
  case 'layout':
81
81
  collectionSchema.description[key] = collectionNode[key];
82
82
  break;
83
+ case 'formLayout':
84
+ collectionSchema.description[key] = collectionNode[key];
85
+ break;
83
86
  case 'required':
84
87
  collectionSchema.description[key] = collectionNode[key];
85
88
  break;
@@ -73,6 +73,9 @@ const makeJSCollectionSchema = (collectionNode, collectionId) => {
73
73
  case "layout":
74
74
  collectionSchema.description[key] = collectionNode[key];
75
75
  break;
76
+ case "formLayout":
77
+ collectionSchema.description[key] = collectionNode[key];
78
+ break;
76
79
  case "required":
77
80
  collectionSchema.description[key] = collectionNode[key];
78
81
  break;
@@ -87,6 +87,12 @@ const makeTSCollectionSchema = (collectionNode, collectionId) => {
87
87
  case 'search':
88
88
  collectionSchema.description[key] = collectionNode[key];
89
89
  break;
90
+ case 'layout':
91
+ collectionSchema.description[key] = collectionNode[key];
92
+ break;
93
+ case 'formLayout':
94
+ collectionSchema.description[key] = collectionNode[key];
95
+ break;
90
96
  case 'required':
91
97
  collectionSchema.description[key] = collectionNode[key];
92
98
  break;
@@ -82,6 +82,12 @@ const makeTSCollectionSchema = (collectionNode, collectionId) => {
82
82
  case "search":
83
83
  collectionSchema.description[key] = collectionNode[key];
84
84
  break;
85
+ case "layout":
86
+ collectionSchema.description[key] = collectionNode[key];
87
+ break;
88
+ case "formLayout":
89
+ collectionSchema.description[key] = collectionNode[key];
90
+ break;
85
91
  case "required":
86
92
  collectionSchema.description[key] = collectionNode[key];
87
93
  break;
@@ -17,6 +17,9 @@ class Diagnostic extends Error {
17
17
  this.message = message;
18
18
  this.location = location;
19
19
  this.fileLocation = fileLocation;
20
+ if (process.env.NODE_ENV === 'debug') {
21
+ console.error(message, location);
22
+ }
20
23
  }
21
24
  }
22
25
  exports.Diagnostic = Diagnostic;
@@ -11,6 +11,9 @@ export class Diagnostic extends Error {
11
11
  this.message = message;
12
12
  this.location = location;
13
13
  this.fileLocation = fileLocation;
14
+ if (false) {
15
+ console.error(message, location);
16
+ }
14
17
  }
15
18
  static currentFile;
16
19
  }
package/dist/lexer.d.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import { type Token } from './token.js';
2
2
  import { Diagnostic } from './diagnostic.js';
3
- export declare const COLLECTION_KEYWORDS: readonly ["actions", "additionalProperties", "filters", "form", "functions", "icon", "indexes", "individualActions", "layout", "owned", "presets", "properties", "required", "search", "table"];
3
+ export declare const COLLECTION_KEYWORDS: readonly ["actions", "additionalProperties", "filters", "form", "formLayout", "functions", "icon", "indexes", "individualActions", "layout", "owned", "presets", "properties", "required", "search", "table"];
4
4
  export declare const COLLECTION_ACTIONS_KEYWORDS: readonly ["ask", "button", "clearItem", "effect", "event", "fetchItem", "function", "icon", "label", "params", "query", "requires", "roles", "route", "selection", "setItem", "translate"];
5
5
  export declare const COLLECTION_SEARCH_KEYWORDS: readonly ["indexes", "placeholder", "exactMatches"];
6
6
  export declare const COLLECTION_LAYOUT_KEYWORDS: readonly ["name", "options"];
7
7
  export declare const COLLECTION_LAYOUT_OPTIONS_KEYWORDS: readonly ["title", "picture", "badge", "information", "active", "translateBadge"];
8
+ export declare const COLLECTION_FORM_LAYOUT_KEYWORDS: readonly ["fields", "if", "span", "verticalSpacing", "separator"];
8
9
  export declare const CONTRACT_KEYWORDS: readonly ["roles", "payload", "query", "response"];
9
10
  export declare const TOPLEVEL_KEYWORDS: readonly ["collection", "contract", "functionset"];
10
11
  export declare const MISC_KEYWORDS: readonly ["extends"];
11
- export type Keyword = typeof COLLECTION_KEYWORDS[number] | typeof COLLECTION_ACTIONS_KEYWORDS[number] | typeof COLLECTION_SEARCH_KEYWORDS[number] | typeof COLLECTION_LAYOUT_KEYWORDS[number] | typeof COLLECTION_LAYOUT_OPTIONS_KEYWORDS[number] | typeof CONTRACT_KEYWORDS[number] | typeof TOPLEVEL_KEYWORDS[number] | typeof MISC_KEYWORDS[number];
12
+ export type Keyword = typeof COLLECTION_KEYWORDS[number] | typeof COLLECTION_ACTIONS_KEYWORDS[number] | typeof COLLECTION_SEARCH_KEYWORDS[number] | typeof COLLECTION_LAYOUT_KEYWORDS[number] | typeof COLLECTION_LAYOUT_OPTIONS_KEYWORDS[number] | typeof COLLECTION_FORM_LAYOUT_KEYWORDS[number] | typeof CONTRACT_KEYWORDS[number] | typeof TOPLEVEL_KEYWORDS[number] | typeof MISC_KEYWORDS[number];
12
13
  export declare const KEYWORDS: Keyword[];
14
+ export declare const OPERATORS: readonly ["&&", "||", "==", "in", ">=", "<=", ">", "<"];
13
15
  export declare const tokenize: (rawInput: string) => {
14
16
  tokens: Token[];
15
17
  errors: Diagnostic[];
package/dist/lexer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.tokenize = exports.KEYWORDS = exports.MISC_KEYWORDS = exports.TOPLEVEL_KEYWORDS = exports.CONTRACT_KEYWORDS = exports.COLLECTION_LAYOUT_OPTIONS_KEYWORDS = exports.COLLECTION_LAYOUT_KEYWORDS = exports.COLLECTION_SEARCH_KEYWORDS = exports.COLLECTION_ACTIONS_KEYWORDS = exports.COLLECTION_KEYWORDS = void 0;
3
+ exports.tokenize = exports.OPERATORS = exports.KEYWORDS = exports.MISC_KEYWORDS = exports.TOPLEVEL_KEYWORDS = exports.CONTRACT_KEYWORDS = exports.COLLECTION_FORM_LAYOUT_KEYWORDS = exports.COLLECTION_LAYOUT_OPTIONS_KEYWORDS = exports.COLLECTION_LAYOUT_KEYWORDS = exports.COLLECTION_SEARCH_KEYWORDS = exports.COLLECTION_ACTIONS_KEYWORDS = exports.COLLECTION_KEYWORDS = void 0;
4
4
  const token_js_1 = require("./token.js");
5
5
  const diagnostic_js_1 = require("./diagnostic.js");
6
6
  exports.COLLECTION_KEYWORDS = [
@@ -8,6 +8,7 @@ exports.COLLECTION_KEYWORDS = [
8
8
  'additionalProperties',
9
9
  'filters',
10
10
  'form',
11
+ 'formLayout',
11
12
  'functions',
12
13
  'icon',
13
14
  'indexes',
@@ -56,6 +57,13 @@ exports.COLLECTION_LAYOUT_OPTIONS_KEYWORDS = [
56
57
  'active',
57
58
  'translateBadge',
58
59
  ];
60
+ exports.COLLECTION_FORM_LAYOUT_KEYWORDS = [
61
+ 'fields',
62
+ 'if',
63
+ 'span',
64
+ 'verticalSpacing',
65
+ 'separator',
66
+ ];
59
67
  exports.CONTRACT_KEYWORDS = [
60
68
  'roles',
61
69
  'payload',
@@ -68,7 +76,17 @@ exports.TOPLEVEL_KEYWORDS = [
68
76
  'functionset',
69
77
  ];
70
78
  exports.MISC_KEYWORDS = ['extends'];
71
- exports.KEYWORDS = [].concat(exports.COLLECTION_KEYWORDS, exports.COLLECTION_ACTIONS_KEYWORDS, exports.COLLECTION_SEARCH_KEYWORDS, exports.COLLECTION_LAYOUT_KEYWORDS, exports.COLLECTION_LAYOUT_OPTIONS_KEYWORDS, exports.CONTRACT_KEYWORDS, exports.TOPLEVEL_KEYWORDS, exports.MISC_KEYWORDS);
79
+ exports.KEYWORDS = [].concat(exports.COLLECTION_KEYWORDS, exports.COLLECTION_ACTIONS_KEYWORDS, exports.COLLECTION_SEARCH_KEYWORDS, exports.COLLECTION_LAYOUT_KEYWORDS, exports.COLLECTION_LAYOUT_OPTIONS_KEYWORDS, exports.COLLECTION_FORM_LAYOUT_KEYWORDS, exports.CONTRACT_KEYWORDS, exports.TOPLEVEL_KEYWORDS, exports.MISC_KEYWORDS);
80
+ exports.OPERATORS = [
81
+ '&&',
82
+ '||',
83
+ '==',
84
+ 'in',
85
+ '>=',
86
+ '<=',
87
+ '>',
88
+ '<',
89
+ ];
72
90
  const keywordsSet = new Set();
73
91
  for (const keyword of exports.KEYWORDS) {
74
92
  keywordsSet.add(keyword);
@@ -110,6 +128,10 @@ const TOKENS = [
110
128
  type: token_js_1.TokenType.RightSquareBracket,
111
129
  matcher: ']',
112
130
  },
131
+ {
132
+ type: token_js_1.TokenType.Operator,
133
+ matcher: exports.OPERATORS,
134
+ },
113
135
  {
114
136
  type: token_js_1.TokenType.Pipe,
115
137
  matcher: '|',
@@ -150,7 +172,7 @@ const TOKENS = [
150
172
  type: token_js_1.TokenType.Keyword,
151
173
  matcher: Array.from(keywordsSet),
152
174
  condition: (state, lastToken) => {
153
- if (state.inPropertiesStack.at(-1)) {
175
+ if (state.variableScopeStack.at(-1)) {
154
176
  return false;
155
177
  }
156
178
  if (lastToken && lastToken.type === token_js_1.TokenType.Keyword) {
@@ -190,7 +212,7 @@ const tokenize = function (rawInput) {
190
212
  const tokens = [];
191
213
  const errors = [];
192
214
  const state = {
193
- inPropertiesStack: [],
215
+ variableScopeStack: [],
194
216
  };
195
217
  while (index < input.length) {
196
218
  let hasMatch = false;
@@ -260,8 +282,10 @@ const tokenize = function (rawInput) {
260
282
  };
261
283
  switch (type) {
262
284
  case token_js_1.TokenType.LeftBracket: {
285
+ let variableScope = false;
263
286
  if (lastToken && lastToken.type === token_js_1.TokenType.Keyword) {
264
287
  switch (lastToken.value) {
288
+ case 'fields':
265
289
  case 'information':
266
290
  case 'form':
267
291
  case 'table':
@@ -270,19 +294,17 @@ const tokenize = function (rawInput) {
270
294
  case 'writable':
271
295
  case 'required':
272
296
  case 'properties': {
273
- state.inPropertiesStack.push(true);
297
+ variableScope = true;
274
298
  break;
275
299
  }
276
- default: {
277
- state.inPropertiesStack.push(false);
278
- }
279
300
  }
280
301
  }
302
+ state.variableScopeStack.push(variableScope);
281
303
  break;
282
304
  }
283
305
  case token_js_1.TokenType.RightBracket: {
284
- if (state.inPropertiesStack.length > 0) {
285
- state.inPropertiesStack.pop();
306
+ if (state.variableScopeStack.length > 0) {
307
+ state.variableScopeStack.pop();
286
308
  }
287
309
  break;
288
310
  }
package/dist/lexer.mjs CHANGED
@@ -6,6 +6,7 @@ export const COLLECTION_KEYWORDS = [
6
6
  "additionalProperties",
7
7
  "filters",
8
8
  "form",
9
+ "formLayout",
9
10
  "functions",
10
11
  "icon",
11
12
  "indexes",
@@ -54,6 +55,13 @@ export const COLLECTION_LAYOUT_OPTIONS_KEYWORDS = [
54
55
  "active",
55
56
  "translateBadge"
56
57
  ];
58
+ export const COLLECTION_FORM_LAYOUT_KEYWORDS = [
59
+ "fields",
60
+ "if",
61
+ "span",
62
+ "verticalSpacing",
63
+ "separator"
64
+ ];
57
65
  export const CONTRACT_KEYWORDS = [
58
66
  "roles",
59
67
  "payload",
@@ -72,10 +80,21 @@ export const KEYWORDS = [].concat(
72
80
  COLLECTION_SEARCH_KEYWORDS,
73
81
  COLLECTION_LAYOUT_KEYWORDS,
74
82
  COLLECTION_LAYOUT_OPTIONS_KEYWORDS,
83
+ COLLECTION_FORM_LAYOUT_KEYWORDS,
75
84
  CONTRACT_KEYWORDS,
76
85
  TOPLEVEL_KEYWORDS,
77
86
  MISC_KEYWORDS
78
87
  );
88
+ export const OPERATORS = [
89
+ "&&",
90
+ "||",
91
+ "==",
92
+ "in",
93
+ ">=",
94
+ "<=",
95
+ ">",
96
+ "<"
97
+ ];
79
98
  const keywordsSet = /* @__PURE__ */ new Set();
80
99
  for (const keyword of KEYWORDS) {
81
100
  keywordsSet.add(keyword);
@@ -117,6 +136,10 @@ const TOKENS = [
117
136
  type: TokenType.RightSquareBracket,
118
137
  matcher: "]"
119
138
  },
139
+ {
140
+ type: TokenType.Operator,
141
+ matcher: OPERATORS
142
+ },
120
143
  {
121
144
  type: TokenType.Pipe,
122
145
  matcher: "|"
@@ -157,7 +180,7 @@ const TOKENS = [
157
180
  type: TokenType.Keyword,
158
181
  matcher: Array.from(keywordsSet),
159
182
  condition: (state, lastToken) => {
160
- if (state.inPropertiesStack.at(-1)) {
183
+ if (state.variableScopeStack.at(-1)) {
161
184
  return false;
162
185
  }
163
186
  if (lastToken && lastToken.type === TokenType.Keyword) {
@@ -197,7 +220,7 @@ export const tokenize = function(rawInput) {
197
220
  const tokens = [];
198
221
  const errors = [];
199
222
  const state = {
200
- inPropertiesStack: []
223
+ variableScopeStack: []
201
224
  };
202
225
  while (index < input.length) {
203
226
  let hasMatch = false;
@@ -262,8 +285,10 @@ export const tokenize = function(rawInput) {
262
285
  };
263
286
  switch (type) {
264
287
  case TokenType.LeftBracket: {
288
+ let variableScope = false;
265
289
  if (lastToken && lastToken.type === TokenType.Keyword) {
266
290
  switch (lastToken.value) {
291
+ case "fields":
267
292
  case "information":
268
293
  case "form":
269
294
  case "table":
@@ -272,19 +297,17 @@ export const tokenize = function(rawInput) {
272
297
  case "writable":
273
298
  case "required":
274
299
  case "properties": {
275
- state.inPropertiesStack.push(true);
300
+ variableScope = true;
276
301
  break;
277
302
  }
278
- default: {
279
- state.inPropertiesStack.push(false);
280
- }
281
303
  }
282
304
  }
305
+ state.variableScopeStack.push(variableScope);
283
306
  break;
284
307
  }
285
308
  case TokenType.RightBracket: {
286
- if (state.inPropertiesStack.length > 0) {
287
- state.inPropertiesStack.pop();
309
+ if (state.variableScopeStack.length > 0) {
310
+ state.variableScopeStack.pop();
288
311
  }
289
312
  break;
290
313
  }
package/dist/parser.js CHANGED
@@ -600,6 +600,7 @@ const parse = (tokens) => {
600
600
  }
601
601
  continue;
602
602
  }
603
+ throw err;
603
604
  }
604
605
  }
605
606
  consume(token_js_1.TokenType.RightBracket);
@@ -731,6 +732,10 @@ const parse = (tokens) => {
731
732
  node[keyword] = parseLayoutBlock();
732
733
  break;
733
734
  }
735
+ case 'formLayout': {
736
+ node[keyword] = parseFormLayoutBlock();
737
+ break;
738
+ }
734
739
  }
735
740
  }
736
741
  catch (err) {
@@ -739,6 +744,7 @@ const parse = (tokens) => {
739
744
  recover(lexer.COLLECTION_KEYWORDS);
740
745
  continue;
741
746
  }
747
+ throw err;
742
748
  }
743
749
  }
744
750
  consume(token_js_1.TokenType.RightBracket);
@@ -792,6 +798,7 @@ const parse = (tokens) => {
792
798
  if (match(token_js_1.TokenType.MacroName)) {
793
799
  const { value: macroName } = consume(token_js_1.TokenType.MacroName, ['include']);
794
800
  switch (macroName) {
801
+ /* eslint-disable-next-line */
795
802
  case 'include': {
796
803
  const { value: functionSetName, location } = consume(token_js_1.TokenType.Identifier);
797
804
  const functionset = ast.functionsets.find((node) => node.name === functionSetName);
@@ -835,6 +842,7 @@ const parse = (tokens) => {
835
842
  errors.push(err);
836
843
  continue;
837
844
  }
845
+ throw err;
838
846
  }
839
847
  }
840
848
  consume(token_js_1.TokenType.RightBracket);
@@ -1051,6 +1059,148 @@ const parse = (tokens) => {
1051
1059
  },
1052
1060
  };
1053
1061
  };
1062
+ const parseFormLayoutBlock = () => {
1063
+ const fields = {};
1064
+ const node = {
1065
+ kind: 'formLayout',
1066
+ fields,
1067
+ [AST.LOCATION_SYMBOL]: {
1068
+ fields: {},
1069
+ },
1070
+ };
1071
+ consume(token_js_1.TokenType.LeftBracket);
1072
+ while (!match(token_js_1.TokenType.RightBracket)) {
1073
+ const { value: keyword, location: keywordLocation } = consume(token_js_1.TokenType.Keyword, lexer.COLLECTION_FORM_LAYOUT_KEYWORDS);
1074
+ switch (keyword) {
1075
+ case 'fields': {
1076
+ consume(token_js_1.TokenType.LeftBracket);
1077
+ while (!match(token_js_1.TokenType.RightBracket)) {
1078
+ const { value: identifier, location: identifierLocation } = consume(token_js_1.TokenType.Identifier);
1079
+ const identifierSymbol = Symbol();
1080
+ exports.locationMap.set(identifierSymbol, identifierLocation);
1081
+ fields[identifier] ??= {};
1082
+ node[AST.LOCATION_SYMBOL].fields[identifier] = {
1083
+ name: identifierSymbol,
1084
+ field: {},
1085
+ };
1086
+ consume(token_js_1.TokenType.LeftBracket);
1087
+ while (!match(token_js_1.TokenType.RightBracket)) {
1088
+ const { value: keyword, location: keywordLocation } = consume(token_js_1.TokenType.Keyword, lexer.COLLECTION_FORM_LAYOUT_KEYWORDS);
1089
+ switch (keyword) {
1090
+ case 'if': {
1091
+ fields[identifier].if = parseCondition();
1092
+ break;
1093
+ }
1094
+ case 'span':
1095
+ case 'verticalSpacing': {
1096
+ fields[identifier].span = consume(token_js_1.TokenType.Number).value;
1097
+ break;
1098
+ }
1099
+ case 'separator': {
1100
+ fields[identifier].separator = match(token_js_1.TokenType.Boolean)
1101
+ ? consume(token_js_1.TokenType.Boolean).value
1102
+ : consume(token_js_1.TokenType.QuotedString, [
1103
+ 'top',
1104
+ 'bottom',
1105
+ ]).value;
1106
+ break;
1107
+ }
1108
+ default: {
1109
+ throw new diagnostic_js_1.Diagnostic(`invalid keyword "${keyword}"`, keywordLocation);
1110
+ }
1111
+ }
1112
+ }
1113
+ consume(token_js_1.TokenType.RightBracket);
1114
+ }
1115
+ consume(token_js_1.TokenType.RightBracket);
1116
+ break;
1117
+ }
1118
+ default: {
1119
+ throw new diagnostic_js_1.Diagnostic(`invalid keyword "${keyword}"`, keywordLocation);
1120
+ }
1121
+ }
1122
+ }
1123
+ consume(token_js_1.TokenType.RightBracket);
1124
+ return node;
1125
+ };
1126
+ const parseCondition = () => {
1127
+ if (match(token_js_1.TokenType.LeftParens)) {
1128
+ consume(token_js_1.TokenType.LeftParens);
1129
+ let operatorType, newOp = operatorType;
1130
+ const conditions = [];
1131
+ while (!match(token_js_1.TokenType.RightParens)) {
1132
+ conditions.push(parseCondition());
1133
+ if (match(token_js_1.TokenType.RightParens)) {
1134
+ break;
1135
+ }
1136
+ const { value: operatorSymbol, location } = consume(token_js_1.TokenType.Operator);
1137
+ switch (operatorSymbol) {
1138
+ case '&&':
1139
+ newOp = 'and';
1140
+ break;
1141
+ case '||':
1142
+ newOp = 'or';
1143
+ break;
1144
+ default: {
1145
+ throw new diagnostic_js_1.Diagnostic(`unsupported operator: "${operatorSymbol}"`, location);
1146
+ }
1147
+ }
1148
+ if (operatorType && operatorType !== newOp) {
1149
+ throw new diagnostic_js_1.Diagnostic('having "and" or "or" in the same expression is not supported, please use parenthesis', location);
1150
+ }
1151
+ operatorType = newOp;
1152
+ }
1153
+ consume(token_js_1.TokenType.RightParens);
1154
+ switch (operatorType) {
1155
+ case 'and': {
1156
+ return {
1157
+ and: conditions,
1158
+ };
1159
+ }
1160
+ case 'or': {
1161
+ return {
1162
+ or: conditions,
1163
+ };
1164
+ }
1165
+ default: {
1166
+ return conditions[0];
1167
+ }
1168
+ }
1169
+ }
1170
+ const { value: term1 } = consume(token_js_1.TokenType.Identifier);
1171
+ const { value: operatorSymbol, location } = consume(token_js_1.TokenType.Operator);
1172
+ const { value: term2 } = current();
1173
+ advance();
1174
+ let operator;
1175
+ switch (operatorSymbol) {
1176
+ case '==':
1177
+ operator = 'equal';
1178
+ break;
1179
+ case 'in':
1180
+ operator = 'in';
1181
+ break;
1182
+ case '>=':
1183
+ operator = 'gte';
1184
+ break;
1185
+ case '<=':
1186
+ operator = 'lte';
1187
+ break;
1188
+ case '>':
1189
+ operator = 'gt';
1190
+ break;
1191
+ case '<':
1192
+ operator = 'lt';
1193
+ break;
1194
+ default: {
1195
+ throw new diagnostic_js_1.Diagnostic(`unsuported operator: "${operatorSymbol}"`, location);
1196
+ }
1197
+ }
1198
+ return {
1199
+ operator,
1200
+ term1,
1201
+ term2,
1202
+ };
1203
+ };
1054
1204
  while (index < tokens.length) {
1055
1205
  const { value: declType, location } = current();
1056
1206
  try {
package/dist/parser.mjs CHANGED
@@ -551,6 +551,7 @@ export const parse = (tokens) => {
551
551
  }
552
552
  continue;
553
553
  }
554
+ throw err;
554
555
  }
555
556
  }
556
557
  consume(TokenType.RightBracket);
@@ -676,6 +677,10 @@ export const parse = (tokens) => {
676
677
  node[keyword] = parseLayoutBlock();
677
678
  break;
678
679
  }
680
+ case "formLayout": {
681
+ node[keyword] = parseFormLayoutBlock();
682
+ break;
683
+ }
679
684
  }
680
685
  } catch (err) {
681
686
  if (err instanceof Diagnostic) {
@@ -683,6 +688,7 @@ export const parse = (tokens) => {
683
688
  recover(lexer.COLLECTION_KEYWORDS);
684
689
  continue;
685
690
  }
691
+ throw err;
686
692
  }
687
693
  }
688
694
  consume(TokenType.RightBracket);
@@ -777,6 +783,7 @@ export const parse = (tokens) => {
777
783
  errors.push(err);
778
784
  continue;
779
785
  }
786
+ throw err;
780
787
  }
781
788
  }
782
789
  consume(TokenType.RightBracket);
@@ -991,6 +998,146 @@ export const parse = (tokens) => {
991
998
  }
992
999
  };
993
1000
  };
1001
+ const parseFormLayoutBlock = () => {
1002
+ const fields = {};
1003
+ const node = {
1004
+ kind: "formLayout",
1005
+ fields,
1006
+ [AST.LOCATION_SYMBOL]: {
1007
+ fields: {}
1008
+ }
1009
+ };
1010
+ consume(TokenType.LeftBracket);
1011
+ while (!match(TokenType.RightBracket)) {
1012
+ const { value: keyword, location: keywordLocation } = consume(TokenType.Keyword, lexer.COLLECTION_FORM_LAYOUT_KEYWORDS);
1013
+ switch (keyword) {
1014
+ case "fields": {
1015
+ consume(TokenType.LeftBracket);
1016
+ while (!match(TokenType.RightBracket)) {
1017
+ const { value: identifier, location: identifierLocation } = consume(TokenType.Identifier);
1018
+ const identifierSymbol = Symbol();
1019
+ locationMap.set(identifierSymbol, identifierLocation);
1020
+ fields[identifier] ??= {};
1021
+ node[AST.LOCATION_SYMBOL].fields[identifier] = {
1022
+ name: identifierSymbol,
1023
+ field: {}
1024
+ };
1025
+ consume(TokenType.LeftBracket);
1026
+ while (!match(TokenType.RightBracket)) {
1027
+ const { value: keyword2, location: keywordLocation2 } = consume(TokenType.Keyword, lexer.COLLECTION_FORM_LAYOUT_KEYWORDS);
1028
+ switch (keyword2) {
1029
+ case "if": {
1030
+ fields[identifier].if = parseCondition();
1031
+ break;
1032
+ }
1033
+ case "span":
1034
+ case "verticalSpacing": {
1035
+ fields[identifier].span = consume(TokenType.Number).value;
1036
+ break;
1037
+ }
1038
+ case "separator": {
1039
+ fields[identifier].separator = match(TokenType.Boolean) ? consume(TokenType.Boolean).value : consume(TokenType.QuotedString, [
1040
+ "top",
1041
+ "bottom"
1042
+ ]).value;
1043
+ break;
1044
+ }
1045
+ default: {
1046
+ throw new Diagnostic(`invalid keyword "${keyword2}"`, keywordLocation2);
1047
+ }
1048
+ }
1049
+ }
1050
+ consume(TokenType.RightBracket);
1051
+ }
1052
+ consume(TokenType.RightBracket);
1053
+ break;
1054
+ }
1055
+ default: {
1056
+ throw new Diagnostic(`invalid keyword "${keyword}"`, keywordLocation);
1057
+ }
1058
+ }
1059
+ }
1060
+ consume(TokenType.RightBracket);
1061
+ return node;
1062
+ };
1063
+ const parseCondition = () => {
1064
+ if (match(TokenType.LeftParens)) {
1065
+ consume(TokenType.LeftParens);
1066
+ let operatorType, newOp = operatorType;
1067
+ const conditions = [];
1068
+ while (!match(TokenType.RightParens)) {
1069
+ conditions.push(parseCondition());
1070
+ if (match(TokenType.RightParens)) {
1071
+ break;
1072
+ }
1073
+ const { value: operatorSymbol2, location: location2 } = consume(TokenType.Operator);
1074
+ switch (operatorSymbol2) {
1075
+ case "&&":
1076
+ newOp = "and";
1077
+ break;
1078
+ case "||":
1079
+ newOp = "or";
1080
+ break;
1081
+ default: {
1082
+ throw new Diagnostic(`unsupported operator: "${operatorSymbol2}"`, location2);
1083
+ }
1084
+ }
1085
+ if (operatorType && operatorType !== newOp) {
1086
+ throw new Diagnostic('having "and" or "or" in the same expression is not supported, please use parenthesis', location2);
1087
+ }
1088
+ operatorType = newOp;
1089
+ }
1090
+ consume(TokenType.RightParens);
1091
+ switch (operatorType) {
1092
+ case "and": {
1093
+ return {
1094
+ and: conditions
1095
+ };
1096
+ }
1097
+ case "or": {
1098
+ return {
1099
+ or: conditions
1100
+ };
1101
+ }
1102
+ default: {
1103
+ return conditions[0];
1104
+ }
1105
+ }
1106
+ }
1107
+ const { value: term1 } = consume(TokenType.Identifier);
1108
+ const { value: operatorSymbol, location } = consume(TokenType.Operator);
1109
+ const { value: term2 } = current();
1110
+ advance();
1111
+ let operator;
1112
+ switch (operatorSymbol) {
1113
+ case "==":
1114
+ operator = "equal";
1115
+ break;
1116
+ case "in":
1117
+ operator = "in";
1118
+ break;
1119
+ case ">=":
1120
+ operator = "gte";
1121
+ break;
1122
+ case "<=":
1123
+ operator = "lte";
1124
+ break;
1125
+ case ">":
1126
+ operator = "gt";
1127
+ break;
1128
+ case "<":
1129
+ operator = "lt";
1130
+ break;
1131
+ default: {
1132
+ throw new Diagnostic(`unsuported operator: "${operatorSymbol}"`, location);
1133
+ }
1134
+ }
1135
+ return {
1136
+ operator,
1137
+ term1,
1138
+ term2
1139
+ };
1140
+ };
994
1141
  while (index < tokens.length) {
995
1142
  const { value: declType, location } = current();
996
1143
  try {
package/dist/semantic.js CHANGED
@@ -160,6 +160,16 @@ const analyze = async (ast, options, errors = []) => {
160
160
  }
161
161
  }
162
162
  }
163
+ if (node.formLayout) {
164
+ if (node.formLayout.fields) {
165
+ for (const [name, value] of Object.entries(node.formLayout[AST.LOCATION_SYMBOL].fields)) {
166
+ if (!(name in node.properties)) {
167
+ const location = parser_js_1.locationMap.get(value.name);
168
+ errors.push(new diagnostic_js_1.Diagnostic(`invalid property "${name}"`, location));
169
+ }
170
+ }
171
+ }
172
+ }
163
173
  }
164
174
  for (const node of ast.contracts) {
165
175
  if (node.payload) {
package/dist/semantic.mjs CHANGED
@@ -122,6 +122,16 @@ export const analyze = async (ast, options, errors = []) => {
122
122
  }
123
123
  }
124
124
  }
125
+ if (node.formLayout) {
126
+ if (node.formLayout.fields) {
127
+ for (const [name, value] of Object.entries(node.formLayout[AST.LOCATION_SYMBOL].fields)) {
128
+ if (!(name in node.properties)) {
129
+ const location = locationMap.get(value.name);
130
+ errors.push(new Diagnostic(`invalid property "${name}"`, location));
131
+ }
132
+ }
133
+ }
134
+ }
125
135
  }
126
136
  for (const node of ast.contracts) {
127
137
  if (node.payload) {
package/dist/token.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare const TokenType: {
19
19
  readonly AttributeName: "ATTRIBUTE_NAME";
20
20
  readonly MacroName: "MACRO_NAME";
21
21
  readonly Range: "RANGE";
22
+ readonly Operator: "OPERATOR";
22
23
  };
23
24
  export type TokenType = typeof TokenType[keyof typeof TokenType];
24
25
  export type TypeMap = {
package/dist/token.js CHANGED
@@ -22,4 +22,5 @@ exports.TokenType = {
22
22
  AttributeName: 'ATTRIBUTE_NAME',
23
23
  MacroName: 'MACRO_NAME',
24
24
  Range: 'RANGE',
25
+ Operator: 'OPERATOR',
25
26
  };
package/dist/token.mjs CHANGED
@@ -19,5 +19,6 @@ export const TokenType = {
19
19
  QuotedString: "QUOTED_STRING",
20
20
  AttributeName: "ATTRIBUTE_NAME",
21
21
  MacroName: "MACRO_NAME",
22
- Range: "RANGE"
22
+ Range: "RANGE",
23
+ Operator: "OPERATOR"
23
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeriajs/compiler",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -21,8 +21,8 @@
21
21
  "dist"
22
22
  ],
23
23
  "peerDependencies": {
24
- "@aeriajs/common": "^0.0.141",
25
- "@aeriajs/types": "^0.0.123"
24
+ "@aeriajs/common": "^0.0.142",
25
+ "@aeriajs/types": "^0.0.124"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@aeriajs/common": "link:../common",