Skip to main content

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}