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 "net.http.get",
106 "net.http.post",
107 "net.http.put",
108 "net.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.pow",
161 "i.+",
163 "i.-",
164 "i.*",
165 "i./",
166 "i.%",
167 "i.=",
169 "i.<",
170 "i.>",
171 "i.<=",
172 "i.>=",
173 "i.<>",
174 "i.eq",
176 "i.lt",
177 "i.gt",
178 "i.lte",
179 "i.gte",
180 "i.neq",
181 "dup",
183 "drop",
184 "swap",
185 "over",
186 "rot",
187 "nip",
188 "tuck",
189 "2dup",
190 "3drop",
191 "pick",
192 "roll",
193 ">aux",
195 "aux>",
196 "and",
198 "or",
199 "not",
200 "band",
202 "bor",
203 "bxor",
204 "bnot",
205 "i.neg",
206 "negate",
207 "+",
209 "-",
210 "*",
211 "/",
212 "%",
213 "=",
214 "<",
215 ">",
216 "<=",
217 ">=",
218 "<>",
219 "shl",
220 "shr",
221 "popcount",
222 "clz",
223 "ctz",
224 "int-bits",
225 "chan.make",
227 "chan.send",
228 "chan.receive",
229 "chan.close",
230 "chan.yield",
231 "call",
233 "dip",
235 "keep",
236 "bi",
237 "if",
238 "strand.spawn",
239 "strand.weave",
240 "strand.resume",
241 "strand.weave-cancel",
242 "yield",
243 "cond",
244 "net.tcp.listen",
246 "net.tcp.accept",
247 "net.tcp.read",
248 "net.tcp.write",
249 "net.tcp.close",
250 "fd->socket",
252 "socket->fd",
253 "net.udp.bind",
255 "net.udp.send-to",
256 "net.udp.receive-from",
257 "net.udp.close",
258 "os.getenv",
260 "os.home-dir",
261 "os.current-dir",
262 "os.path-exists",
263 "os.path-is-file",
264 "os.path-is-dir",
265 "os.path-join",
266 "os.path-parent",
267 "os.path-filename",
268 "os.exit",
269 "os.name",
270 "os.arch",
271 "signal.trap",
273 "signal.received?",
274 "signal.pending?",
275 "signal.default",
276 "signal.ignore",
277 "signal.clear",
278 "signal.SIGINT",
279 "signal.SIGTERM",
280 "signal.SIGHUP",
281 "signal.SIGPIPE",
282 "signal.SIGUSR1",
283 "signal.SIGUSR2",
284 "signal.SIGCHLD",
285 "signal.SIGALRM",
286 "signal.SIGCONT",
287 "terminal.raw-mode",
289 "terminal.read-char",
290 "terminal.read-char?",
291 "terminal.width",
292 "terminal.height",
293 "terminal.flush",
294 "f.add",
296 "f.subtract",
297 "f.multiply",
298 "f.divide",
299 "f.+",
301 "f.-",
302 "f.*",
303 "f./",
304 "f.=",
306 "f.<",
307 "f.>",
308 "f.<=",
309 "f.>=",
310 "f.<>",
311 "f.eq",
313 "f.lt",
314 "f.gt",
315 "f.lte",
316 "f.gte",
317 "f.neq",
318 "f.sqrt",
320 "f.cbrt",
321 "f.pow",
322 "f.exp",
324 "f.ln",
325 "f.log10",
326 "f.log2",
327 "f.sin",
329 "f.cos",
330 "f.tan",
331 "f.asin",
332 "f.acos",
333 "f.atan",
334 "f.atan2",
335 "f.floor",
337 "f.ceil",
338 "f.round",
339 "f.trunc",
340 "f.pi",
342 "f.e",
343 "f.tau",
344 "int->float",
346 "float->int",
347 "float->string",
348 "string->float",
349 "int.to-bytes-i32-be",
351 "float.to-bytes-f32-be",
352 "test.init",
354 "test.set-name",
355 "test.finish",
356 "test.has-failures",
357 "test.assert",
358 "test.assert-not",
359 "test.assert-eq",
360 "test.assert-eq-str",
361 "test.fail",
362 "test.pass-count",
363 "test.fail-count",
364 "time.now",
366 "time.nanos",
367 "time.sleep-ms",
368 "son.dump",
370 "son.dump-pretty",
371 "stack.dump",
373 "regex.match?",
375 "regex.find",
376 "regex.find-all",
377 "regex.replace",
378 "regex.replace-all",
379 "regex.captures",
380 "regex.split",
381 "regex.valid?",
382 "compress.gzip",
384 "compress.gzip-level",
385 "compress.gunzip",
386 "compress.zstd",
387 "compress.zstd-level",
388 "compress.unzstd",
389 ];
390
391 for word in &self.words {
392 self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
393 }
394
395 Ok(())
396 }
397
398 fn validate_statements(
400 &self,
401 statements: &[Statement],
402 word_name: &str,
403 builtins: &[&str],
404 external_words: &[&str],
405 ) -> Result<(), String> {
406 for statement in statements {
407 match statement {
408 Statement::WordCall { name, .. } => {
409 if builtins.contains(&name.as_str()) {
411 continue;
412 }
413 if self.find_word(name).is_some() {
415 continue;
416 }
417 if external_words.contains(&name.as_str()) {
419 continue;
420 }
421 if let Some(replacement) = v7_renamed_to(name) {
425 return Err(format!(
426 "'{}' was renamed to '{}' in v7.0 (called in word '{}'). \
427 See docs/MIGRATION_7_0.md.",
428 name, replacement, word_name
429 ));
430 }
431 return Err(format!(
433 "Undefined word '{}' called in word '{}'. \
434 Did you forget to define it or misspell a built-in?",
435 name, word_name
436 ));
437 }
438 Statement::If {
439 then_branch,
440 else_branch,
441 span: _,
442 } => {
443 self.validate_statements(then_branch, word_name, builtins, external_words)?;
445 if let Some(eb) = else_branch {
446 self.validate_statements(eb, word_name, builtins, external_words)?;
447 }
448 }
449 Statement::Quotation { body, .. } => {
450 self.validate_statements(body, word_name, builtins, external_words)?;
452 }
453 Statement::Match { arms, span: _ } => {
454 for arm in arms {
456 self.validate_statements(&arm.body, word_name, builtins, external_words)?;
457 }
458 }
459 _ => {} }
461 }
462 Ok(())
463 }
464
465 pub const MAX_VARIANT_FIELDS: usize = 12;
469
470 pub fn generate_constructors(&mut self) -> Result<(), String> {
483 let mut new_words = Vec::new();
484
485 for union_def in &self.unions {
486 for variant in &union_def.variants {
487 let field_count = variant.fields.len();
488
489 if field_count > Self::MAX_VARIANT_FIELDS {
491 return Err(format!(
492 "Variant '{}' in union '{}' has {} fields, but the maximum is {}. \
493 Consider grouping fields into nested union types.",
494 variant.name,
495 union_def.name,
496 field_count,
497 Self::MAX_VARIANT_FIELDS
498 ));
499 }
500
501 let constructor_name = format!("Make-{}", variant.name);
503 let mut input_stack = StackType::RowVar("a".to_string());
504 for field in &variant.fields {
505 let field_type = parse_type_name(&field.type_name);
506 input_stack = input_stack.push(field_type);
507 }
508 let output_stack =
509 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
510 let effect = Effect::new(input_stack, output_stack);
511 let body = vec![
512 Statement::Symbol(variant.name.clone()),
513 Statement::WordCall {
514 name: format!("variant.make-{}", field_count),
515 span: None,
516 },
517 ];
518 new_words.push(WordDef {
519 name: constructor_name,
520 effect: Some(effect),
521 body,
522 source: variant.source.clone(),
523 allowed_lints: vec![],
524 });
525
526 let predicate_name = format!("is-{}?", variant.name);
530 let predicate_input =
531 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
532 let predicate_output = StackType::RowVar("a".to_string()).push(Type::Bool);
533 let predicate_effect = Effect::new(predicate_input, predicate_output);
534 let predicate_body = vec![
535 Statement::WordCall {
536 name: "variant.tag".to_string(),
537 span: None,
538 },
539 Statement::Symbol(variant.name.clone()),
540 Statement::WordCall {
541 name: "symbol.=".to_string(),
542 span: None,
543 },
544 ];
545 new_words.push(WordDef {
546 name: predicate_name,
547 effect: Some(predicate_effect),
548 body: predicate_body,
549 source: variant.source.clone(),
550 allowed_lints: vec![],
551 });
552
553 for (index, field) in variant.fields.iter().enumerate() {
557 let accessor_name = format!("{}-{}", variant.name, field.name);
558 let field_type = parse_type_name(&field.type_name);
559 let accessor_input = StackType::RowVar("a".to_string())
560 .push(Type::Union(union_def.name.clone()));
561 let accessor_output = StackType::RowVar("a".to_string()).push(field_type);
562 let accessor_effect = Effect::new(accessor_input, accessor_output);
563 let accessor_body = vec![
564 Statement::IntLiteral(index as i64),
565 Statement::WordCall {
566 name: "variant.field-at".to_string(),
567 span: None,
568 },
569 ];
570 new_words.push(WordDef {
571 name: accessor_name,
572 effect: Some(accessor_effect),
573 body: accessor_body,
574 source: variant.source.clone(), allowed_lints: vec![],
576 });
577 }
578 }
579 }
580
581 self.words.extend(new_words);
582 Ok(())
583 }
584
585 pub fn fixup_union_types(&mut self) {
594 let union_names: std::collections::HashSet<String> =
596 self.unions.iter().map(|u| u.name.clone()).collect();
597
598 for word in &mut self.words {
600 if let Some(ref mut effect) = word.effect {
601 Self::fixup_stack_type(&mut effect.inputs, &union_names);
602 Self::fixup_stack_type(&mut effect.outputs, &union_names);
603 }
604 }
605 }
606
607 fn fixup_stack_type(stack: &mut StackType, union_names: &std::collections::HashSet<String>) {
609 match stack {
610 StackType::Empty | StackType::RowVar(_) => {}
611 StackType::Cons { rest, top } => {
612 Self::fixup_type(top, union_names);
613 Self::fixup_stack_type(rest, union_names);
614 }
615 }
616 }
617
618 fn fixup_type(ty: &mut Type, union_names: &std::collections::HashSet<String>) {
620 match ty {
621 Type::Var(name) if union_names.contains(name) => {
622 *ty = Type::Union(name.clone());
623 }
624 Type::Quotation(effect) => {
625 Self::fixup_stack_type(&mut effect.inputs, union_names);
626 Self::fixup_stack_type(&mut effect.outputs, union_names);
627 }
628 Type::Closure { effect, captures } => {
629 Self::fixup_stack_type(&mut effect.inputs, union_names);
630 Self::fixup_stack_type(&mut effect.outputs, union_names);
631 for cap in captures {
632 Self::fixup_type(cap, union_names);
633 }
634 }
635 _ => {}
636 }
637 }
638}
639
640fn parse_type_name(name: &str) -> Type {
643 match name {
644 "Int" => Type::Int,
645 "Float" => Type::Float,
646 "Bool" => Type::Bool,
647 "String" => Type::String,
648 "Channel" => Type::Channel,
649 "Socket" => Type::Socket,
650 other => Type::Union(other.to_string()),
651 }
652}
653
654fn v7_renamed_to(name: &str) -> Option<&'static str> {
659 Some(match name {
660 "tcp.listen" => "net.tcp.listen",
661 "tcp.accept" => "net.tcp.accept",
662 "tcp.read" => "net.tcp.read",
663 "tcp.write" => "net.tcp.write",
664 "tcp.close" => "net.tcp.close",
665 "udp.bind" => "net.udp.bind",
666 "udp.send-to" => "net.udp.send-to",
667 "udp.receive-from" => "net.udp.receive-from",
668 "udp.close" => "net.udp.close",
669 "http.get" => "net.http.get",
670 "http.post" => "net.http.post",
671 "http.put" => "net.http.put",
672 "http.delete" => "net.http.delete",
673 "mod" => "i.modulo",
676 _ => return None,
677 })
678}
679
680impl Default for Program {
681 fn default() -> Self {
682 Self::new()
683 }
684}