1#![deny(warnings)]
2
3pub(crate) mod ast;
4pub(crate) mod cfg;
5pub(crate) mod constant;
6pub mod diag;
7pub(crate) mod elab;
8pub(crate) mod emit;
9pub(crate) mod flow;
10pub(crate) mod lex;
11pub(crate) mod lower;
12pub(crate) mod origin;
13pub(crate) mod parse;
14pub(crate) mod sig;
15pub mod source;
16pub(crate) mod sym;
17
18use std::{
19 error,
20 fmt::{self, Display},
21 io::{self, Write},
22 mem,
23 ops::ControlFlow,
24 path::Path,
25};
26
27use crate::{ast::visit, lex::Comment};
28
29use self::{
30 ast::visit::{Node, NodeKind},
31 elab::Elaborater,
32 emit::Emitter,
33 lex::Lexer,
34 lower::Lowerer,
35 parse::Parser,
36 source::{Diags, File},
37};
38
39pub use ast::{Context, visit::Token};
40
41use dolang_util::intern::{self, BinTable};
42
43use ast::Res;
44
45const STD_PRELUDE: &[&str] = &[
46 "array", "bin", "bool", "dbg", "dict", "float", "func", "int", "module", "range", "record",
47 "set", "str", "sym", "tuple", "type",
48];
49
50#[derive(Debug)]
51enum ErrorInfo<B> {
52 Fail,
53 Io(io::Error),
54 Break(B),
55}
56
57#[derive(Debug)]
59#[non_exhaustive]
60pub enum ErrorKind {
61 Fail,
63 Io,
65 Break,
67}
68
69#[derive(Debug)]
71pub struct Error<B>(ErrorInfo<B>);
72
73impl<B> Error<B> {
74 pub fn kind(&self) -> ErrorKind {
76 match &self.0 {
77 ErrorInfo::Fail => ErrorKind::Fail,
78 ErrorInfo::Io(_) => ErrorKind::Io,
79 ErrorInfo::Break(_) => ErrorKind::Break,
80 }
81 }
82
83 pub fn as_io(&self) -> Option<&io::Error> {
85 match &self.0 {
86 ErrorInfo::Io(error) => Some(error),
87 _ => None,
88 }
89 }
90
91 pub fn as_break(&self) -> Option<&B> {
93 match &self.0 {
94 ErrorInfo::Break(b) => Some(b),
95 _ => None,
96 }
97 }
98}
99
100impl<B> From<io::Error> for Error<B> {
101 fn from(value: io::Error) -> Self {
102 Error(ErrorInfo::Io(value))
103 }
104}
105
106impl<B: Display> Display for Error<B> {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match &self.0 {
109 ErrorInfo::Fail => "compilation failed".fmt(f),
110 ErrorInfo::Io(e) => e.fmt(f),
111 ErrorInfo::Break(b) => b.fmt(f),
112 }
113 }
114}
115
116impl<B: error::Error> error::Error for Error<B> {
117 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
118 match &self.0 {
119 ErrorInfo::Fail => None,
120 ErrorInfo::Io(error) => Some(error),
121 ErrorInfo::Break(b) => b.source(),
122 }
123 }
124}
125
126impl<B> From<parse::Error> for Error<B> {
127 fn from(_: parse::Error) -> Self {
128 Self(ErrorInfo::Fail)
129 }
130}
131
132impl<B> From<elab::Error> for Error<B> {
133 fn from(_: elab::Error) -> Self {
134 Self(ErrorInfo::Fail)
135 }
136}
137
138impl<B> From<lower::Error> for Error<B> {
139 fn from(_: lower::Error) -> Self {
140 Self(ErrorInfo::Fail)
141 }
142}
143
144pub trait EmitDiag {
146 type Break;
148
149 fn emit(&mut self, diag: diag::Diag) -> ControlFlow<Self::Break>;
151}
152
153impl<F, B> EmitDiag for F
155where
156 F: FnMut(diag::Diag) -> ControlFlow<B>,
157{
158 type Break = B;
159
160 fn emit(&mut self, diag: diag::Diag) -> ControlFlow<Self::Break> {
161 self(diag)
162 }
163}
164
165pub use diag::Origin;
167
168pub trait EmitToken {
170 type Break;
172
173 fn emit(
175 &mut self,
176 token: Token,
177 span: diag::Span,
178 origin: Option<diag::Origin>,
179 context: Context,
180 ) -> ControlFlow<Self::Break>;
181}
182
183impl<F, B> EmitToken for F
185where
186 F: FnMut(Token, diag::Span, Option<diag::Origin>, Context) -> ControlFlow<B>,
187{
188 type Break = B;
189
190 fn emit(
191 &mut self,
192 token: Token,
193 span: diag::Span,
194 origin: Option<diag::Origin>,
195 context: Context,
196 ) -> ControlFlow<Self::Break> {
197 self(token, span, origin, context)
198 }
199}
200
201struct VisitAdapter<'a, 'e, B> {
202 file: &'a File<'a>,
203 origintab: &'a origin::Table,
204 emit: &'e mut dyn EmitToken<Break = B>,
205}
206
207struct CallAdapter<'a, 'b, 'e, B> {
208 parent: &'b mut VisitAdapter<'a, 'e, B>,
209 seen_arg0: bool,
210}
211
212struct CallIdentAdapter<'a, 'b, 'e, B>(&'b mut VisitAdapter<'a, 'e, B>);
213
214impl<'a, 'b, 'e, B> visit::Visit for CallIdentAdapter<'a, 'b, 'e, B> {
215 type Break = B;
216
217 fn node<T: Node + ?Sized>(&mut self, node: &T) -> ControlFlow<Self::Break> {
218 node.accept(self.0)
219 }
220
221 fn token(
222 &mut self,
223 _token: Token,
224 span: source::Span,
225 origin: Option<origin::Id>,
226 ) -> ControlFlow<Self::Break> {
227 self.0
228 .emit_token(Token::Variable, span, origin, Context::Call)
229 }
230}
231
232struct MethodAdapter<'a, 'b, 'e, B>(&'b mut VisitAdapter<'a, 'e, B>);
233
234impl<'a, 'b, 'e, B> visit::Visit for MethodAdapter<'a, 'b, 'e, B> {
235 type Break = B;
236
237 fn node<T: Node + ?Sized>(&mut self, node: &T) -> ControlFlow<Self::Break> {
238 node.accept(self.0)
239 }
240
241 fn token(
242 &mut self,
243 token: Token,
244 span: source::Span,
245 origin: Option<origin::Id>,
246 ) -> ControlFlow<Self::Break> {
247 let context = match token {
248 Token::Field => Context::Call,
249 _ => Context::None,
250 };
251 self.0.emit_token(token, span, origin, context)
252 }
253}
254
255impl<'a, 'b, 'e, B> visit::Visit for CallAdapter<'a, 'b, 'e, B> {
256 type Break = B;
257
258 fn node<T: Node + ?Sized>(&mut self, node: &T) -> ControlFlow<Self::Break> {
259 if self.seen_arg0 {
260 node.accept(self.parent)
261 } else {
262 self.seen_arg0 = true;
263 match node.kind() {
264 NodeKind::Index | NodeKind::Call => node.accept(&mut CallAdapter {
265 parent: self.parent,
266 seen_arg0: false,
267 }),
268 NodeKind::Ident => node.accept(&mut CallIdentAdapter(self.parent)),
269 NodeKind::Field => node.accept(&mut MethodAdapter(self.parent)),
270 _ => node.accept(self.parent),
271 }
272 }
273 }
274
275 fn token(
276 &mut self,
277 token: Token,
278 span: source::Span,
279 origin: Option<origin::Id>,
280 ) -> ControlFlow<Self::Break> {
281 self.parent.emit_token(token, span, origin, Context::None)
282 }
283}
284
285fn convert_span(file: &File, span: source::Span) -> diag::Span {
286 let coords = file.coord_span(span);
287 diag::Span::new(
288 diag::Pos::new(span.start as usize, coords.start.line, coords.start.column),
289 diag::Pos::new(span.end as usize, coords.end.line, coords.end.column),
290 )
291}
292
293fn convert_origin(file: &File, internal: &origin::Origin) -> Option<diag::Origin> {
294 match internal {
295 origin::Origin::ImportItem { module, item, name } => Some(diag::Origin::ImportItem {
296 module: convert_span(file, *module),
297 item: convert_span(file, *item),
298 name: convert_span(file, *name),
299 }),
300 origin::Origin::ImportModule { module, name } => Some(diag::Origin::ImportModule {
301 module: convert_span(file, *module),
302 name: convert_span(file, *name),
303 }),
304 origin::Origin::PreludeModule { module, name } => Some(diag::Origin::PreludeModule {
305 module: module.clone(),
306 name: name.clone(),
307 }),
308 origin::Origin::PreludeItem { module, item, name } => Some(diag::Origin::PreludeItem {
309 module: module.clone(),
310 item: item.clone(),
311 name: name.clone(),
312 }),
313 origin::Origin::Class { span } => Some(diag::Origin::Class {
314 span: convert_span(file, *span),
315 }),
316 origin::Origin::Def { span, class } => Some(diag::Origin::Def {
317 span: convert_span(file, *span),
318 class: class.map(|s| convert_span(file, s)),
319 }),
320 origin::Origin::Bind { span, class } => Some(diag::Origin::Bind {
321 span: convert_span(file, *span),
322 class: class.map(|s| convert_span(file, s)),
323 }),
324 origin::Origin::Param { span } => Some(diag::Origin::Param {
325 span: convert_span(file, *span),
326 }),
327 origin::Origin::SelfParam { span } => Some(diag::Origin::SelfParam {
328 span: convert_span(file, *span),
329 }),
330 origin::Origin::Synthetic | origin::Origin::Repl => None,
331 }
332}
333
334impl<'a, 'e, B> VisitAdapter<'a, 'e, B> {
335 fn emit_token(
336 &mut self,
337 token: Token,
338 span: source::Span,
339 origin: Option<origin::Id>,
340 context: Context,
341 ) -> ControlFlow<B> {
342 let diag_span = convert_span(self.file, span);
343 let diag_origin = origin.and_then(|id| convert_origin(self.file, &self.origintab[id]));
344 self.emit.emit(token, diag_span, diag_origin, context)
345 }
346}
347
348impl<'a, 'e, B> visit::Visit for VisitAdapter<'a, 'e, B> {
349 type Break = B;
350
351 fn node<T: Node + ?Sized>(&mut self, node: &T) -> ControlFlow<Self::Break> {
352 if matches!(node.kind(), NodeKind::Call) {
353 node.accept(&mut CallAdapter {
354 parent: self,
355 seen_arg0: false,
356 })
357 } else {
358 node.accept(self)
359 }
360 }
361
362 fn token(
363 &mut self,
364 token: Token,
365 span: source::Span,
366 origin: Option<origin::Id>,
367 ) -> ControlFlow<Self::Break> {
368 self.emit_token(token, span, origin, Context::None)
369 }
370}
371
372#[non_exhaustive]
374#[derive(Clone, PartialEq, Eq)]
375pub enum Mode<'a> {
376 Script,
378 Module { name: &'a str },
380 Repl,
385}
386
387#[derive(Debug)]
388pub(crate) struct PreludeItem {
389 item: String,
390 bind: String,
391 res: Option<Res>,
392}
393
394#[derive(Debug)]
395pub(crate) enum PreludeImport {
396 Items {
397 module: String,
398 items: Vec<PreludeItem>,
399 },
400 ModuleAsIs {
401 module: String,
402 bind: String,
403 res: Option<Res>,
404 insert: bool,
405 },
406 ModuleRenamed {
407 module: String,
408 bind: String,
409 res: Option<Res>,
410 },
411}
412
413pub struct Prelude<'a, 'b> {
415 compiler: &'b mut Compiler<'a>,
416}
417
418impl<'a, 'b> Prelude<'a, 'b> {
419 fn module_name_first(module: &str) -> &str {
420 if let Some((first, _)) = module.split_once(".") {
421 first
422 } else {
423 module
424 }
425 }
426
427 pub fn clear(self) -> Self {
429 self.compiler.prelude.clear();
430 self
431 }
432
433 pub fn import_module(self, module: impl Into<String>) -> Self {
438 let module = module.into();
439 let bind = Self::module_name_first(&module).to_owned();
440
441 self.compiler.prelude.push(PreludeImport::ModuleAsIs {
442 module,
443 bind,
444 res: None,
445 insert: false,
446 });
447 self
448 }
449
450 pub fn import_module_with_name(
455 self,
456 module: impl Into<String>,
457 name: impl Into<String>,
458 ) -> Self {
459 self.compiler.prelude.push(PreludeImport::ModuleRenamed {
460 module: module.into(),
461 bind: name.into(),
462 res: None,
463 });
464 self
465 }
466
467 pub fn import_items(self, module: impl Into<String>) -> Items<'a, 'b> {
469 self.compiler.prelude.push(PreludeImport::Items {
470 module: module.into(),
471 items: Vec::new(),
472 });
473 Items {
474 compiler: self.compiler,
475 }
476 }
477}
478
479#[must_use]
481pub struct Items<'a, 'b> {
482 compiler: &'b mut Compiler<'a>,
483}
484
485impl<'a, 'b> Items<'a, 'b> {
486 pub fn item(self, item: impl Into<String>) -> Self {
492 let item = item.into();
493 match self.compiler.prelude.last_mut().unwrap() {
494 PreludeImport::Items { items, .. } => items.push(PreludeItem {
495 item: item.clone(),
496 bind: item,
497 res: None,
498 }),
499 _ => unreachable!(),
500 };
501 self
502 }
503
504 pub fn items(mut self, items: impl IntoIterator<Item = impl Into<String>>) -> Self {
511 for item in items.into_iter() {
512 self = self.item(item);
513 }
514 self
515 }
516
517 pub fn item_with_name(self, item: impl Into<String>, name: impl Into<String>) -> Self {
523 match self.compiler.prelude.last_mut().unwrap() {
524 PreludeImport::Items { items, .. } => items.push(PreludeItem {
525 item: item.into(),
526 bind: name.into(),
527 res: None,
528 }),
529 _ => unreachable!(),
530 };
531 self
532 }
533
534 pub fn commit(self) -> Prelude<'a, 'b> {
538 Prelude {
539 compiler: self.compiler,
540 }
541 }
542}
543
544pub struct Compiler<'a> {
546 file: File<'a>,
547 origintab: origin::Table,
548 symtab: sym::Table,
549 bintab: BinTable,
550 consttab: constant::Table,
551 packtab: sig::PackTable,
552 unpacktab: sig::UnpackTable,
553 mode: Mode<'a>,
554 prelude: Vec<PreludeImport>,
555}
556
557impl<'a> Compiler<'a> {
558 pub fn new(path: &'a Path, content: &'a [u8]) -> Self {
564 let mut this = Self {
565 file: File::new(path, content),
566 origintab: Default::default(),
567 symtab: sym::Table::new(),
568 bintab: BinTable::new(),
569 consttab: constant::Table::new(),
570 packtab: sig::PackTable::new(),
571 unpacktab: sig::UnpackTable::new(),
572 mode: Mode::Script,
573 prelude: Default::default(),
574 };
575 this.prelude()
576 .import_module("std")
577 .import_module("strand")
578 .import_items("std")
579 .items(STD_PRELUDE.iter().copied())
580 .commit();
581 this
582 }
583
584 pub fn mode(&mut self, mode: Mode<'a>) -> &mut Self {
588 self.mode = mode;
589 self
590 }
591
592 pub fn prelude(&mut self) -> Prelude<'a, '_> {
597 Prelude { compiler: self }
598 }
599
600 fn feed_comments<B>(
601 &self,
602 tokens: &mut dyn EmitToken<Break = B>,
603 comments: impl IntoIterator<Item = source::Span>,
604 ) -> Result<(), Error<B>> {
605 for comment in comments.into_iter() {
606 let content = self.file.str(comment);
607 let slice = content.trim_end();
608 if let ControlFlow::Break(b) = tokens.emit(
609 Token::Comment,
610 convert_span(
611 &self.file,
612 source::Span {
613 start: comment.start,
614 end: comment.start + slice.len() as u32,
615 },
616 ),
617 None,
618 ast::Context::None,
619 ) {
620 return Err(Error(ErrorInfo::Break(b)));
621 }
622 }
623 Ok(())
624 }
625
626 fn run<D: EmitDiag>(
627 mut self,
628 diags: &mut D,
629 tokens: Option<&mut dyn EmitToken<Break = D::Break>>,
630 write: Option<&mut dyn Write>,
631 ignore_errors: bool,
632 ) -> Result<(), Error<D::Break>> {
633 let mut prelude = mem::take(&mut self.prelude);
634 let mut ds = Diags::new();
635 let mut comments = vec![];
636 let comment = if tokens.is_some() {
637 Some((&mut |span| comments.push(span)) as &mut dyn Comment)
638 } else {
639 None
640 };
641 let mut parser = self.parser(&ds, comment);
642 let ast = parser.parse(ignore_errors);
643 self.drain_diags(&mut ds, diags)?;
644 let mut ast = ast?;
645 #[cfg(feature = "debug")]
646 if let Err(e) = self.export_ast_dot(&ast, false) {
647 eprintln!("AST DOT export failed: {e}")
648 }
649 let mut elab = self.elaborater(&ds);
650 let res = elab.elaborate(&mut ast, &mut prelude, ignore_errors);
651 #[cfg(feature = "debug")]
652 if let Err(e) = self.export_ast_dot(&ast, true) {
653 eprintln!("Resolved AST DOT export failed: {e}")
654 }
655 self.drain_diags(&mut ds, diags)?;
656 res?;
657 self.prelude = prelude;
658 if let Some(tokens) = tokens {
659 if let ControlFlow::Break(e) = ast.accept(&mut VisitAdapter {
660 file: &self.file,
661 origintab: &self.origintab,
662 emit: tokens,
663 }) {
664 return Err(Error(ErrorInfo::Break(e)));
665 }
666 self.feed_comments(tokens, comments)?;
667 }
668 if let Some(write) = write {
669 let mut lowerer = self.lowerer();
670 let graph = lowerer.run(&ast)?;
671 #[cfg(feature = "debug")]
672 {
673 if let Ok(output) = std::env::var("DO_EXPORT_DOT")
675 && let Err(e) = self.export_cfg_dot(&graph, output)
676 {
677 eprintln!("dot export failed: {e}");
678 }
679 }
680 let mut emitter = self.emitter(&graph);
681 Ok(emitter.emit(write)?)
682 } else {
683 Ok(())
684 }
685 }
686
687 pub fn analyze<D: EmitDiag, T: EmitToken<Break = D::Break>>(
704 self,
705 diags: &mut D,
706 tokens: &mut T,
707 ) -> Result<(), Error<D::Break>> {
708 self.run(diags, Some(tokens), None, true)
709 }
710
711 pub fn compile<E: EmitDiag>(
722 self,
723 write: &mut impl Write,
724 diags: &mut E,
725 ) -> Result<(), Error<E::Break>> {
726 self.run(diags, None, Some(write), false)
727 }
728
729 fn drain_diags<E: EmitDiag>(
730 &self,
731 diags: &mut Diags,
732 emit: &mut E,
733 ) -> Result<(), Error<E::Break>> {
734 for diag in diags.drain() {
735 if let ControlFlow::Break(b) = emit.emit(diag.resolve(self)) {
736 return Err(Error(ErrorInfo::Break(b)));
737 }
738 }
739 Ok(())
740 }
741
742 fn parser<'b>(
743 &'b mut self,
744 diags: &'b Diags,
745 comment: Option<&'b mut dyn Comment>,
746 ) -> Parser<'b> {
747 Parser::new(Lexer::new(&self.file, diags, comment), &self.file, diags)
748 }
749
750 fn elaborater<'b>(&'b mut self, diags: &'b Diags) -> Elaborater<'b> {
751 Elaborater::new(
752 self.mode.clone(),
753 &self.file,
754 &mut self.bintab,
755 &mut self.symtab,
756 &mut self.origintab,
757 diags,
758 )
759 }
760
761 fn lowerer(&mut self) -> Lowerer<'_> {
762 Lowerer {
763 mode: self.mode.clone(),
764 file: &self.file,
765 symtab: &mut self.symtab,
766 bintab: &mut self.bintab,
767 consttab: &mut self.consttab,
768 packtab: &mut self.packtab,
769 unpacktab: &mut self.unpacktab,
770 origintab: &self.origintab,
771 prelude: &self.prelude,
772 sentinel_const: None,
773 }
774 }
775
776 fn emitter<'b>(&'a self, graph: &'b cfg::Graph) -> Emitter<'b>
777 where
778 'a: 'b,
779 {
780 Emitter {
781 file: &self.file,
782 graph,
783 bintab: &self.bintab,
784 symtab: &self.symtab,
785 consttab: &self.consttab,
786 packtab: &self.packtab,
787 unpacktab: &self.unpacktab,
788 debugbintab: Default::default(),
789 mode: self.mode.clone(),
790 }
791 }
792}
793
794#[cfg(feature = "debug")]
795impl Compiler<'_> {
796 fn ast_to_dot<N: Node + ?Sized>(&self, ast: &N, writer: &mut impl Write) -> io::Result<()> {
798 use crate::ast::{dot::DotVisitor, visit::Visit};
799 use dot_writer::DotWriter;
800
801 let mut writer = DotWriter::from(writer);
802 writer.set_pretty_print(true);
803 let mut digraph = writer.digraph();
804 let mut visitor = DotVisitor::new(&mut digraph, self);
805 match visitor.node(ast) {
806 ControlFlow::Continue(()) => Ok(()),
807 ControlFlow::Break(e) => Err(e),
808 }
809 }
810
811 fn export_ast_dot<N: Node + ?Sized>(&self, ast: &N, res: bool) -> io::Result<()> {
814 use std::{
815 fs,
816 path::{self, Component},
817 };
818
819 if let Ok(output) = std::env::var("DO_EXPORT_DOT") {
820 let src = path::absolute(self.file.path())?;
821 let cwd = std::env::current_dir()?;
822 let rel = src.strip_prefix(&cwd).unwrap_or(&src);
823 let comps: Vec<_> = rel
824 .components()
825 .filter_map(|c| {
826 if let Component::Normal(c) = c {
827 Some(c.to_string_lossy())
828 } else {
829 None
830 }
831 })
832 .collect();
833 let name = comps.join("_");
834 fs::create_dir_all(&output)?;
835 let out = std::path::Path::new(&output)
836 .join(&name)
837 .with_extension(if res { "res.dot" } else { "ast.dot" });
838 let mut file = fs::File::create(&out)?;
839
840 self.ast_to_dot(ast, &mut file)?;
841 eprintln!("AST DOT exported to: {}", out.display());
842 }
843
844 Ok(())
845 }
846
847 fn export_cfg_dot(&mut self, graph: &cfg::Graph, output: String) -> Result<(), io::Error> {
848 use std::{
849 fs,
850 path::{self, Component},
851 };
852 let src = path::absolute(self.file.path())?;
853 let cwd = std::env::current_dir()?;
854 let rel = src.strip_prefix(cwd).unwrap_or(&src);
855 let comps: Vec<_> = rel
856 .components()
857 .filter_map(|c| {
858 if let Component::Normal(c) = c {
859 Some(c.to_string_lossy())
860 } else {
861 None
862 }
863 })
864 .collect();
865 let name = comps.join("_");
866 fs::create_dir_all(&output)?;
867 let out = Path::new(&output).join(&name).with_extension("cfg.dot");
868 let mut file = fs::File::create(&out)?;
869 graph.dot(self, &mut file)
870 }
871}