@mirascript/typed 0.1.62 → 0.1.63

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/src/type.peggy CHANGED
@@ -1,5 +1,30 @@
1
+ {{
2
+ import { KEYWORDS } from '@mirascript/constants';
3
+
4
+ const BUILT_IN_TYPES = new Set([
5
+ 'any',
6
+ 'unknown',
7
+ 'never',
8
+
9
+ 'boolean',
10
+ 'number',
11
+ 'string',
12
+ 'record',
13
+ 'array',
14
+ 'extern',
15
+ 'module',
16
+ 'nil',
17
+ ]);
18
+
19
+ const BUILT_IN_LITERALS = new Map([
20
+ ['true', true],
21
+ ['false', false],
22
+ ])
23
+ }}
24
+
1
25
  {
2
26
  function named(name) {
27
+ if (KEYWORDS.includes(name)) throw new Error(`'${name}' is a reserved keyword`);
3
28
  return name;
4
29
  }
5
30
 
@@ -18,34 +43,35 @@ function record(fields) {
18
43
  }
19
44
  return { kind: "record", fields };
20
45
  }
46
+
47
+ function literal(value) {
48
+ return { kind: "literal", value };
49
+ }
50
+
51
+ function string(parts) {
52
+ if (parts.length === 0) return '';
53
+ if (parts.length === 1 && parts[0].kind === 'literal') {
54
+ return parts[0].value;
55
+ }
56
+ return { kind: 'template', parts };
57
+ }
21
58
  }
22
59
 
23
60
  Start
24
- = _ t:Type _ { return t; }
61
+ = _ @NamedFunction _
62
+ / _ @Type _
25
63
 
26
64
  ///////////////////////////////////////////////////////////////////////////////
27
65
  // Type (union)
28
66
  ///////////////////////////////////////////////////////////////////////////////
29
67
 
30
68
  Type
31
- = head:UnionType {
32
- return head;
33
- }
69
+ = UnionType
34
70
 
35
71
  UnionType
