bynkc/lib.rs
1//! Bynk v0.3 compiler library.
2//!
3//! Compiles `.bynk` commons source into TypeScript modules.
4//!
5//! Pipeline: lex → parse → resolve → check → emit.
6//!
7//! v0.3 introduces multi-file commons and the `uses` mechanism. A "project"
8//! is a directory containing one or more commons; a commons is either a
9//! single `.bynk` file or a directory of `.bynk` files that share a
10//! `commons name` header. See [`compile_project`].
11//!
12//! The single-string entrypoint [`compile`] remains for v0–v0.2 fixtures
13//! and any single-file commons that does not declare `uses` against another
14//! commons.
15
16pub mod cli;
17pub mod test_json;
18
19// The syntax foundation now lives in the `bynk-syntax` leaf crate (slice 1 of
20// the crate-decomposition track). Re-export its modules at the crate root so
21// `bynkc`'s public API and every internal `crate::ast` / `crate::lexer` path is
22// preserved — consumers and the rest of the pipeline see no change.
23pub use bynk_syntax::error::Severity;
24pub use bynk_syntax::{CompileError, ast, diagnostics, error, keywords, lexer, parser, span};
25
26// The semantic-analysis layer moved down into the `bynk-check` crate (slice 3):
27// resolver, checker, the registries, first-party sources, actors, and the
28// captured index/hints/expr_types/locals tables. Re-export its modules at the
29// crate root so `bynkc`'s public API and every internal `crate::checker` /
30// `crate::index` path is preserved — the emitter/project layers above see no
31// change.
32pub use bynk_check::{
33 actors, builtin_names, checker, expr_types, firstparty, hints, index, kernel_methods, locals,
34 requirements, resolver,
35};
36
37// Build orchestration + TS emission moved down into the `bynk-emit` crate
38// (slice 4). Re-export its modules at the crate root so `bynkc`'s public API and
39// every internal `crate::emitter` / `crate::project` path is preserved — the CLI
40// and compile/diagnose glue see no change.
41pub use bynk_emit::{emitter, project};
42
43// The IDE/LSP analysis surface moved down into the `bynk-ide` crate (slice 5):
44// the non-bailing single-file and project diagnostics. Re-export them so
45// `bynkc`'s public API and its index/diagnose integration tests resolve
46// unchanged (the binary itself does not use this surface).
47pub use bynk_ide::{Diagnostic, FileDiagnostics, ProjectDiagnostics, diagnose, diagnose_project};
48
49// The formatter moved down into the `bynk-fmt` leaf (slice 2). Re-export it as
50// `bynkc::fmt` so the `bynkc fmt` command and existing `bynkc::fmt::…` consumers
51// (e.g. the LSP's formatting path) keep resolving unchanged.
52pub use bynk_fmt as fmt;
53
54// The diagnostic renderers moved down into the `bynk-render` crate (slice 6):
55// ariadne human + the short/json line forms over `CompileError`. Re-export them
56// so `bynkc`'s binary, the diagnostic transcripts, and the tests resolve
57// unchanged. The `ProjectFailure` flatteners (below) stay here and delegate.
58pub use bynk_render::{
59 print_errors, print_errors_short, print_project_errors, render_errors, render_errors_plain,
60 render_errors_short, render_project_errors,
61};
62
63pub use firstparty::Platform;
64
65// The Node floor moved to `bynk-emit` (slice 7) so the `bynk` driver can read it
66// without depending on the `bynkc` crate. Re-export it so `bynkc::NODE_MAJOR_FLOOR`
67// and the `cli.rs` doc-links resolve unchanged.
68pub use bynk_emit::{NODE_MAJOR_FLOOR, write_compiled_file, write_output};
69pub use project::{
70 AttributedError, BuildTarget, CompileOptions, CompiledFile, DiscoveredCase, DiscoveredSuite,
71 ImportExt, ProjectFailure, ProjectOutput, ProjectPaths, Roots, TestLocation, compile_project,
72 read_project_paths,
73};
74
75// In-browser track (ADR 0137): strip-only TS→JS, re-exported so the CLI, the API,
76// and tests share one entry point. `strip_project_to_js` moved into `bynk-strip`
77// in slice 3 so the wasm entry can reuse it without depending on `bynkc`.
78pub use bynk_strip::{StripError, strip_project_to_js, strip_types};
79
80/// Compile a single Bynk source string to a TypeScript string.
81///
82/// This entry point parses the input as a self-contained, single-file commons
83/// with no `uses` against other commons. Use [`compile_project`] for
84/// multi-file projects or for any source that declares `uses`.
85///
86/// `filename` is used only for diagnostic rendering.
87pub fn compile(source: &str, filename: &str) -> Result<String, Vec<CompileError>> {
88 compile_with_warnings(source, filename).map(|c| c.ts)
89}
90
91/// v0.89 (ADR 0117): single-file compile that also returns the non-failing
92/// warnings produced on success — what the CLI prints. `compile` is the
93/// warning-discarding convenience over this.
94pub struct Compiled {
95 pub ts: String,
96 pub warnings: Vec<CompileError>,
97}
98
99pub fn compile_with_warnings(source: &str, _filename: &str) -> Result<Compiled, Vec<CompileError>> {
100 let tokens = lexer::tokenize(source).map_err(|e| vec![e])?;
101 let commons = parser::parse(&tokens, source)?;
102 // v0.20a: function types are confined to non-boundary positions — the
103 // same rule the project path applies.
104 let mut boundary_errors = Vec::new();
105 project::check_function_type_boundary_items(&commons.items, &mut boundary_errors);
106 if !boundary_errors.is_empty() {
107 return Err(boundary_errors);
108 }
109 let resolved = resolver::resolve(commons)?;
110 let typed = checker::check(resolved)?;
111 let warnings = typed.warnings.clone();
112 Ok(Compiled {
113 ts: emitter::emit(&typed),
114 warnings,
115 })
116}
117
118/// v0.24 (ADR 0052 rider): render a failed project build with full ariadne
119/// source context per file — the attribution built for the LSP, fixing the
120/// standing gap where project-mode CLI errors were bare lines while
121/// single-file mode had rich rendering. Unattributed (project-level)
122/// errors keep the plain form.
123///
124/// This is the **flattening layer** (ADR 0100): it attributes each
125/// `AttributedError` to its file snapshot and delegates the actual rendering to
126/// [`bynk_render::print_errors`]. The `ProjectFailure → CompileError` flattening
127/// stays here, above `bynk-render`, so there is no `render → emit` edge.
128pub fn print_project_failure(failure: &project::ProjectFailure) {
129 let texts: std::collections::HashMap<&std::path::Path, &str> = failure
130 .snapshots
131 .iter()
132 .map(|(p, t)| (p.as_path(), t.as_str()))
133 .collect();
134 for ae in &failure.errors {
135 match ae
136 .source_path
137 .as_deref()
138 .and_then(|p| texts.get(p).map(|t| (p, *t)))
139 {
140 Some((path, text)) => {
141 let label = path.to_string_lossy().replace('\\', "/");
142 bynk_render::print_errors(std::slice::from_ref(&ae.error), text, &label);
143 }
144 None => {
145 eprintln!("[{}] {}", ae.error.category, ae.error.message);
146 for note in &ae.error.notes {
147 eprintln!(" note: {note}");
148 }
149 }
150 }
151 }
152}
153
154/// v0.89 (ADR 0117): print a successful build's non-failing warnings. A
155/// successful build keeps no per-file snapshots, so warnings render in the
156/// plain `warning[<category>]: <message>` form (with the owning file, when
157/// known) rather than ariadne source context.
158pub fn print_project_warnings(warnings: &[project::AttributedError]) {
159 for w in warnings {
160 let where_ = w
161 .source_path
162 .as_deref()
163 .map(|p| format!("{}: ", p.to_string_lossy().replace('\\', "/")))
164 .unwrap_or_default();
165 eprintln!("{where_}warning[{}]: {}", w.error.category, w.error.message);
166 for note in &w.error.notes {
167 eprintln!(" note: {note}");
168 }
169 }
170}
171
172/// The project-failure analogue of [`bynk_render::print_errors_short`]: each
173/// attributed error is positioned against its file's snapshot; an unattributed
174/// (project-level) error falls back to `<severity>[<category>]: <message>`.
175pub fn print_project_failure_short(failure: &project::ProjectFailure) {
176 for line in project_failure_short_lines(failure) {
177 eprintln!("{line}");
178 }
179}
180
181/// The string form of [`print_project_failure_short`]: one `path:line:col:
182/// severity[category]: message` line per attributed error (an unattributed
183/// project-level error falls back to `severity[category]: message`). Backs both
184/// the printer above and the `bynkc test --format json` compile-error document,
185/// whose `diagnostics` the VS Code `bynkc` problem-matcher re-parses.
186///
187/// The flattening layer (ADR 0100): it delegates the per-error formatting to
188/// [`bynk_render::short_line`] / [`bynk_render::severity_word`].
189pub fn project_failure_short_lines(failure: &project::ProjectFailure) -> Vec<String> {
190 let texts: std::collections::HashMap<&std::path::Path, &str> = failure
191 .snapshots
192 .iter()
193 .map(|(p, t)| (p.as_path(), t.as_str()))
194 .collect();
195 failure
196 .errors
197 .iter()
198 .map(|ae| {
199 match ae
200 .source_path
201 .as_deref()
202 .and_then(|p| texts.get(p).map(|t| (p, *t)))
203 {
204 Some((path, text)) => {
205 let label = path.to_string_lossy().replace('\\', "/");
206 bynk_render::short_line(&label, text, &ae.error)
207 }
208 None => format!(
209 "{}[{}]: {}",
210 bynk_render::severity_word(&ae.error),
211 ae.error.category,
212 ae.error.message
213 ),
214 }
215 })
216 .collect()
217}