1use crate::types::{Effect, StackType, Type};
6
7use super::{Program, Statement, WordDef};
8
9impl Program {
10 pub fn new() -> Self {
11 Program {
12 includes: Vec::new(),
13 unions: Vec::new(),
14 words: Vec::new(),
15 }
16 }
17
18 pub fn find_word(&self, name: &str) -> Option<&WordDef> {
19 self.words.iter().find(|w| w.name == name)
20 }
21
22 pub fn validate_word_calls(&self) -> Result<(), String> {
24 self.validate_word_calls_with_externals(&[])
25 }
26
27 pub fn validate_word_calls_with_externals(
32 &self,
33 external_words: &[&str],
34 ) -> Result<(), String> {
35 let builtins = [
38 "io.write",
40 "io.write-line",
41 "io.read-line",
42 "io.read-n",
43 "int->string",
44 "symbol->string",
45 "string->symbol",
46 "args.count",
48 "args.at",
49 "file.slurp",
51 "file.exists?",
52 "file.for-each-line+",
53 "file.spit",
54 "file.append",
55 "file.delete",
56 "file.size",
57 "dir.exists?",
59 "dir.make",
60 "dir.delete",
61 "dir.list",
62 "string.concat",
64 "string.length",
65 "string.byte-length",
66 "string.char-at",
67 "string.substring",
68 "char->string",
69 "string.find",
70 "string.split",
71 "string.contains",
72 "string.starts-with",
73 "string.empty?",
74 "string.trim",
75 "string.chomp",
76 "string.to-upper",
77 "string.to-lower",
78 "string.equal?",
79 "string.join",
80 "string.json-escape",
81 "string->int",
82 "symbol.=",
84 "encoding.base64-encode",
86 "encoding.base64-decode",
87 "encoding.base64url-encode",
88 "encoding.base64url-decode",
89 "encoding.hex-encode",
90 "encoding.hex-decode",
91 "crypto.sha256",
93 "crypto.hmac-sha256",
94 "crypto.constant-time-eq",
95 "crypto.random-bytes",
96 "crypto.random-int",
97 "crypto.uuid4",
98 "crypto.aes-gcm-encrypt",
99 "crypto.aes-gcm-decrypt",
100 "crypto.pbkdf2-sha256",
101 "crypto.ed25519-keypair",
102 "crypto.ed25519-sign",
103 "crypto.ed25519-verify",
104 "http.get",
106 "http.post",
107 "http.put",
108 "http.delete",
109 "list.make",
111 "list.push",
112 "list.get",
113 "list.set",
114 "list.map",
115 "list.filter",
116 "list.fold",
117 "list.each",
118 "list.length",
119 "list.empty?",
120 "list.reverse",
121 "list.first",
122 "list.last",
123 "map.make",
125 "map.get",
126 "map.set",
127 "map.has?",
128 "map.remove",
129 "map.keys",
130 "map.values",
131 "map.size",
132 "map.empty?",
133 "map.each",
134 "map.fold",
135 "variant.field-count",
137 "variant.tag",
138 "variant.field-at",
139 "variant.append",
140 "variant.first",
141 "variant.last",
142 "variant.init",
143 "variant.make-0",
144 "variant.make-1",
145 "variant.make-2",
146 "variant.make-3",
147 "variant.make-4",
148 "wrap-0",
150 "wrap-1",
151 "wrap-2",
152 "wrap-3",
153 "wrap-4",
154 "i.add",
156 "i.subtract",
157 "i.multiply",
158 "i.divide",
159 "i.modulo",
160 "i.+",
162 "i.-",
163 "i.*",
164 "i./",
165 "i.%",
166 "i.=",
168 "i.<",
169 "i.>",
170 "i.<=",
171 "i.>=",
172 "i.<>",
173 "i.eq",
175 "i.lt",
176 "i.gt",
177 "i.lte",
178 "i.gte",
179 "i.neq",
180 "dup",
182 "drop",
183 "swap",
184 "over",
185 "rot",
186 "nip",
187 "tuck",
188 "2dup",
189 "3drop",
190 "pick",
191 "roll",
192 ">aux",
194 "aux>",
195 "and",
197 "or",
198 "not",
199 "band",
201 "bor",
202 "bxor",
203 "bnot",
204 "i.neg",
205 "negate",
206 "+",
208 "-",
209 "*",
210 "/",
211 "%",
212 "=",
213 "<",
214 ">",
215 "<=",
216 ">=",
217 "<>",
218 "shl",
219 "shr",
220 "popcount",
221 "clz",
222 "ctz",
223 "int-bits",
224 "chan.make",
226 "chan.send",
227 "chan.receive",
228 "chan.close",
229 "chan.yield",
230 "call",
232 "dip",
234 "keep",
235 "bi",
236 "if",
237 "strand.spawn",
238 "strand.weave",
239 "strand.resume",
240 "strand.weave-cancel",
241 "yield",
242 "cond",
243 "tcp.listen",
245 "tcp.accept",
246 "tcp.read",
247 "tcp.write",
248 "tcp.close",
249 "udp.bind",
251 "udp.send-to",
252 "udp.receive-from",
253 "udp.close",
254 "os.getenv",
256 "os.home-dir",
257 "os.current-dir",
258 "os.path-exists",
259 "os.path-is-file",
260 "os.path-is-dir",
261 "os.path-join",
262 "os.path-parent",
263 "os.path-filename",
264 "os.exit",
265 "os.name",
266 "os.arch",
267 "signal.trap",
269 "signal.received?",
270 "signal.pending?",
271 "signal.default",
272 "signal.ignore",
273 "signal.clear",
274 "signal.SIGINT",
275 "signal.SIGTERM",
276 "signal.SIGHUP",
277 "signal.SIGPIPE",
278 "signal.SIGUSR1",
279 "signal.SIGUSR2",
280 "signal.SIGCHLD",
281 "signal.SIGALRM",
282 "signal.SIGCONT",
283 "terminal.raw-mode",
285 "terminal.read-char",
286 "terminal.read-char?",
287 "terminal.width",
288 "terminal.height",
289 "terminal.flush",
290 "f.add",
292 "f.subtract",
293 "f.multiply",
294 "f.divide",
295 "f.+",
297 "f.-",
298 "f.*",
299 "f./",
300 "f.=",
302 "f.<",
303 "f.>",
304 "f.<=",
305 "f.>=",
306 "f.<>",
307 "f.eq",
309 "f.lt",
310 "f.gt",
311 "f.lte",
312 "f.gte",
313 "f.neq",
314 "int->float",
316 "float->int",
317 "float->string",
318 "string->float",
319 "int.to-bytes-i32-be",
321 "float.to-bytes-f32-be",
322 "test.init",
324 "test.set-name",
325 "test.finish",
326 "test.has-failures",
327 "test.assert",
328 "test.assert-not",
329 "test.assert-eq",
330 "test.assert-eq-str",
331 "test.fail",
332 "test.pass-count",
333 "test.fail-count",
334 "time.now",
336 "time.nanos",
337 "time.sleep-ms",
338 "son.dump",
340 "son.dump-pretty",
341 "stack.dump",
343 "regex.match?",
345 "regex.find",
346 "regex.find-all",
347 "regex.replace",
348 "regex.replace-all",
349 "regex.captures",
350 "regex.split",
351 "regex.valid?",
352 "compress.gzip",
354 "compress.gzip-level",
355 "compress.gunzip",
356 "compress.zstd",
357 "compress.zstd-level",
358 "compress.unzstd",
359 ];
360
361 for word in &self.words {
362 self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
363 }
364
365 Ok(())
366 }
367
368 fn validate_statements(
370 &self,
371 statements: &[Statement],
372 word_name: &str,
373 builtins: &[&str],
374 external_words: &[&str],
375 ) -> Result<(), String> {
376 for statement in statements {
377 match statement {
378 Statement::WordCall { name, .. } => {
379 if builtins.contains(&name.as_str()) {
381 continue;
382 }
383 if self.find_word(name).is_some() {
385 continue;
386 }
387 if external_words.contains(&name.as_str()) {
389 continue;
390 }
391 return Err(format!(
393 "Undefined word '{}' called in word '{}'. \
394 Did you forget to define it or misspell a built-in?",
395 name, word_name
396 ));
397 }
398 Statement::If {
399 then_branch,
400 else_branch,
401 span: _,
402 } => {
403 self.validate_statements(then_branch, word_name, builtins, external_words)?;
405 if let Some(eb) = else_branch {
406 self.validate_statements(eb, word_name, builtins, external_words)?;
407 }
408 }
409 Statement::Quotation { body, .. } => {
410 self.validate_statements(body, word_name, builtins, external_words)?;
412 }
413 Statement::Match { arms, span: _ } => {
414 for arm in arms {
416 self.validate_statements(&arm.body, word_name, builtins, external_words)?;
417 }
418 }
419 _ => {} }
421 }
422 Ok(())
423 }
424
425 pub const MAX_VARIANT_FIELDS: usize = 12;
429
430 pub fn generate_constructors(&mut self) -> Result<(), String> {
443 let mut new_words = Vec::new();
444
445 for union_def in &self.unions {
446 for variant in &union_def.variants {
447 let field_count = variant.fields.len();
448
449 if field_count > Self::MAX_VARIANT_FIELDS {
451 return Err(format!(
452 "Variant '{}' in union '{}' has {} fields, but the maximum is {}. \
453 Consider grouping fields into nested union types.",
454 variant.name,
455 union_def.name,
456 field_count,
457 Self::MAX_VARIANT_FIELDS
458 ));
459 }
460
461 let constructor_name = format!("Make-{}", variant.name);
463 let mut input_stack = StackType::RowVar("a".to_string());
464 for field in &variant.fields {
465 let field_type = parse_type_name(&field.type_name);
466 input_stack = input_stack.push(field_type);
467 }
468 let output_stack =
469 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
470 let effect = Effect::new(input_stack, output_stack);
471 let body = vec![
472 Statement::Symbol(variant.name.clone()),
473 Statement::WordCall {
474 name: format!("variant.make-{}", field_count),
475 span: None,
476 },
477 ];
478 new_words.push(WordDef {
479 name: constructor_name,
480 effect: Some(effect),
481 body,
482 source: variant.source.clone(),
483 allowed_lints: vec![],
484 });
485
486 let predicate_name = format!("is-{}?", variant.name);
490 let predicate_input =
491 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
492 let predicate_output = StackType::RowVar("a".to_string()).push(Type::Bool);
493 let predicate_effect = Effect::new(predicate_input, predicate_output);
494 let predicate_body = vec![
495 Statement::WordCall {
496 name: "variant.tag".to_string(),
497 span: None,
498 },
499 Statement::Symbol(variant.name.clone()),
500 Statement::WordCall {
501 name: "symbol.=".to_string(),
502 span: None,
503 },
504 ];
505 new_words.push(WordDef {
506 name: predicate_name,
507 effect: Some(predicate_effect),
508 body: predicate_body,
509 source: variant.source.clone(),
510 allowed_lints: vec![],
511 });
512
513 for (index, field) in variant.fields.iter().enumerate() {
517 let accessor_name = format!("{}-{}", variant.name, field.name);
518 let field_type = parse_type_name(&field.type_name);
519 let accessor_input = StackType::RowVar("a".to_string())
520 .push(Type::Union(union_def.name.clone()));
521 let accessor_output = StackType::RowVar("a".to_string()).push(field_type);
522 let accessor_effect = Effect::new(accessor_input, accessor_output);
523 let accessor_body = vec![
524 Statement::IntLiteral(index as i64),
525 Statement::WordCall {
526 name: "variant.field-at".to_string(),
527 span: None,
528 },
529 ];
530 new_words.push(WordDef {
531 name: accessor_name,
532 effect: Some(accessor_effect),
533 body: accessor_body,
534 source: variant.source.clone(), allowed_lints: vec![],
536 });
537 }
538 }
539 }
540
541 self.words.extend(new_words);
542 Ok(())
543 }
544
545 pub fn fixup_union_types(&mut self) {
554 let union_names: std::collections::HashSet<String> =
556 self.unions.iter().map(|u| u.name.clone()).collect();
557
558 for word in &mut self.words {
560 if let Some(ref mut effect) = word.effect {
561 Self::fixup_stack_type(&mut effect.inputs, &union_names);
562 Self::fixup_stack_type(&mut effect.outputs, &union_names);
563 }
564 }
565 }
566
567 fn fixup_stack_type(stack: &mut StackType, union_names: &std::collections::HashSet<String>) {
569 match stack {
570 StackType::Empty | StackType::RowVar(_) => {}
571 StackType::Cons { rest, top } => {
572 Self::fixup_type(top, union_names);
573 Self::fixup_stack_type(rest, union_names);
574 }
575 }
576 }
577
578 fn fixup_type(ty: &mut Type, union_names: &std::collections::HashSet<String>) {
580 match ty {
581 Type::Var(name) if union_names.contains(name) => {
582 *ty = Type::Union(name.clone());
583 }
584 Type::Quotation(effect) => {
585 Self::fixup_stack_type(&mut effect.inputs, union_names);
586 Self::fixup_stack_type(&mut effect.outputs, union_names);
587 }
588 Type::Closure { effect, captures } => {
589 Self::fixup_stack_type(&mut effect.inputs, union_names);
590 Self::fixup_stack_type(&mut effect.outputs, union_names);
591 for cap in captures {
592 Self::fixup_type(cap, union_names);
593 }
594 }
595 _ => {}
596 }
597 }
598}
599
600fn parse_type_name(name: &str) -> Type {
603 match name {
604 "Int" => Type::Int,
605 "Float" => Type::Float,
606 "Bool" => Type::Bool,
607 "String" => Type::String,
608 "Channel" => Type::Channel,
609 other => Type::Union(other.to_string()),
610 }
611}
612
613impl Default for Program {
614 fn default() -> Self {
615 Self::new()
616 }
617}