36
- = _ "|" _ first:PostfixType rest:(_ "|" _ PostfixType)* {
37
- return union([
38
- first,
39
- ...rest.map(x => x[3])
40
- ]);
41
- }
42
- / first:PostfixType rest:(_ "|" _ PostfixType)+ {
43
- return union([
44
- first,
45
- ...rest.map(x => x[3])
46
- ]);
72
+ = _ ( "|" _ )? u:PostfixType|1.., _ "|" _ | {
73
+ return union(u);
47
74
  }
48
- / PostfixType
49
75
 
50
76
  ///////////////////////////////////////////////////////////////////////////////
51
77
  // Array
@@ -64,65 +90,179 @@ PostfixType
64
90
  ///////////////////////////////////////////////////////////////////////////////
65
91
 
66
92
  PrimaryType
67
- = name:Identifier generic:(_ "<" _ t:Type _ ">")? {
93
+ = FunctionType
94
+ / name:Identifier generic:(_ "<" _ @TypeArgList _ ">")? {
68
95
  if (generic) {
69
- const t = generic[3];
70
- if (name === 'array') return array(t);
71
- if (name === 'record') return { kind: "record", fields: [], value: t };
96
+ if (name === 'array') {
97
+ if (generic.length !== 1) throw new Error("'array' expects one generic type parameter");
98
+ return array(generic[0]);
99
+ }
100
+ if (name === 'record') {
101
+ if (generic.length === 1) return { kind: "record", value: generic[0] };
102
+ if (generic.length === 2) return { kind: "record", key: generic[0], value: generic[1] };
103
+ throw new Error("'record' expects one or two generic type parameters");
104
+ }
105
+ throw new Error(`'${name}' is not a generic type`);
106
+ }
107
+ if (BUILT_IN_LITERALS.has(name)) {
108
+ return { kind: "literal", value: BUILT_IN_LITERALS.get(name) };
109
+ }
110
+ if (BUILT_IN_TYPES.has(name)) {
111
+ return name;
72
112
  }
73
- if (name === 'true') return { kind: "literal", value: true };
74
- if (name === 'false') return { kind: "literal", value: false };
75
113
  return named(name);
76
114
  }
77
115
  / lit:String {
78
- return { kind: "literal", value: lit };
79
- }
80
- / "(" _ t:Type _ ")" {
81
- return t;
116
+ if (typeof lit == 'string') return { kind: "literal", value: lit };
117
+ return lit;
82
118
  }
119
+ / "(" _ @Type _ ")"
83
120
  / RecordType
84
121
 
122
+
123
+ ///////////////////////////////////////////////////////////////////////////////
124
+ // Generics
125
+ ///////////////////////////////////////////////////////////////////////////////
126
+
127
+ TypeParamList
128
+ = @Identifier|1.., _ "," _ | ( _ "," _ )?
129
+
130
+ TypeArgList
131
+ = @Type|1.., _ "," _ | ( _ "," _ )?
132
+
133
+ ///////////////////////////////////////////////////////////////////////////////
134
+ // Function
135
+ ///////////////////////////////////////////////////////////////////////////////
136
+
137
+ FunctionType
138
+ = "fn" _ body:FunctionBody { return body; }
139
+
140
+ NamedFunction
141
+ = "fn" _ name:Identifier _ body:FunctionBody {
142
+ if (KEYWORDS.includes(name)) throw new Error(`'${name}' is a reserved keyword`);
143
+ body.name = name;
144
+ return body;
145
+ }
146
+
147
+ FunctionBody
148
+ = typeParams:(_ "<" _ @TypeParamList _ ">")? _ "(" _ params:FunctionParamList? _ ")" ret:(_ "->" _ @Type)? {
149
+ const result = { kind: "function", params: params ?? [] };
150
+ if (typeParams !== null) result.typeParams = typeParams.map(name => Symbol(name));
151
+ if (ret !== null) result.returns = ret;
152
+ return result;
153
+ }
154
+
155
+ FunctionParamList
156
+ = params:FunctionParam|1.., _ "," _ | ( _ "," _ )? {
157
+ for (let i = 0; i < params.length - 1; i++) {
158
+ if (params[i].spread) throw new Error("Rest parameter must be last");
159
+ }
160
+ return params;
161
+ }
162
+
163
+ FunctionParam
164
+ = spread:".." name:Identifier? _ t:(":" _ @Type)? {
165
+ return {
166
+ name: name ?? "",
167
+ spread: true,
168
+ type: t ?? { kind: "array", element: "any" }
169
+ }
170
+ }
171
+ / name:Identifier _ t:(":" _ @Type)? {
172
+ return { name, type: t ?? "any" };
173
+ }
174
+
85
175
  ///////////////////////////////////////////////////////////////////////////////
86
176
  // Record
87
177
  ///////////////////////////////////////////////////////////////////////////////
88
178
 
89
179
  RecordType
90
- = "(" _ ")" { return record([]); }
91
- / "(" _ fields:FieldList _ ")" { return record(fields); }
180
+ = "(" _ fields:FieldList? _ ")" { return record(fields ?? []); }
92
181
 
93
182
  FieldList
94
- = fields:Field|1.., _ "," _ | ( _ "," _ )? {
95
- return fields;
96
- }
183
+ = @Field|1.., _ "," _ | ( _ "," _ )?
97
184
 
98
185
  Field
99
186
  = name:FieldName opt:("?"?) _ ":" _ t:Type {
100
187
  return {
101
188
  name,
102
- optional: opt !== null,
103
- type: t
104
- };
105
- }
106
- / t:Type {
107
- return {
189
+ optional: opt != null,
108
190
  type: t
109
191
  };
110
192
  }
193
+ / type:Type { return { type }; }
111
194
 
112
195
  FieldName
113
196
  = Identifier
114
- / String
197
+ / name:String {
198
+ if (typeof name != 'string') throw new Error('Field name must be a string literal');
199
+ return name;
200
+ }
115
201
 
116
202
  ///////////////////////////////////////////////////////////////////////////////
117
203
  // Lexer
118
204
  ///////////////////////////////////////////////////////////////////////////////
119
205
 
120
- String
121
- = '"' @$[^"]* '"'
122
- / "'" @$[^']* "'"
206
+ HexDigit "hex digit"
207
+ = [0-9a-f]i
208
+
209
+ EscapeSequence
210
+ = "\\" char:(['"`\\nrtbfv0$]) {
211
+ switch (char) {
212
+ case 'n': return '\n';
213
+ case 'r': return '\r';
214
+ case 't': return '\t';
215
+ case 'b': return '\b';
216
+ case 'f': return '\f';
217
+ case 'v': return '\v';
218
+ case '0': return '\0';
219
+ default: return char;
220
+ }
221
+ } / "\\x" hex:$(HexDigit HexDigit) {
222
+ const code = parseInt(hex, 16);
223
+ // Valid ASCII characters are in the range 0x00 to 0x7F.
224
+ if (code >= 128) throw new Error(`Invalid hex escape sequence: \\x${hex}`);
225
+ return String.fromCharCode(code);
226
+ } / "\\u{" hex:$(HexDigit+) "}" {
227
+ const code = parseInt(hex, 16);
228
+ // Valid Unicode code points are in the range 0x0000 to 0x10FFFF, not including the surrogate halves (0xD800 to 0xDFFF).
229
+ if (code > 0x10FFFF || (code >= 0xD800 && code <= 0xDFFF)) {
230
+ throw new Error(`Invalid Unicode code point: \\u{${hex}}`);
231
+ }
232
+ return String.fromCodePoint(code);
233
+ }
234
+
235
+ String "string"
236
+ = '"' parts:DoubleQuotePart* '"' { return string(parts); }
237
+ / "'" parts:SingleQuotePart* "'" { return string(parts); }
238
+ / "`" parts:BacktickPart* "`" { return string(parts); }
239
+
240
+ DoubleQuotePart
241
+ = "$" "(" _ @Type _ ")"
242
+ / chars:DoubleQuoteChar+ { return literal(chars.join('')); }
243
+
244
+ SingleQuotePart
245
+ = "$" "(" _ @Type _ ")"
246
+ / chars:SingleQuoteChar+ { return literal(chars.join('')); }
247
+
248
+ BacktickPart
249
+ = "$" "(" _ @Type _ ")"
250
+ / chars:BacktickChar+ { return literal(chars.join('')); }
251
+
252
+ DoubleQuoteChar
253
+ = EscapeSequence
254
+ / $[^\\$"]+
255
+
256
+ SingleQuoteChar
257
+ = EscapeSequence
258
+ / $[^\\$']+
259
+
260
+ BacktickChar
261
+ = EscapeSequence
262
+ / $[^\\$`]+
123
263
 
124
- Identifier
125
- = $([a-zA-Z_][a-zA-Z0-9_]*)
264
+ Identifier "identifier"
265
+ = $(("_"+ / "@"+ / "$"+ / [\p{XID_Start}]u) [\p{XID_Continue}]u*)
126
266
 
127
- _
128
- = [ \t\r\n]*
267
+ _ "whitespace"
268
+ = [ \t\v\f\r\n]*