use std::process::ExitCode;
const USAGE: &str = "\
frazaro - a compiler from sentences to spreadsheets
usage:
frazaro compile <program.vla> [--prelude <prelude.vla>]
the VBA of a VLA program, to stdout (PORT.5)
frazaro load <phrasebook.vla> [--prelude <prelude.vla>] [--allow-raw]
load a phrasebook, its proofs run as the add-in
runs them, and report what it holds, the line
EnglishVocabStats prints (PORT.6, slice 6c);
--allow-raw is the consent a (raw ...) form needs
frazaro prove <phrasebook.vla|folder> [--prelude <prelude.vla>] [--allow-raw]
the treaty's oracle 3: every proof run, each
failure printed, then PASS n/n or FAIL k/n (k of
n passed), exit 0 or 1 (PORT.6, slice 6d); the
prelude is prelude.vla beside the file or in its
parent folder, else the one inside frazaro, unless
--prelude names one; an engine proof file exits 3
(not attempted, PORT.9); a folder holding
distro.vla is a distro (KERNEL.2), proved whole:
its base phrasebook alone, then its overlay and
each dialect over the base, as the doors load
them, one line a book, a library listed and not
scored, the manifest's prelude unless --prelude
names one, and the total last
frazaro translate-vla <program.txt> [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] [--allow-raw]
the treaty's oracle 1: the program's VLA, the text
EnglishToVla writes, to stdout (PORT.6, slice 6e);
each phrasebook loads in order, its proofs run
frazaro translate-vba <program.txt> [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] [--allow-raw]
oracle 1a: that VLA compiled, the VBA the add-in
would write (EnglishToVba)
frazaro build <program.txt> [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] --out <file.xlsx> [--into <model.xlsx>] [--replace] [--allow-raw]
the writer (PORT.7): the program translated as
translate-vla translates it, then written as a
workbook: the Frazaro sheet holds the sentences in
column B with OK beside each in column C, as the
add-in's Check marks them (slice 7a), and the sheets
the sentences describe hold what a sheet can hold
with nothing running - values and formulas into
cells, on the sheet the program names (slice 7b); a
sentence that needs the add-in's Run is refused by
name, and a file already at --out is refused unless
--replace is given; the workbook carries its build
stamp, the defined name Frazaro.Build (slice 7c);
with --into, the sheets are added to that workbook,
whose own parts are copied byte for byte and never
written to by a sentence (slice 7d)
frazaro rebuild <file.xlsx> [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] [--allow-raw]
the sentences read back out of a built workbook's
Frazaro sheet, its stamp checked against them and
the files given, the build done again and compared
whole: one line, yes (exit 0) or no with why (exit
1); a file that is not a build is refused (slice 7c)
frazaro reflect <file.xlsx|.ods> [--counts] [--functions] [--cone <Sheet!A1> ...]
the reader (PORT.8, slices 8a and 8b; an
OpenDocument .ods file reads the same from slice
8e, its formulas spelled as the formula bar shows
them): the workbook's relations to stdout in a
fixed order,
one form a line - every sheet with its state, every
name, every Table, then sheet by sheet each cell's
value as the file holds it, each formula's text as
the formula bar shows it, and what the formula
refers to, one row per distinct reference; with
--counts, counts and times alone, one line a sheet
by position and one for the workbook, the distinct
formulas in R1C1 and the INDIRECT and OFFSET calls
among them, so that nothing confidential leaves the
machine; with --cone, a cell's cone sized through
names and Tables and across sheets, counts alone,
and where it is blind; with --functions, one row
per function the formulas call with the number of
cells calling it, most called first, counts alone
(KERNEL.6's instrument); a file that is not a
workbook, a part with a DOCTYPE, or a shape this
version does not read is refused by name
frazaro diff <old.xlsx|.ods> <new.xlsx|.ods> [--counts]
the difference between two workbooks (PORT.8, slice
8c), read as reflect reads them: the sheets in one
file alone, then the names and Tables that differ,
then cell by cell on the matched sheets what
changed, old and new side by side - a value, a
formula with its cached value, or blank - one form
a line in a fixed order, nothing when the two hold
the same; with --counts, the counts and times alone
frazaro audit <file.xlsx|.ods> [--counts]
the audit list (PORT.8, slice 8d): where a
workbook's risks are, read from the file - a
constant typed over a column of formulas, a formula
inconsistent with its neighbours, a name nothing
refers to, a reference to an empty cell, a hidden
sheet, a link to another workbook - one finding a
line in a fixed order, nothing when there is
nothing to report; with --counts, the six counts
and the times alone
frazaro calc <file.xlsx|.ods> [--counts | --shapes]
recalculation (KERNEL.7): the workbook read as
reflect reads it into the grid, every formula
computed in dependency order under the declared
subset, and one row per formula cell - the sheet,
the cell, the formula, the computed value, the
value the host saved, and agree, differ or
unchecked - sheet by sheet in row order, every
cycle named first by its cells; a function this
version does not compute shows not computed here
with its name, and a cell that reads it inherits
the label; with --counts, the counts and times
alone; with --shapes, nothing computed and the
plan an engine's machine makes of the grid printed,
its census (KERNEL.30): a plan row of totals, a
readers row a sheet and a shape row a formula
shape - its cells, the levels it spans, its runs
and the path each of its reads takes; exit 0
whenever the calculation or the plan ran
the view record (KERNEL.4): the program translated
and built as build builds it, into the sheet model
alone with nothing written, and one window of one
sheet printed as the lines a viewport draws from -
every sheet, the window, the sheet's extent and
gridlines, the window's columns and formats, then
cell by cell in row order the value or the formula
as reflect spells it, each formula followed by its
computed value (KERNEL.7) or not computed here with
the function named, the cell's format and the row
of the sentence that wrote it; without --window,
the sheet's whole extent; a sheet the program does
not make, or a window that is not a rectangle of
cells, is refused by name
frazaro version the version of the core this door is built on
frazaro help this text
Without --prelude a command uses the prelude inside frazaro, and without
--phrasebook its english.vla: the repository's scripts/prelude.vla and
scripts/polyglotta/english.vla, byte for byte, so that an installed frazaro
needs no file beside it. --phrasebook names the whole list, in order, as
the add-in loads them, english.vla first; `--phrasebook english.vla` with
no such file beside you is the one inside frazaro.
Not in this version (docs/HORIZON.md, section 12, slice by slice):
check, run, ask
";
fn read_text(path: &str) -> Result<String, String> {
let bytes = std::fs::read(path).map_err(|e| format!("cannot read {path}: {e}"))?;
let bytes = bytes.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(&bytes);
String::from_utf8(bytes.to_vec()).map_err(|_| format!("{path} is not valid UTF-8"))
}
fn refuse_missing(id: &str, path: &str) -> ExitCode {
eprintln!("{}", frazaro_core::messages::raise(id, &[("path", path)]));
ExitCode::from(1)
}
fn is_file(path: &str) -> bool {
std::path::Path::new(path).is_file()
}
fn option_after<'a>(args: &'a [String], name: &str) -> Option<&'a str> {
args.iter()
.position(|a| a == name)
.and_then(|i| args.get(i + 1))
.map(String::as_str)
}
const BUILT_IN_PRELUDE: &str = include_str!("../data/prelude.vla");
const BUILT_IN_ENGLISH: &str = include_str!("../data/english.vla");
const BUILT_IN_ENGLISH_NAME: &str = "english.vla (inside frazaro)";
fn prelude_of(args: &[String]) -> Result<String, ExitCode> {
match option_after(args, "--prelude") {
Some(path) => {
if !is_file(path) {
return Err(refuse_missing("vla-file-not-found", path));
}
read_text(path).map_err(|e| {
eprintln!("frazaro: {e}");
ExitCode::from(2)
})
}
None => Ok(BUILT_IN_PRELUDE.to_string()),
}
}
fn is_phrasebook_text(text: &str) -> bool {
text.lines().any(|l| {
if l.starts_with("(test-success") || l.starts_with("(test-fail") {
return true;
}
let Some(rest) = l.strip_prefix('(') else {
return false;
};
let head: String = rest
.chars()
.take_while(|c| !c.is_whitespace() && *c != '(' && *c != ')')
.collect();
head.ends_with("-vla") || head.ends_with("-vla-override")
})
}
fn prelude_beside(file: &str) -> Option<String> {
let dir = std::path::Path::new(file)
.parent()
.filter(|p| !p.as_os_str().is_empty())
.map(std::path::Path::to_path_buf)
.unwrap_or_else(|| std::path::PathBuf::from("."));
for candidate in [dir.join("prelude.vla"), dir.join("..").join("prelude.vla")] {
if candidate.is_file() {
return Some(candidate.to_string_lossy().into_owned());
}
}
None
}
fn compile(args: &[String]) -> ExitCode {
let Some(program) = args.first() else {
eprintln!("usage: frazaro compile <program.vla> [--prelude <prelude.vla>]");
return ExitCode::from(2);
};
if !is_file(program) {
return refuse_missing("vla-source-not-found", program);
}
let prelude = match prelude_of(args) {
Ok(p) => p,
Err(code) => return code,
};
let source = match read_text(program) {
Ok(s) => s,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
match frazaro_core::emit::compile(&source, &prelude) {
Ok(vba) => {
use std::io::Write;
let mut out = std::io::stdout().lock();
if out
.write_all(vba.as_bytes())
.and_then(|_| out.flush())
.is_err()
{
return ExitCode::from(2);
}
ExitCode::SUCCESS
}
Err(refusal) => {
eprintln!("{refusal}");
ExitCode::from(1)
}
}
}
fn load(args: &[String]) -> ExitCode {
let Some(book) = args.first() else {
eprintln!("usage: frazaro load <phrasebook.vla> [--prelude <prelude.vla>] [--allow-raw]");
return ExitCode::from(2);
};
let allow_raw = args.iter().any(|a| a == "--allow-raw");
if !is_file(book) {
return refuse_missing("english-vocab-file-not-found", book);
}
let prelude = match prelude_of(args) {
Ok(p) => p,
Err(code) => return code,
};
let text = match read_text(book) {
Ok(t) => t,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
if let Err(refusal) = frazaro_core::api::vocab_gate(&text, book, allow_raw) {
eprintln!("{refusal}");
return ExitCode::from(1);
}
let mut grammar = frazaro_core::english::Grammar::new(&prelude);
match grammar.load_vocabulary_text(&text, book) {
Ok(_rules) => {
println!("{}", grammar.vocab_stats());
let warnings = grammar.lint_warnings();
if !warnings.is_empty() {
print!("{}", grammar.lint_report());
}
ExitCode::SUCCESS
}
Err(refusal) => {
eprintln!("{refusal}");
ExitCode::from(1)
}
}
}
fn prove_over(
prelude: &str,
earlier: &[(&str, &str)],
book: (&str, &str),
) -> Result<(u64, u64), frazaro_core::messages::Refusal> {
let mut grammar = frazaro_core::english::Grammar::new(prelude);
for (name, text) in earlier {
grammar.load_vocabulary_text(text, name)?;
}
grammar.set_proof_mode(frazaro_core::english::grammar::ProofMode::Collect);
let (t0, f0) = grammar.proofs_run();
let before = grammar.proof_failures().len();
grammar.load_vocabulary_text(book.1, book.0)?;
let (t1, f1) = grammar.proofs_run();
let n = (t1 + f1) - (t0 + f0);
let failures = &grammar.proof_failures()[before..];
for f in failures {
println!("{}", f.refusal.text);
println!();
}
Ok((n, n - failures.len() as u64))
}
fn prove_distro(folder: &str, args: &[String], allow_raw: bool) -> ExitCode {
use frazaro_core::distro::Book;
let dir = std::path::Path::new(folder);
let manifest = dir.join("distro.vla");
let label = manifest.to_string_lossy().into_owned();
if !manifest.is_file() {
let edition = dir
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| folder.to_string());
eprintln!(
"{}",
frazaro_core::messages::raise(
"distro-manifest-missing",
&[("edition", &edition), ("path", &label)]
)
);
return ExitCode::from(1);
}
let text = match read_text(&label) {
Ok(t) => t,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
let distro = match frazaro_core::distro::parse(&text, &label) {
Ok(d) => d,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let prelude_path = match option_after(args, "--prelude") {
Some(p) => p.to_string(),
None => dir.join(&distro.prelude).to_string_lossy().into_owned(),
};
if !is_file(&prelude_path) {
return refuse_missing("vla-file-not-found", &prelude_path);
}
let prelude = match read_text(&prelude_path) {
Ok(p) => p,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
let read_book = |b: &Book| -> Result<String, ExitCode> {
let path = dir.join(&b.path).to_string_lossy().into_owned();
if !is_file(&path) {
return Err(refuse_missing("english-vocab-file-not-found", &path));
}
let t = read_text(&path).map_err(|e| {
eprintln!("frazaro: {e}");
ExitCode::from(2)
})?;
if let Err(refusal) = frazaro_core::api::vocab_gate(&t, &path, allow_raw) {
eprintln!("{refusal}");
return Err(ExitCode::from(1));
}
Ok(t)
};
let base_text = match read_book(&distro.base) {
Ok(t) => t,
Err(code) => return code,
};
let base_name = distro.base.name.as_str();
let (mut total, mut passed) = (0u64, 0u64);
let mut line =
|what: String, result: Result<(u64, u64), frazaro_core::messages::Refusal>| match result {
Ok((n, k)) => {
total += n;
passed += k;
if n == k {
println!("{what}: PASS {n}/{n}");
} else {
println!("{what}: FAIL {k}/{n}");
}
true
}
Err(refusal) => {
eprintln!("{refusal}");
false
}
};
if !line(
base_name.to_string(),
prove_over(&prelude, &[], (base_name, &base_text)),
) {
return ExitCode::from(1);
}
let over: Vec<&Book> = distro
.overlay
.iter()
.chain(distro.dialects.iter())
.collect();
for book in over {
let text = match read_book(book) {
Ok(t) => t,
Err(code) => return code,
};
if !line(
format!("{} over {base_name}", book.name),
prove_over(
&prelude,
&[(base_name, &base_text)],
(book.name.as_str(), &text),
),
) {
return ExitCode::from(1);
}
}
for lib in &distro.libraries {
println!("{}: a library of macros, not scored", lib.name);
}
if passed == total {
println!("PASS {total}/{total}");
ExitCode::SUCCESS
} else {
println!("FAIL {passed}/{total}");
ExitCode::from(1)
}
}
fn prove(args: &[String]) -> ExitCode {
let Some(book) = args.first() else {
eprintln!(
"usage: frazaro prove <phrasebook.vla|folder> [--prelude <prelude.vla>] [--allow-raw]"
);
return ExitCode::from(2);
};
let allow_raw = args.iter().any(|a| a == "--allow-raw");
if std::path::Path::new(book).is_dir() {
return prove_distro(book, args, allow_raw);
}
if !is_file(book) {
return refuse_missing("english-vocab-file-not-found", book);
}
let text = match read_text(book) {
Ok(t) => t,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
if !is_phrasebook_text(&text) {
eprintln!(
"frazaro: 'prove' attempts a phrasebook's proofs; engine proofs are not attempted in this version (PORT.9)"
);
return ExitCode::from(3);
}
let prelude_path = option_after(args, "--prelude")
.map(str::to_string)
.or_else(|| prelude_beside(book));
let prelude = match prelude_path {
Some(path) => {
if !is_file(&path) {
return refuse_missing("vla-file-not-found", &path);
}
match read_text(&path) {
Ok(p) => p,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
}
}
None => BUILT_IN_PRELUDE.to_string(),
};
if let Err(refusal) = frazaro_core::api::vocab_gate(&text, book, allow_raw) {
eprintln!("{refusal}");
return ExitCode::from(1);
}
let mut grammar = frazaro_core::english::Grammar::new(&prelude);
grammar.set_proof_mode(frazaro_core::english::grammar::ProofMode::Collect);
match grammar.load_vocabulary_text(&text, book) {
Ok(_rules) => {
let (tests, fails) = grammar.proofs_run();
let n = tests + fails;
let failures = grammar.proof_failures();
for f in failures {
println!("{}", f.refusal.text);
println!();
}
let k = n - failures.len() as u64;
if failures.is_empty() {
println!("PASS {n}/{n}");
ExitCode::SUCCESS
} else {
println!("FAIL {k}/{n}");
ExitCode::from(1)
}
}
Err(refusal) => {
eprintln!("{refusal}");
ExitCode::from(1)
}
}
}
fn translate(kind: &str, args: &[String]) -> ExitCode {
let Some(program) = args.first() else {
eprintln!(
"usage: frazaro {kind} <program.txt> [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] [--allow-raw]"
);
return ExitCode::from(2);
};
let allow_raw = args.iter().any(|a| a == "--allow-raw");
if !is_file(program) {
return refuse_missing("english-program-file-not-found", program);
}
let prelude = match prelude_of(args) {
Ok(p) => p,
Err(code) => return code,
};
let text = match read_text(program) {
Ok(t) => t,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
let grammar = match load_books(&prelude, args, allow_raw) {
Ok((g, _)) => g,
Err(code) => return code,
};
let out = if kind == "translate-vla" {
grammar.translate_program(&text).map(|t| t.vla)
} else {
grammar.translate_program_to_vba(&text)
};
match out {
Ok(s) => {
print!("{s}");
ExitCode::SUCCESS
}
Err(refusal) => {
eprintln!("{refusal}");
ExitCode::from(1)
}
}
}
fn read_books(args: &[String], allow_raw: bool) -> Result<Vec<(String, String)>, ExitCode> {
let named: Vec<&str> = args
.windows(2)
.filter(|w| w[0] == "--phrasebook")
.map(|w| w[1].as_str())
.collect();
let built_in = || {
(
BUILT_IN_ENGLISH_NAME.to_string(),
BUILT_IN_ENGLISH.to_string(),
)
};
if named.is_empty() {
return Ok(vec![built_in()]);
}
let mut books = Vec::with_capacity(named.len());
for book in named {
if !is_file(book) {
if book == "english.vla" {
books.push(built_in());
continue;
}
return Err(refuse_missing("english-vocab-file-not-found", book));
}
let vocab = read_text(book).map_err(|e| {
eprintln!("frazaro: {e}");
ExitCode::from(2)
})?;
if let Err(refusal) = frazaro_core::api::vocab_gate(&vocab, book, allow_raw) {
eprintln!("{refusal}");
return Err(ExitCode::from(1));
}
books.push((book.to_string(), vocab));
}
Ok(books)
}
fn load_books(
prelude: &str,
args: &[String],
allow_raw: bool,
) -> Result<(frazaro_core::english::Grammar, Vec<String>), ExitCode> {
let books = read_books(args, allow_raw)?;
let mut grammar = frazaro_core::english::Grammar::new(prelude);
for (name, vocab) in &books {
if let Err(refusal) = grammar.load_vocabulary_text(vocab, name) {
eprintln!("{refusal}");
return Err(ExitCode::from(1));
}
}
Ok((grammar, books.into_iter().map(|(_, text)| text).collect()))
}
fn rebuild(args: &[String]) -> ExitCode {
let Some(file) = args.first() else {
eprintln!(
"usage: frazaro rebuild <file.xlsx> [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] [--allow-raw]"
);
return ExitCode::from(2);
};
let allow_raw = args.iter().any(|a| a == "--allow-raw");
if !is_file(file) {
return refuse_missing("vla-file-not-found", file);
}
let prelude_text = match prelude_of(args) {
Ok(p) => p,
Err(code) => return code,
};
let bytes = match std::fs::read(file) {
Ok(b) => b,
Err(e) => {
eprintln!("frazaro: cannot read {file}: {e}");
return ExitCode::from(2);
}
};
let books = match read_books(args, allow_raw) {
Ok(b) => b,
Err(code) => return code,
};
let texts: Vec<&str> = books.iter().map(|(_, text)| text.as_str()).collect();
match frazaro_core::api::english_rebuild_xlsx(&bytes, &prelude_text, &texts) {
Ok(r) => {
println!("{}", r.line());
if r.matches {
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
Err(r) => {
eprintln!("{}", r.refusal);
ExitCode::from(1)
}
}
}
fn build(args: &[String]) -> ExitCode {
let (Some(program), Some(out)) = (args.first(), option_after(args, "--out")) else {
eprintln!(
"usage: frazaro build <program.txt> [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] --out <file.xlsx> [--into <model.xlsx>] [--replace] [--allow-raw]"
);
return ExitCode::from(2);
};
let allow_raw = args.iter().any(|a| a == "--allow-raw");
let replace = args.iter().any(|a| a == "--replace");
let into = option_after(args, "--into");
if !is_file(program) {
return refuse_missing("english-program-file-not-found", program);
}
let prelude_text = match prelude_of(args) {
Ok(p) => p,
Err(code) => return code,
};
if let Some(model) = into {
if !is_file(model) {
return refuse_missing("vla-file-not-found", model);
}
}
if std::path::Path::new(out).exists() && !replace {
eprintln!(
"{}",
frazaro_core::messages::raise("build-output-exists", &[("path", out)])
);
return ExitCode::from(1);
}
let text = match read_text(program) {
Ok(t) => t,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
let (grammar, texts) = match load_books(&prelude_text, args, allow_raw) {
Ok(gt) => gt,
Err(code) => return code,
};
let translation = match grammar.translate_program(&text) {
Ok(t) => t,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let texts: Vec<&str> = texts.iter().map(String::as_str).collect();
let built = match into {
None => frazaro_core::build::build_xlsx(&text, &translation.vla, &prelude_text, &texts),
Some(model) => match std::fs::read(model) {
Ok(bytes) => frazaro_core::build::build_xlsx_into(
&text,
&translation.vla,
&prelude_text,
&texts,
bytes,
model,
),
Err(e) => {
eprintln!("frazaro: cannot read {model}: {e}");
return ExitCode::from(2);
}
},
};
let bytes = match built {
Ok(b) => b,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
if let Err(e) = std::fs::write(out, &bytes) {
eprintln!("frazaro: cannot write {out}: {e}");
return ExitCode::from(2);
}
println!(
"wrote {out}: {} bytes, sha256 {}",
bytes.len(),
frazaro_core::sha256::sha256_hex(&bytes)
);
ExitCode::SUCCESS
}
fn view(args: &[String]) -> ExitCode {
let (Some(program), Some(sheet)) = (args.first(), option_after(args, "--sheet")) else {
eprintln!(
"usage: frazaro view <program.txt> --sheet <name> [--window <A1:F20>] [--prelude <prelude.vla>] [--phrasebook <file.vla> ...] [--allow-raw]"
);
return ExitCode::from(2);
};
let allow_raw = args.iter().any(|a| a == "--allow-raw");
let window = option_after(args, "--window");
if !is_file(program) {
return refuse_missing("english-program-file-not-found", program);
}
let prelude_text = match prelude_of(args) {
Ok(p) => p,
Err(code) => return code,
};
let text = match read_text(program) {
Ok(t) => t,
Err(e) => {
eprintln!("frazaro: {e}");
return ExitCode::from(2);
}
};
let (grammar, texts) = match load_books(&prelude_text, args, allow_raw) {
Ok(gt) => gt,
Err(code) => return code,
};
let translation = match grammar.translate_program(&text) {
Ok(t) => t,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let texts: Vec<&str> = texts.iter().map(String::as_str).collect();
let model =
match frazaro_core::build::build_workbook(&text, &translation.vla, &prelude_text, &texts) {
Ok(wb) => wb,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let window = match frazaro_core::view::window_of(&model, sheet, window) {
Ok(w) => w,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let library = frazaro_core::excel::library();
let calc = frazaro_core::calc::Calc::run(&model, &library);
print!(
"{}",
frazaro_core::view::view_text_valued(&model, &window, &calc)
);
ExitCode::SUCCESS
}
fn ms_since(since: std::time::Instant) -> f64 {
since.elapsed().as_secs_f64() * 1000.0
}
fn reflect(args: &[String]) -> ExitCode {
const USAGE_LINE: &str =
"usage: frazaro reflect <file.xlsx|.ods> [--counts] [--functions] [--cone <Sheet!A1> ...]";
let Some(file) = args.first() else {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
};
let counts = args.iter().any(|a| a == "--counts");
let functions = args.iter().any(|a| a == "--functions");
let mut cones: Vec<String> = Vec::new();
let mut at = 1;
while at < args.len() {
if args[at] == "--cone" {
match args.get(at + 1) {
Some(root) => cones.push(root.clone()),
None => {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
}
}
at += 2;
} else {
at += 1;
}
}
if !is_file(file) {
return refuse_missing("vla-file-not-found", file);
}
let bytes = match std::fs::read(file) {
Ok(b) => b,
Err(e) => {
eprintln!("frazaro: cannot read {file}: {e}");
return ExitCode::from(2);
}
};
let opened = std::time::Instant::now();
let package = match frazaro_core::reflect::open(&bytes, file) {
Ok(p) => p,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let open_ms = ms_since(opened);
if functions {
let mut histogram = frazaro_core::reflect::histogram::Histogram::default();
for i in 0..package.sheets().len() {
if let Err(refusal) = package.walk_sheet(i, &mut histogram) {
eprintln!("{refusal}");
return ExitCode::from(1);
}
}
print!("{}", histogram.text());
return ExitCode::SUCCESS;
}
if counts {
let (mut cells, mut formulas, mut arrays, mut unreadable, mut part_bytes) =
(0u64, 0u64, 0u64, 0u64, 0usize);
let mut book_distinct: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut read_ms = 0.0f64;
for i in 0..package.sheets().len() {
let started = std::time::Instant::now();
let s = match package.walk_sheet_with(
i,
&mut frazaro_core::reflect::Discard,
&mut book_distinct,
) {
Ok(s) => s,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let took = ms_since(started);
println!(
"sheet {}: cells {} formulas {} distinct-r1c1 {} unreadable {} arrays {} rows {} columns {} bytes {} read {took:.1} ms",
i + 1,
s.cells,
s.formulas,
s.distinct_r1c1,
s.unreadable,
s.array_anchors,
s.rows,
s.columns,
s.part_bytes
);
cells += s.cells;
formulas += s.formulas;
arrays += s.array_anchors;
unreadable += s.unreadable;
part_bytes += s.part_bytes;
read_ms += took;
}
let summary = package.summary();
println!(
"workbook: sheets {} cells {cells} formulas {formulas} distinct-r1c1 {} unreadable {unreadable} arrays {arrays} names {} placeholders {} tables {} books {} strings {} bytes {part_bytes} string-bytes {} open {open_ms:.1} ms read {read_ms:.1} ms",
package.sheets().len(),
book_distinct.len(),
summary.names,
summary.placeholders,
summary.tables,
summary.external_books,
summary.strings,
summary.string_bytes
);
return ExitCode::SUCCESS;
}
if !cones.is_empty() {
let mut index = frazaro_core::reflect::cone::Index::new(package.as_ref());
let started = std::time::Instant::now();
for i in 0..package.sheets().len() {
if let Err(refusal) = package.walk_sheet(i, &mut index) {
eprintln!("{refusal}");
return ExitCode::from(1);
}
}
let indexed_ms = ms_since(started);
for root in &cones {
let Some((sheet, row, col)) = frazaro_core::refers::parse_home(root) else {
eprintln!("frazaro: --cone wants a cell as Sheet!A1 or 'Q1 Data'!A1, not {root}");
return ExitCode::from(2);
};
let wanted = frazaro_core::intrinsics::fold(&sheet);
let Some(s) = package
.sheets()
.iter()
.position(|info| frazaro_core::intrinsics::fold(&info.name) == wanted)
else {
eprintln!("frazaro: --cone {root}: the workbook has no sheet so named");
return ExitCode::from(2);
};
let started = std::time::Instant::now();
let stats = frazaro_core::reflect::cone::cone(&index, package.as_ref(), s, row, col);
println!(
"cone {root}: {} indexed {indexed_ms:.1} ms walked {:.1} ms",
stats.line(),
ms_since(started)
);
}
return ExitCode::SUCCESS;
}
use std::io::Write;
struct Lines<W: Write>(W);
impl<W: Write> frazaro_core::reflect::Sink for Lines<W> {
fn row(&mut self, row: &frazaro_core::reflect::Row<'_>) {
let _ = writeln!(self.0, "{}", frazaro_core::reflect::print::line(row));
}
}
let mut out = Lines(std::io::BufWriter::new(std::io::stdout().lock()));
package.header_rows(&mut out);
for i in 0..package.sheets().len() {
if let Err(refusal) = package.walk_sheet(i, &mut out) {
let _ = out.0.flush();
eprintln!("{refusal}");
return ExitCode::from(1);
}
}
if out.0.flush().is_err() {
return ExitCode::from(2);
}
ExitCode::SUCCESS
}
fn diff(args: &[String]) -> ExitCode {
const USAGE_LINE: &str = "usage: frazaro diff <old.xlsx|.ods> <new.xlsx|.ods> [--counts]";
let mut files: Vec<&str> = Vec::new();
let mut counts = false;
for a in args {
if a == "--counts" {
counts = true;
} else if a.starts_with("--") || files.len() == 2 {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
} else {
files.push(a);
}
}
if files.len() != 2 {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
}
let mut bytes: Vec<Vec<u8>> = Vec::new();
for file in &files {
if !is_file(file) {
return refuse_missing("vla-file-not-found", file);
}
match std::fs::read(file) {
Ok(b) => bytes.push(b),
Err(e) => {
eprintln!("frazaro: cannot read {file}: {e}");
return ExitCode::from(2);
}
}
}
let opened = std::time::Instant::now();
let mut packages = Vec::new();
for (i, file) in files.iter().enumerate() {
match frazaro_core::reflect::open(&bytes[i], file) {
Ok(p) => packages.push(p),
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
}
}
let open_ms = ms_since(opened);
let (old, new) = (packages[0].as_ref(), packages[1].as_ref());
use frazaro_core::reflect::diff::{self as d, ChangeSink};
if counts {
let started = std::time::Instant::now();
return match d::diff(old, new, &mut d::Discard) {
Ok(stats) => {
println!(
"diff: {} open {open_ms:.1} ms compared {:.1} ms",
stats.line(),
ms_since(started)
);
ExitCode::SUCCESS
}
Err(refusal) => {
eprintln!("{refusal}");
ExitCode::from(1)
}
};
}
use std::io::Write;
struct Changes<W: Write>(W);
impl<W: Write> ChangeSink for Changes<W> {
fn change(&mut self, change: &d::Change<'_>) {
let _ = writeln!(self.0, "{}", d::line(change));
}
}
let mut out = Changes(std::io::BufWriter::new(std::io::stdout().lock()));
if let Err(refusal) = d::diff(old, new, &mut out) {
let _ = out.0.flush();
eprintln!("{refusal}");
return ExitCode::from(1);
}
if out.0.flush().is_err() {
return ExitCode::from(2);
}
ExitCode::SUCCESS
}
fn audit(args: &[String]) -> ExitCode {
const USAGE_LINE: &str = "usage: frazaro audit <file.xlsx|.ods> [--counts]";
let mut file: Option<&str> = None;
let mut counts = false;
for a in args {
if a == "--counts" {
counts = true;
} else if a.starts_with("--") || file.is_some() {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
} else {
file = Some(a);
}
}
let Some(file) = file else {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
};
if !is_file(file) {
return refuse_missing("vla-file-not-found", file);
}
let bytes = match std::fs::read(file) {
Ok(b) => b,
Err(e) => {
eprintln!("frazaro: cannot read {file}: {e}");
return ExitCode::from(2);
}
};
let opened = std::time::Instant::now();
let package = match frazaro_core::reflect::open(&bytes, file) {
Ok(p) => p,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let open_ms = ms_since(opened);
use frazaro_core::reflect::audit::{self as au, FindingSink};
let started = std::time::Instant::now();
let mut index = au::AuditIndex::new(package.as_ref());
for i in 0..package.sheets().len() {
if let Err(refusal) = package.walk_sheet(i, &mut index) {
eprintln!("{refusal}");
return ExitCode::from(1);
}
}
let indexed_ms = ms_since(started);
if counts {
let started = std::time::Instant::now();
let stats = au::audit(&index, package.as_ref(), &mut au::Discard);
println!(
"audit: {} open {open_ms:.1} ms indexed {indexed_ms:.1} ms walked {:.1} ms",
stats.line(),
ms_since(started)
);
return ExitCode::SUCCESS;
}
use std::io::Write;
struct Findings<W: Write>(W);
impl<W: Write> FindingSink for Findings<W> {
fn finding(&mut self, finding: &au::Finding<'_>) {
let _ = writeln!(self.0, "{}", au::line(finding));
}
}
let mut out = Findings(std::io::BufWriter::new(std::io::stdout().lock()));
au::audit(&index, package.as_ref(), &mut out);
if out.0.flush().is_err() {
return ExitCode::from(2);
}
ExitCode::SUCCESS
}
fn calc(args: &[String]) -> ExitCode {
const USAGE_LINE: &str = "usage: frazaro calc <file.xlsx|.ods> [--counts | --shapes]";
let mut file: Option<&str> = None;
let mut counts = false;
let mut shapes = false;
for a in args {
if a == "--counts" {
counts = true;
} else if a == "--shapes" {
shapes = true;
} else if a.starts_with("--") || file.is_some() {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
} else {
file = Some(a);
}
}
let Some(file) = file.filter(|_| !(counts && shapes)) else {
eprintln!("{USAGE_LINE}");
return ExitCode::from(2);
};
if !is_file(file) {
return refuse_missing("vla-file-not-found", file);
}
let bytes = match std::fs::read(file) {
Ok(b) => b,
Err(e) => {
eprintln!("frazaro: cannot read {file}: {e}");
return ExitCode::from(2);
}
};
let opened = std::time::Instant::now();
let collected = match frazaro_core::reflect::calc::collect(&bytes, file) {
Ok(c) => c,
Err(refusal) => {
eprintln!("{refusal}");
return ExitCode::from(1);
}
};
let read_ms = ms_since(opened);
let library = frazaro_core::excel::library();
if shapes {
print!(
"{}",
frazaro_core::reflect::calc::census_of(&collected, &library)
);
return ExitCode::SUCCESS;
}
let started = std::time::Instant::now();
let (rows, tally) = frazaro_core::reflect::calc::rows_of(&collected, &library);
if counts {
println!(
"calc: {} read {read_ms:.1} ms calc {:.1} ms",
tally.line(),
ms_since(started)
);
return ExitCode::SUCCESS;
}
print!("{rows}");
ExitCode::SUCCESS
}
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
match args.first().map(String::as_str) {
Some("compile") => compile(&args[1..]),
Some("load") => load(&args[1..]),
Some("prove") => prove(&args[1..]),
Some("translate-vla") => translate("translate-vla", &args[1..]),
Some("translate-vba") => translate("translate-vba", &args[1..]),
Some("build") => build(&args[1..]),
Some("rebuild") => rebuild(&args[1..]),
Some("reflect") => reflect(&args[1..]),
Some("diff") => diff(&args[1..]),
Some("audit") => audit(&args[1..]),
Some("view") => view(&args[1..]),
Some("calc") => calc(&args[1..]),
Some("version") | Some("--version") | Some("-V") => {
let core = frazaro_core::version();
let abi = frazaro_core::frazaro_abi_version();
println!("frazaro {core} (core abi {abi})");
ExitCode::SUCCESS
}
None | Some("help") | Some("--help") | Some("-h") => {
print!("{USAGE}");
ExitCode::SUCCESS
}
Some(other) => {
eprintln!(
"frazaro: '{other}' is not a command in this version; \
'frazaro help' lists the commands."
);
ExitCode::from(2)
}
}
}