From c21bf070a2af0aab60f9a36e2d6bbb382dfa61ee Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder Date: Sun, 18 Aug 2024 21:55:32 -0400 Subject: [PATCH] chore: remove miden-diagnostics, start making midenc-session no-std-compatible --- Cargo.lock | 6 +- Cargo.toml | 7 +- frontend-wasm/src/test_utils.rs | 7 +- hir-transform/Cargo.toml | 1 - hir/src/parser/tests/utils.rs | 10 +- midenc-compile/Cargo.toml | 1 - midenc-compile/src/compiler.rs | 19 +- midenc-compile/src/stages/parse.rs | 8 +- midenc-debug/src/cli.rs | 4 +- midenc-debug/src/ui/panes/source_code.rs | 13 +- midenc-driver/Cargo.toml | 9 +- midenc-driver/src/midenc.rs | 6 +- midenc-session/Cargo.toml | 9 +- midenc-session/src/color.rs | 133 ++++++ midenc-session/src/diagnostics.rs | 53 +-- midenc-session/src/emit.rs | 9 +- midenc-session/src/emitter.rs | 322 +++++++++++++++ .../src/{flags.rs => flags/flag.rs} | 80 +--- midenc-session/src/flags/mod.rs | 388 ++++++++++++++++++ midenc-session/src/inputs.rs | 113 ++++- midenc-session/src/lib.rs | 62 +-- midenc-session/src/libs.rs | 3 +- midenc-session/src/options/mod.rs | 128 +----- midenc-session/src/outputs.rs | 20 +- midenc/Cargo.toml | 2 +- sdk/base-sys/Cargo.toml | 4 +- sdk/base-sys/src/lib.rs | 2 +- 27 files changed, 1116 insertions(+), 303 deletions(-) create mode 100644 midenc-session/src/color.rs create mode 100644 midenc-session/src/emitter.rs rename midenc-session/src/{flags.rs => flags/flag.rs} (54%) create mode 100644 midenc-session/src/flags/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 477bccd6..33880343 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3144,7 +3144,6 @@ dependencies = [ "inventory", "log", "miden-assembly", - "miden-diagnostics", "miden-thiserror", "midenc-codegen-masm", "midenc-frontend-wasm", @@ -3300,7 +3299,6 @@ version = "0.0.1" dependencies = [ "anyhow", "inventory", - "miden-diagnostics", "midenc-hir", "midenc-hir-analysis", "midenc-session", @@ -3322,20 +3320,20 @@ dependencies = [ name = "midenc-session" version = "0.0.1" dependencies = [ - "atty", "clap", "inventory", "log", "miden-assembly", "miden-base-sys", "miden-core", - "miden-diagnostics", "miden-stdlib", "miden-thiserror", "midenc-hir-macros", "midenc-hir-symbol", + "parking_lot", "serde", "serde_repr", + "termcolor", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 883e5f3d..35c3cfa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,11 @@ publish = false anyhow = "1.0" bitflags = "2.4" bitcode = { version = "0.6.3", default-features = false, features = ["serde"] } -clap = { version = "4.1", features = ["derive", "env"] } +clap = { version = "4.1", default-features = false, features = [ + "derive", + "std", + "env", +] } cranelift-entity = "0.108" cranelift-bforest = "0.108" env_logger = "0.11" @@ -86,7 +90,6 @@ miden-stdlib = { version = "0.10.3" } #miden-processor = { git = "https://github.com/0xPolygonMiden/miden-vm", rev = "828557c28ca1d159bfe42195e7ea73256ce4aa06" } #miden-stdlib = { git = "https://github.com/0xPolygonMiden/miden-vm", rev = "828557c28ca1d159bfe42195e7ea73256ce4aa06" } midenc-codegen-masm = { path = "codegen/masm" } -miden-diagnostics = "0.1" midenc-hir = { version = "0.0.1", path = "hir" } midenc-hir-analysis = { version = "0.0.1", path = "hir-analysis" } midenc-hir-macros = { version = "0.0.1", path = "hir-macros" } diff --git a/frontend-wasm/src/test_utils.rs b/frontend-wasm/src/test_utils.rs index fe6b1697..c0891542 100644 --- a/frontend-wasm/src/test_utils.rs +++ b/frontend-wasm/src/test_utils.rs @@ -1,10 +1,7 @@ use std::sync::Arc; -use midenc_hir::{ - diagnostics::{ColorChoice, NullEmitter}, - testing::TestContext, -}; -use midenc_session::Options; +use midenc_hir::{diagnostics::NullEmitter, testing::TestContext}; +use midenc_session::{ColorChoice, Options}; pub fn test_context() -> TestContext { let options = Options::default().with_verbosity(midenc_session::Verbosity::Debug); diff --git a/hir-transform/Cargo.toml b/hir-transform/Cargo.toml index 98db47e2..388e0bad 100644 --- a/hir-transform/Cargo.toml +++ b/hir-transform/Cargo.toml @@ -14,7 +14,6 @@ edition.workspace = true [dependencies] anyhow.workspace = true inventory.workspace = true -miden-diagnostics.workspace = true midenc-hir.workspace = true midenc-hir-analysis.workspace = true midenc-session.workspace = true diff --git a/hir/src/parser/tests/utils.rs b/hir/src/parser/tests/utils.rs index 09165372..907c1917 100644 --- a/hir/src/parser/tests/utils.rs +++ b/hir/src/parser/tests/utils.rs @@ -4,7 +4,7 @@ use midenc_session::{Options, Verbosity, Warnings}; use pretty_assertions::assert_eq; use crate::{ - diagnostics::{self, CaptureEmitter, DefaultEmitter, Emitter, EmitterBuffer, Report}, + diagnostics::{self, Buffer, CaptureEmitter, DefaultEmitter, Emitter, IntoDiagnostic, Report}, parser::{ast::Module, Parser}, testing::TestContext, }; @@ -16,7 +16,7 @@ struct SplitEmitter { impl SplitEmitter { #[inline] pub fn new() -> Self { - use diagnostics::ColorChoice; + use midenc_session::ColorChoice; Self { capture: Default::default(), @@ -31,16 +31,16 @@ impl SplitEmitter { } impl Emitter for SplitEmitter { #[inline] - fn buffer(&self) -> EmitterBuffer { + fn buffer(&self) -> Buffer { self.capture.buffer() } #[inline] - fn print(&self, buffer: EmitterBuffer) -> std::io::Result<()> { + fn print(&self, buffer: Buffer) -> Result<(), Report> { use std::io::Write; let mut copy = self.capture.buffer(); - copy.write_all(buffer.as_slice())?; + copy.write_all(buffer.as_slice()).into_diagnostic()?; self.capture.print(buffer)?; self.default.print(copy) } diff --git a/midenc-compile/Cargo.toml b/midenc-compile/Cargo.toml index 6e166ab6..c1066b61 100644 --- a/midenc-compile/Cargo.toml +++ b/midenc-compile/Cargo.toml @@ -21,7 +21,6 @@ intrusive-collections.workspace = true inventory.workspace = true midenc-codegen-masm.workspace = true miden-assembly = { workspace = true, features = ["std"] } -miden-diagnostics.workspace = true midenc-frontend-wasm.workspace = true midenc-hir.workspace = true midenc-hir-analysis.workspace = true diff --git a/midenc-compile/src/compiler.rs b/midenc-compile/src/compiler.rs index ab819023..6315c8cd 100644 --- a/midenc-compile/src/compiler.rs +++ b/midenc-compile/src/compiler.rs @@ -1,10 +1,10 @@ use std::{ffi::OsString, path::PathBuf, sync::Arc}; -use clap::{builder::ArgPredicate, ColorChoice, Parser}; +use clap::{builder::ArgPredicate, Parser}; use midenc_session::{ - diagnostics::{ColorChoice as MDColorChoice, DefaultSourceManager, Emitter}, - DebugInfo, InputFile, LinkLibrary, OptLevel, Options, OutputFile, OutputType, OutputTypeSpec, - OutputTypes, ProjectType, Session, TargetEnv, Verbosity, Warnings, + diagnostics::{DefaultSourceManager, Emitter}, + ColorChoice, DebugInfo, InputFile, LinkLibrary, OptLevel, Options, OutputFile, OutputType, + OutputTypeSpec, OutputTypes, ProjectType, Session, TargetEnv, Verbosity, Warnings, }; /// Compile a program from WebAssembly or Miden IR, to Miden Assembly. @@ -409,7 +409,7 @@ impl Compiler { .unwrap_or_else(|err| err.exit()); let inputs = inputs.into_iter().collect(); - opts.into_session(inputs, emitter).with_arg_matches(compile_matches) + opts.into_session(inputs, emitter).with_extra_flags(compile_matches.into()) } /// Use this configuration to obtain a [Session] used for compilation @@ -422,13 +422,6 @@ impl Compiler { .working_dir .unwrap_or_else(|| std::env::current_dir().expect("no working directory available")); - // Map clap color choices to internal color choice - let color = match self.color { - ColorChoice::Auto => MDColorChoice::Auto, - ColorChoice::Always => MDColorChoice::Always, - ColorChoice::Never => MDColorChoice::Never, - }; - // Determine if a specific output file has been requested let output_file = match self.output_file { Some(path) => Some(OutputFile::Real(path)), @@ -454,7 +447,7 @@ impl Compiler { // Consolidate all compiler options let mut options = Options::new(self.name, self.target, project_type, cwd, self.sysroot) - .with_color(color) + .with_color(self.color) .with_verbosity(self.verbosity) .with_warnings(self.warn) .with_debug_info(self.debug) diff --git a/midenc-compile/src/stages/parse.rs b/midenc-compile/src/stages/parse.rs index 0b431f5f..1d188c75 100644 --- a/midenc-compile/src/stages/parse.rs +++ b/midenc-compile/src/stages/parse.rs @@ -57,7 +57,7 @@ impl Stage for ParseStage { input, session, &WasmTranslationConfig { - source_name: name.as_str().unwrap().to_string().into(), + source_name: name.as_str().to_string().into(), ..Default::default() }, ), @@ -65,13 +65,11 @@ impl Stage for ParseStage { input, session, &WasmTranslationConfig { - source_name: name.as_str().unwrap().to_string().into(), + source_name: name.as_str().to_string().into(), ..Default::default() }, ), - FileType::Masm => { - self.parse_masm_from_bytes(name.as_str().unwrap(), input, session) - } + FileType::Masm => self.parse_masm_from_bytes(name.as_str(), input, session), FileType::Mast => Err(Report::msg( "invalid input: mast libraries are not supported as inputs, did you mean to \ use '-l'?", diff --git a/midenc-debug/src/cli.rs b/midenc-debug/src/cli.rs index 8179dac2..920ce79e 100644 --- a/midenc-debug/src/cli.rs +++ b/midenc-debug/src/cli.rs @@ -2,8 +2,8 @@ use std::{ffi::OsString, path::PathBuf, sync::Arc}; use clap::{ColorChoice, Parser}; use midenc_session::{ - diagnostics::{ColorChoice as MDColorChoice, DefaultSourceManager, Emitter}, - InputFile, LinkLibrary, Options, ProjectType, Session, TargetEnv, + diagnostics::{DefaultSourceManager, Emitter}, + ColorChoice as MDColorChoice, InputFile, LinkLibrary, Options, ProjectType, Session, TargetEnv, }; /// Run a compiled Miden program with the Miden VM diff --git a/midenc-debug/src/ui/panes/source_code.rs b/midenc-debug/src/ui/panes/source_code.rs index a0acac44..4b1d2089 100644 --- a/midenc-debug/src/ui/panes/source_code.rs +++ b/midenc-debug/src/ui/panes/source_code.rs @@ -260,18 +260,9 @@ impl SourceCodePane { fn enable_syntax_highlighting(&mut self, state: &State) { use std::io::IsTerminal; - use midenc_session::diagnostics::ColorChoice; - - let nocolor = match state.session.options.color { - ColorChoice::Always | ColorChoice::AlwaysAnsi => false, - ColorChoice::Never => true, - ColorChoice::Auto => match std::env::var("NO_COLOR") { - _ if !std::io::stdout().is_terminal() => true, - Ok(value) => !matches!(value.as_str(), "0" | "false"), - _ => false, - }, - }; + use midenc_session::ColorChoice; + let nocolor = !state.session.options.color.should_attempt_color(); if nocolor { return; } diff --git a/midenc-driver/Cargo.toml b/midenc-driver/Cargo.toml index 09382776..b8566fea 100644 --- a/midenc-driver/Cargo.toml +++ b/midenc-driver/Cargo.toml @@ -13,11 +13,18 @@ license.workspace = true readme.workspace = true edition.workspace = true +[features] +default = ["all"] +all = ["std", "debug"] +debug = ["dep:midenc-debug"] +std = ["alloc", "log/std", "clap/std", "clap/color", "clap/env"] +alloc = ["clap/help", "clap/usage", "clap/error-context", "clap/suggestions"] + [dependencies] clap.workspace = true log.workspace = true midenc-hir.workspace = true midenc-session.workspace = true midenc-compile.workspace = true -midenc-debug.workspace = true +midenc-debug = { workspace = true, optional = true } thiserror.workspace = true diff --git a/midenc-driver/src/midenc.rs b/midenc-driver/src/midenc.rs index 0248c36b..9cf226a3 100644 --- a/midenc-driver/src/midenc.rs +++ b/midenc-driver/src/midenc.rs @@ -3,6 +3,7 @@ use std::{ffi::OsString, path::PathBuf, rc::Rc, sync::Arc}; use clap::{ColorChoice, Parser, Subcommand}; use log::Log; use midenc_compile as compile; +#[cfg(feature = "debug")] use midenc_debug as debugger; use midenc_hir::FunctionIdent; use midenc_session::{ @@ -96,6 +97,7 @@ enum Commands { /// Run a program under the interactive Miden VM debugger /// /// This command starts a TUI-based interactive debugger with the given program loaded. + #[cfg(feature = "debug")] Debug { /// Specify the path to a Miden program file to execute. /// @@ -180,9 +182,11 @@ impl Midenc { if options.working_dir.is_none() { options.working_dir = Some(cwd); } - let session = options.into_session(vec![input], emitter).with_arg_matches(matches); + let session = + options.into_session(vec![input], emitter).with_extra_flags(matches.into()); compile::compile(Rc::new(session)) } + #[cfg(feature = "debug")] Commands::Debug { input, inputs, diff --git a/midenc-session/Cargo.toml b/midenc-session/Cargo.toml index 4aebd823..1a92cd85 100644 --- a/midenc-session/Cargo.toml +++ b/midenc-session/Cargo.toml @@ -14,23 +14,24 @@ readme.workspace = true edition.workspace = true [features] -default = [] +default = ["std"] +std = ["dep:termcolor", "dep:parking_lot", "dep:clap"] serde = ["dep:serde", "dep:serde_repr", "midenc-hir-symbol/serde"] [dependencies] -atty = "0.2" -clap.workspace = true +clap = { workspace = true, optional = true } inventory.workspace = true log.workspace = true miden-assembly.workspace = true miden-core.workspace = true miden-stdlib.workspace = true -miden-diagnostics.workspace = true midenc-hir-symbol.workspace = true midenc-hir-macros.workspace = true miden-base-sys = { version = "0.0.0", path = "../sdk/base-sys", features = [ "masl-lib", ] } +parking_lot = { workspace = true, optional = true } +termcolor = { version = "1.4.1", optional = true } thiserror.workspace = true serde = { workspace = true, optional = true } serde_repr = { workspace = true, optional = true } diff --git a/midenc-session/src/color.rs b/midenc-session/src/color.rs new file mode 100644 index 00000000..dfe039da --- /dev/null +++ b/midenc-session/src/color.rs @@ -0,0 +1,133 @@ +use alloc::string::ToString; +use core::str::FromStr; + +/// ColorChoice represents the color preferences of an end user. +/// +/// The `Default` implementation for this type will select `Auto`, which tries +/// to do the right thing based on the current environment. +/// +/// The `FromStr` implementation for this type converts a lowercase kebab-case +/// string of the variant name to the corresponding variant. Any other string +/// results in an error. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(clap::ValueEnum))] +pub enum ColorChoice { + /// Try very hard to emit colors. This includes emitting ANSI colors + /// on Windows if the console API is unavailable. + Always, + /// AlwaysAnsi is like Always, except it never tries to use anything other + /// than emitting ANSI color codes. + AlwaysAnsi, + /// Try to use colors, but don't force the issue. If the console isn't + /// available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined, for + /// example, then don't use colors. + #[default] + Auto, + /// Never emit colors. + Never, +} + +#[cfg(feature = "std")] +impl From for termcolor::ColorChoice { + fn from(choice: ColorChoice) -> Self { + match choice { + ColorChoice::Always => Self::Always, + ColorChoice::AlwaysAnsi => Self::AlwaysAnsi, + ColorChoice::Auto => Self::Auto, + ColorChoice::Never => Self::Never, + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error("invalid color choice: {0}")] +pub struct ColorChoiceParseError(alloc::borrow::Cow<'static, str>); + +impl FromStr for ColorChoice { + type Err = ColorChoiceParseError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "always" => Ok(ColorChoice::Always), + "always-ansi" => Ok(ColorChoice::AlwaysAnsi), + "never" => Ok(ColorChoice::Never), + "auto" => Ok(ColorChoice::Auto), + unknown => Err(ColorChoiceParseError(unknown.to_string().into())), + } + } +} + +impl ColorChoice { + /// Returns true if we should attempt to write colored output. + pub fn should_attempt_color(&self) -> bool { + match *self { + ColorChoice::Always => true, + ColorChoice::AlwaysAnsi => true, + ColorChoice::Never => false, + #[cfg(feature = "std")] + ColorChoice::Auto => self.env_allows_color(), + #[cfg(not(feature = "std"))] + ColorChoice::Auto => false, + } + } + + #[cfg(all(feature = "std", not(windows)))] + pub fn env_allows_color(&self) -> bool { + match std::env::var_os("TERM") { + // If TERM isn't set, then we are in a weird environment that + // probably doesn't support colors. + None => return false, + Some(k) => { + if k == "dumb" { + return false; + } + } + } + // If TERM != dumb, then the only way we don't allow colors at this + // point is if NO_COLOR is set. + if std::env::var_os("NO_COLOR").is_some() { + return false; + } + true + } + + #[cfg(all(feature = "std", windows))] + pub fn env_allows_color(&self) -> bool { + // On Windows, if TERM isn't set, then we shouldn't automatically + // assume that colors aren't allowed. This is unlike Unix environments + // where TERM is more rigorously set. + if let Some(k) = std::env::var_os("TERM") { + if k == "dumb" { + return false; + } + } + // If TERM != dumb, then the only way we don't allow colors at this + // point is if NO_COLOR is set. + if std::env::var_os("NO_COLOR").is_some() { + return false; + } + true + } + + /// Returns true if this choice should forcefully use ANSI color codes. + /// + /// It's possible that ANSI is still the correct choice even if this + /// returns false. + #[cfg(all(feature = "std", windows))] + pub fn should_ansi(&self) -> bool { + match *self { + ColorChoice::Always => false, + ColorChoice::AlwaysAnsi => true, + ColorChoice::Never => false, + ColorChoice::Auto => { + match std::env::var("TERM") { + Err(_) => false, + // cygwin doesn't seem to support ANSI escape sequences + // and instead has its own variety. However, the Windows + // console API may be available. + Ok(k) => k != "dumb" && k != "cygwin", + } + } + } + } +} diff --git a/midenc-session/src/diagnostics.rs b/midenc-session/src/diagnostics.rs index a21b2841..11ff71bd 100644 --- a/midenc-session/src/diagnostics.rs +++ b/midenc-session/src/diagnostics.rs @@ -1,11 +1,13 @@ -use std::{ +use alloc::{ + boxed::Box, collections::BTreeMap, fmt::{self, Display}, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, + format, + string::{String, ToString}, + sync::Arc, + vec::Vec, }; +use core::sync::atomic::{AtomicUsize, Ordering}; pub use miden_assembly::diagnostics::{ miette, @@ -16,13 +18,12 @@ pub use miden_assembly::diagnostics::{ WrapErr, }; pub use miden_core::debuginfo::*; -pub use miden_diagnostics::{ - term::termcolor::{Buffer as EmitterBuffer, ColorChoice}, - CaptureEmitter, DefaultEmitter, Emitter, FatalError, NullEmitter, -}; pub use midenc_hir_macros::Spanned; -use crate::{Verbosity, Warnings}; +#[cfg(feature = "std")] +pub use crate::emitter::CaptureEmitter; +pub use crate::emitter::{Buffer, DefaultEmitter, Emitter, NullEmitter}; +use crate::{ColorChoice, Verbosity, Warnings}; #[derive(Default, Debug, Copy, Clone)] pub struct DiagnosticsConfig { @@ -94,17 +95,10 @@ impl DiagnosticsHandler { #[track_caller] pub fn abort_if_errors(&self) { if self.has_errors() { - FatalError.raise(); + panic!("Compiler has encountered unexpected errors. See diagnostics for details.") } } - /// Emits an error message and produces a FatalError object - /// which can be used to terminate execution immediately - pub fn fatal(&self, err: impl ToString) -> FatalError { - self.error(err); - FatalError - } - /// Emit a diagnostic [Report] pub fn report(&self, report: impl Into) { self.emit(report.into()) @@ -144,10 +138,8 @@ impl DiagnosticsHandler { } /// Emits the given diagnostic - #[inline(always)] + #[inline(never)] pub fn emit(&self, diagnostic: impl Into) { - use std::io::Write; - let diagnostic: Report = diagnostic.into(); let diagnostic = match diagnostic.severity() { Some(Severity::Advice) if self.verbosity > Verbosity::Info => return, @@ -170,11 +162,24 @@ impl DiagnosticsHandler { return; } + self.write_report(diagnostic); + } + + #[cfg(feature = "std")] + fn write_report(&self, diagnostic: Report) { + use std::io::Write; + let mut buffer = self.emitter.buffer(); let printer = PrintDiagnostic::new(diagnostic); write!(&mut buffer, "{printer}").expect("failed to write diagnostic to buffer"); self.emitter.print(buffer).unwrap(); } + + #[cfg(not(feature = "std"))] + fn write_report(&self, diagnostic: Report) { + let out = PrintDiagnostic::new(diagnostic).to_string(); + self.emitter.print(out).unwrap(); + } } #[derive(thiserror::Error, Diagnostic, Debug)] @@ -339,18 +344,18 @@ impl InFlightDiagnostic { } impl fmt::Display for InFlightDiagnostic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", &self.message) } } impl fmt::Debug for InFlightDiagnostic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", &self.message) } } -impl std::error::Error for InFlightDiagnostic {} +impl core::error::Error for InFlightDiagnostic {} impl Diagnostic for InFlightDiagnostic { fn code<'a>(&'a self) -> Option> { diff --git a/midenc-session/src/emit.rs b/midenc-session/src/emit.rs index d13b9620..736ad035 100644 --- a/midenc-session/src/emit.rs +++ b/midenc-session/src/emit.rs @@ -1,4 +1,5 @@ -use std::{fmt, fs::File, io::Write, path::Path, sync::Arc}; +use alloc::{boxed::Box, fmt, format, string::ToString, sync::Arc, vec}; +use std::{fs::File, io::Write, path::Path}; use miden_core::{prettier::PrettyPrint, utils::Serializable}; use midenc_hir_symbol::Symbol; @@ -13,8 +14,9 @@ pub trait Emit { /// Write this item to standard output, inferring the best [OutputMode] based on whether or not /// stdout is a tty or not fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { + use std::io::IsTerminal; let stdout = std::io::stdout().lock(); - let mode = if atty::is(atty::Stream::Stdout) { + let mode = if stdout.is_terminal() { OutputMode::Text } else { OutputMode::Binary @@ -268,8 +270,9 @@ impl Emit for miden_core::Program { } fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { + use std::io::IsTerminal; let mut stdout = std::io::stdout().lock(); - let mode = if atty::is(atty::Stream::Stdout) { + let mode = if stdout.is_terminal() { OutputMode::Text } else { OutputMode::Binary diff --git a/midenc-session/src/emitter.rs b/midenc-session/src/emitter.rs new file mode 100644 index 00000000..abeb153c --- /dev/null +++ b/midenc-session/src/emitter.rs @@ -0,0 +1,322 @@ +use alloc::{string::String, vec::Vec}; +use core::ops::Deref; + +use crate::{ + diagnostics::{IntoDiagnostic, Report}, + ColorChoice, +}; + +/// The [Emitter] trait is used for controlling how diagnostics are displayed. +/// +/// An [Emitter] must produce a [Buffer] for use by the rendering +/// internals, and its own print implementation. +/// +/// When a diagnostic is being emitted, a new [Buffer] is allocated, +/// the diagnostic is rendered into it, and then the buffer is passed +/// to `print` for display by the [Emitter] implementation. +pub trait Emitter: Send + Sync { + /// Construct a new [Buffer] for use by the renderer + fn buffer(&self) -> Buffer; + /// Display the contents of the given [Buffer] + fn print(&self, buffer: Buffer) -> Result<(), Report>; +} + +/// [DefaultEmitter] is used for rendering to stderr, and as is implied +/// by the name, is the default emitter implementation. +pub struct DefaultEmitter(DefaultEmitterImpl); +impl DefaultEmitter { + /// Construct a new [DefaultEmitter] with the given [ColorChoice] behavior. + pub fn new(color: ColorChoice) -> Self { + Self(DefaultEmitterImpl::new(color)) + } +} +impl Emitter for DefaultEmitter { + #[inline(always)] + fn buffer(&self) -> Buffer { + self.0.buffer() + } + + #[inline(always)] + fn print(&self, buffer: Buffer) -> Result<(), Report> { + self.0.print(buffer) + } +} +impl Deref for DefaultEmitter { + type Target = DefaultEmitterImpl; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(feature = "std")] +#[doc(hidden)] +pub struct DefaultEmitterImpl { + writer: termcolor::BufferWriter, +} + +#[cfg(not(feature = "std"))] +#[doc(hidden)] +pub struct DefaultEmitterImpl { + writer: Vec, + ansi: bool, +} + +#[cfg(feature = "std")] +impl DefaultEmitterImpl { + fn new(color: ColorChoice) -> Self { + Self { + writer: termcolor::BufferWriter::stderr(color.into()), + } + } +} + +#[cfg(feature = "std")] +impl Emitter for DefaultEmitterImpl { + #[inline(always)] + fn buffer(&self) -> Buffer { + Buffer(self.writer.buffer()) + } + + #[inline(always)] + fn print(&self, buffer: Buffer) -> Result<(), Report> { + self.writer.print(&buffer.0).into_diagnostic() + } +} + +#[cfg(not(feature = "std"))] +impl DefaultEmitterImpl { + fn new(color: ColorChoice) -> Self { + Self { + ansi: color.should_ansi(), + writer: vec![], + } + } +} + +#[cfg(not(feature = "std"))] +impl Emitter for DefaultEmitterImpl { + #[inline(always)] + fn buffer(&self) -> Buffer { + if self.ansi { + Buffer::ansi() + } else { + Buffer::no_color() + } + } + + #[inline(always)] + fn print(&self, buffer: Buffer) -> Result<(), Report> { + self.0.push(b'\n'); + self.0.extend(buffer.into_inner()); + Ok(()) + } +} + +/// [CaptureEmitter] is used to capture diagnostics which are emitted, for later examination. +/// +/// This is intended for use in testing, where it is desirable to emit diagnostics +/// and write assertions about what was displayed to the user. +#[derive(Default)] +#[cfg(feature = "std")] +pub struct CaptureEmitter { + buffer: parking_lot::Mutex>, +} +#[cfg(feature = "std")] +impl CaptureEmitter { + /// Create a new [CaptureEmitter] + #[inline] + pub fn new() -> Self { + Self::default() + } + + pub fn captured(&self) -> String { + let buf = self.buffer.lock(); + String::from_utf8_lossy(buf.as_slice()).into_owned() + } +} +#[cfg(feature = "std")] +impl Emitter for CaptureEmitter { + #[inline] + fn buffer(&self) -> Buffer { + Buffer::no_color() + } + + #[inline] + fn print(&self, buffer: Buffer) -> Result<(), Report> { + let mut bytes = buffer.into_inner(); + let mut buf = self.buffer.lock(); + buf.append(&mut bytes); + Ok(()) + } +} + +/// [NullEmitter] is used to silence diagnostics entirely, without changing +/// anything in the diagnostic infrastructure. +/// +/// When used, the rendered buffer is thrown away. +#[derive(Clone, Copy, Default)] +pub struct NullEmitter { + ansi: bool, +} +impl NullEmitter { + #[cfg(feature = "std")] + pub fn new(color: ColorChoice) -> Self { + use std::io::IsTerminal; + + let ansi = match color { + ColorChoice::Never => false, + ColorChoice::Always | ColorChoice::AlwaysAnsi => true, + ColorChoice::Auto => std::io::stdout().is_terminal(), + }; + Self { ansi } + } + + #[cfg(not(feature = "std"))] + pub fn new(color: ColorChoice) -> Self { + let ansi = match color { + ColorChoice::Never => false, + ColorChoice::Always | ColorChoice::AlwaysAnsi => true, + ColorChoice::Auto => false, + }; + Self { ansi } + } +} +impl Emitter for NullEmitter { + #[inline(always)] + fn buffer(&self) -> Buffer { + if self.ansi { + Buffer::ansi() + } else { + Buffer::no_color() + } + } + + #[inline(always)] + fn print(&self, _buffer: Buffer) -> Result<(), Report> { + Ok(()) + } +} + +#[doc(hidden)] +#[cfg(not(feature = "std"))] +#[derive(Clone, Debug)] +pub struct Buffer(Vec); + +#[doc(hidden)] +#[cfg(feature = "std")] +#[derive(Clone, Debug)] +pub struct Buffer(termcolor::Buffer); + +impl Buffer { + /// Create a new buffer with the given color settings. + #[cfg(not(feature = "std"))] + pub fn new(_choice: ColorChoice) -> Buffer { + Self::no_color() + } + + #[cfg(feature = "std")] + pub fn new(choice: ColorChoice) -> Buffer { + match choice { + ColorChoice::Never => Self::no_color(), + ColorChoice::Auto => { + if choice.should_attempt_color() { + Self::ansi() + } else { + Self::no_color() + } + } + ColorChoice::Always | ColorChoice::AlwaysAnsi => Self::ansi(), + } + } + + /// Create a buffer that drops all color information. + #[cfg(not(feature = "std"))] + pub fn no_color() -> Buffer { + Self(vec![]) + } + + /// Create a buffer that drops all color information. + #[cfg(feature = "std")] + pub fn no_color() -> Buffer { + Self(termcolor::Buffer::no_color()) + } + + /// Create a buffer that uses ANSI escape sequences. + #[cfg(not(feature = "std"))] + pub fn ansi() -> Buffer { + Buffer(vec![]) + } + + /// Create a buffer that uses ANSI escape sequences. + #[cfg(feature = "std")] + pub fn ansi() -> Buffer { + Self(termcolor::Buffer::ansi()) + } + + /// Returns true if and only if this buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the length of this buffer in bytes. + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Clears this buffer. + #[inline] + pub fn clear(&mut self) { + self.0.clear() + } + + /// Consume this buffer and return the underlying raw data. + /// + /// On Windows, this unrecoverably drops all color information associated + /// with the buffer. + #[inline(always)] + #[cfg(not(feature = "std"))] + pub fn into_inner(self) -> Vec { + self.0 + } + + #[cfg(feature = "std")] + pub fn into_inner(self) -> Vec { + self.0.into_inner() + } + + /// Return the underlying data of the buffer. + #[inline(always)] + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + /// Return the underlying data of the buffer as a mutable slice. + #[inline(always)] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.0.as_mut_slice() + } +} + +#[cfg(not(feature = "std"))] +impl core::fmt::Write for Buffer { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Result> { + use core::fmt::Write; + self.0.write_str(s); + } +} + +#[cfg(feature = "std")] +impl std::io::Write for Buffer { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } +} diff --git a/midenc-session/src/flags.rs b/midenc-session/src/flags/flag.rs similarity index 54% rename from midenc-session/src/flags.rs rename to midenc-session/src/flags/flag.rs index db3bf7a3..2bcb7095 100644 --- a/midenc-session/src/flags.rs +++ b/midenc-session/src/flags/flag.rs @@ -1,4 +1,4 @@ -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CompileFlag { pub name: &'static str, pub short: Option, @@ -73,7 +73,7 @@ impl CompileFlag { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum FlagAction { Set, Append, @@ -81,6 +81,18 @@ pub enum FlagAction { SetFalse, Count, } +impl FlagAction { + pub fn is_boolean(&self) -> bool { + matches!(self, Self::SetTrue | Self::SetFalse) + } + + pub fn as_boolean_value(&self) -> bool { + assert!(self.is_boolean()); + self == &Self::SetTrue + } +} + +#[cfg(feature = "std")] impl From for clap::ArgAction { fn from(action: FlagAction) -> Self { match action { @@ -94,67 +106,3 @@ impl From for clap::ArgAction { } inventory::collect!(CompileFlag); - -/// Generate a fake compile command for use with default options -fn fake_compile_command() -> clap::Command { - let cmd = clap::Command::new("compile") - .no_binary_name(true) - .disable_help_flag(true) - .disable_version_flag(true) - .disable_help_subcommand(true); - register_flags(cmd) -} - -/// Get [clap::ArgMatches] for registered command-line flags, without a [clap::Command] -pub fn default_arg_matches(argv: I) -> Result -where - I: IntoIterator, - V: Into + Clone, -{ - fake_compile_command().try_get_matches_from(argv) -} - -/// Register dynamic flags to be shown via `midenc help compile` -pub fn register_flags(cmd: clap::Command) -> clap::Command { - inventory::iter::.into_iter().fold(cmd, |cmd, flag| { - let arg = clap::Arg::new(flag.name) - .long(flag.long.unwrap_or(flag.name)) - .action(clap::ArgAction::from(flag.action)); - let arg = if let Some(help) = flag.help { - arg.help(help) - } else { - arg - }; - let arg = if let Some(help_heading) = flag.help_heading { - arg.help_heading(help_heading) - } else { - arg - }; - let arg = if let Some(short) = flag.short { - arg.short(short) - } else { - arg - }; - let arg = if let Some(env) = flag.env { - arg.env(env) - } else { - arg - }; - let arg = if let Some(value) = flag.default_missing_value { - arg.default_missing_value(value) - } else { - arg - }; - let arg = if let Some(value) = flag.default_value { - arg.default_value(value) - } else { - arg - }; - let arg = if let Some(value) = flag.hide { - arg.hide(value) - } else { - arg - }; - cmd.arg(arg) - }) -} diff --git a/midenc-session/src/flags/mod.rs b/midenc-session/src/flags/mod.rs new file mode 100644 index 00000000..a4bc902c --- /dev/null +++ b/midenc-session/src/flags/mod.rs @@ -0,0 +1,388 @@ +mod flag; + +use alloc::vec::Vec; +use core::fmt; + +pub use self::flag::{CompileFlag, FlagAction}; +use crate::diagnostics::{IntoDiagnostic, Report}; + +#[cfg(feature = "std")] +pub struct CompileFlags { + flags: Vec, + arg_matches: clap::ArgMatches, +} + +#[cfg(not(feature = "std"))] +pub struct CompileFlags { + flags: Vec, + args: alloc::collections::BTreeMap, +} + +#[cfg(feature = "std")] +impl Default for CompileFlags { + fn default() -> Self { + Self::new(None::).unwrap() + } +} + +#[cfg(not(feature = "std"))] +impl Default for CompileFlags { + fn default() -> Self { + Self::new(None::) + } +} + +#[cfg(feature = "std")] +impl From for CompileFlags { + fn from(arg_matches: clap::ArgMatches) -> Self { + let flags = inventory::iter::.into_iter().cloned().collect(); + Self { flags, arg_matches } + } +} + +impl CompileFlags { + /// Create a new [CompileFlags] from the given argument vector + #[cfg(feature = "std")] + pub fn new(argv: I) -> Result + where + I: IntoIterator, + V: Into + Clone, + { + let flags = inventory::iter::.into_iter().cloned().collect(); + fake_compile_command() + .try_get_matches_from(argv) + .into_diagnostic() + .map(|arg_matches| Self { flags, arg_matches }) + } + + /// Get [clap::ArgMatches] for registered command-line flags, without a [clap::Command] + #[cfg(not(feature = "std"))] + pub fn new(argv: I) -> Result + where + I: IntoIterator, + V: Into + Clone, + { + use alloc::collections::{BTreeMap, VecDeque}; + let mut argv = argv.into_iter().map(|arg| arg.into()).collect::>(); + + let flags = inventory::iter:: + .into_iter() + .map(|flag| (flag.name.clone(), flag)) + .collect::>(); + let this = Self { + flags: flags.values().cloned().collect(), + arg_matches: Default::default(), + }; + let mut this = flags.values().fold(this, |this, flag| { + if let Some(default_value) = flag.default_value { + this.arg_matches.insert(flag.name, Some(vec![default_value])); + } else { + this.arg_matches.insert(flag.name, None); + } + }); + + while let Some(arg) = argv.pop_front() { + let Some(name) = arg.strip_prefix("--").or_else(|| { + arg.strip_prefix("-").and_then(|name| { + flags.values().find_map(|flag| { + if flag.short == name { + Some(flag.name.clone()) + } else { + None + } + }) + }) + }) else { + return Err(Report::msg(format!("unexpected positional argument: '{arg}'"))); + }; + if let Some(flag) = flags.get(&name) { + match flag.action { + FlagAction::Set => { + let value = argv + .pop_front() + .or_else(flag.default_missing_value.clone()) + .or_else(flag.default_value.clone()); + if let Some(value) = value { + this.arg_matches.insert(name.to_string(), Some(vec![value])) + } else { + return Err(Report::msg(format!( + "missing required value for '--{name}'" + ))); + } + } + FlagAction::Count => { + match this.arg_matches.entry(name.to_string()).or_insert_default() { + Some(ref mut values) => { + values.push("".to_string()); + } + ref mut entry => { + *entry = Some(vec!["".to_string()]); + } + } + } + FlagAction::Append => { + let value = argv + .pop_front() + .or_else(flag.default_missing_value.clone()) + .or_else(flag.default_value.clone()); + if let Some(value) = value { + match this.arg_matches.entry(name.to_string()).or_insert_default() { + Some(ref mut values) => { + values.push(value); + } + ref mut entry => { + *entry = Some(vec![value]); + } + } + } else { + return Err(Report::msg(format!( + "missing required value for '--{name}'" + ))); + } + } + FlagAction::SetTrue | FlagAction::SetFalse => { + this.arg_matches.insert( + name.to_string(), + Some(vec![flag.action.as_boolean_value().to_string()]), + ); + continue; + } + } + } + } + + Ok(this) + } + + pub fn flags(&self) -> &[CompileFlag] { + self.flags.as_slice() + } + + /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse` + #[cfg(feature = "std")] + pub fn get_flag(&self, name: &str) -> bool { + self.arg_matches.get_flag(name) + } + + /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse` + #[cfg(not(feature = "std"))] + pub fn get_flag(&self, name: &str) -> bool { + let flag = + self.flags.iter().find(|flag| flag.name == name).unwrap_or_else(|| { + panic!("invalid flag '--{name}', did you forget to register it?") + }); + self.arg_matches + .get(name) + .and_then(|maybe_values| match maybe_values { + Some(values) => Some(values[0].as_str() == "true"), + None => match flag.default_missing_value.as_deref() { + None => None, + Some("true") => Some(true), + Some("false") => Some(false), + Some(other) => unreachable!( + "should not be possible to set '{other}' for boolean flag '--{name}'" + ), + }, + }) + .unwrap_or_else(|| match flag.default_value.as_deref() { + None => { + if flag.action == FlagAction::SetTrue { + false + } else { + true + } + } + Some("true") => Some(true), + Some("false") => Some(false), + Some(other) => { + panic!("invalid default_value for boolean flag '--{name}': '{other}'") + } + }) + } + + /// Get the count of a specific custom flag with action `FlagAction::Count` + #[cfg(feature = "std")] + pub fn get_flag_count(&self, name: &str) -> usize { + self.arg_matches.get_count(name) as usize + } + + /// Get the count of a specific custom flag with action `FlagAction::Count` + #[cfg(not(feature = "std"))] + pub fn get_flag_count(&self, name: &str) -> usize { + let flag = + self.flags.iter().find(|flag| flag.name == name).unwrap_or_else(|| { + panic!("invalid flag '--{name}', did you forget to register it?") + }); + self.arg_matches + .get(name) + .map(|maybe_values| match maybe_values { + Some(values) => values.len(), + None => match flag.default_missing_value.as_deref() { + None => 0, + Some(n) => n + .parse::() + .expect("invalid default_missing_value for '--{name}': '{n}'"), + }, + }) + .unwrap_or_else(|| match flag.default_value.as_deref() { + None => 0, + Some(n) => n.parse::().expect("invalid default_value for '--{name}': '{n}'"), + }) + } + + /// Get the value of a specific custom flag + #[cfg(feature = "std")] + pub fn get_flag_value(&self, name: &str) -> Option<&T> + where + T: core::any::Any + Clone + Send + Sync + 'static, + { + self.arg_matches.get_one(name) + } + + /// Get the value of a specific custom flag + #[cfg(not(feature = "std"))] + pub fn get_flag_value(&self, name: &str) -> Option<&T> + where + T: core::any::Any + FromStr + Clone + Send + Sync + 'static, + { + let flag = + self.flags.iter().find(|flag| flag.name == name).unwrap_or_else(|| { + panic!("invalid flag '--{name}', did you forget to register it?") + }); + match self.arg_matches.get(name)? { + Some(values) => values + .last() + .as_deref()? + .parse::() + .unwrap_or_else(|err| panic!("failed to parse value for --{name}: {err}")), + None => match flag.default_missing_value.as_deref() { + None => flag + .default_value + .as_deref()? + .parse::() + .unwrap_or_else(|err| panic!("invalid default_value for --{name}: {err}")), + Some(value) => value.parse::().unwrap_or_else(|err| { + panic!("invalid default_missing_value for --{name}: {err}") + }), + }, + } + } + + /// Iterate over values of a specific custom flag + #[cfg(feature = "std")] + pub fn get_flag_values(&self, name: &str) -> Option> + where + T: core::any::Any + Clone + Send + Sync + 'static, + { + self.arg_matches.get_many(name) + } + + /// Get the remaining [clap::ArgMatches] left after parsing the base session configuration + #[cfg(feature = "std")] + pub fn matches(&self) -> &clap::ArgMatches { + &self.arg_matches + } +} + +impl fmt::Debug for CompileFlags { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut map = f.debug_map(); + for id in self.arg_matches.ids() { + use clap::parser::ValueSource; + // Don't print CompilerOptions arg group + if id.as_str() == "CompilerOptions" { + continue; + } + // Don't print default values + if matches!(self.arg_matches.value_source(id.as_str()), Some(ValueSource::DefaultValue)) + { + continue; + } + map.key(&id.as_str()).value_with(|f| { + let mut list = f.debug_list(); + if let Some(occurs) = + self.arg_matches.try_get_raw_occurrences(id.as_str()).expect("expected flag") + { + list.entries(occurs.flatten()); + } + list.finish() + }); + } + map.finish() + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut map = f.debug_map(); + for (name, value) in self.arg_matches.iter() { + let flag = self.flags.iter().find(|flag| flag.name == name).unwrap(); + match value { + Some(values) => { + map.key(&name).value_with(|f| f.debug_list().entries(values.iter()).finish()); + } + None => { + map.key(&name).value(&None::<&str>); + } + } + } + map.finish() + } +} + +/// Generate a fake compile command for use with default options +#[cfg(feature = "std")] +fn fake_compile_command() -> clap::Command { + let cmd = clap::Command::new("compile") + .no_binary_name(true) + .disable_help_flag(true) + .disable_version_flag(true) + .disable_help_subcommand(true); + register_flags(cmd) +} + +/// Register dynamic flags to be shown via `midenc help compile` +#[cfg(feature = "std")] +pub fn register_flags(cmd: clap::Command) -> clap::Command { + inventory::iter::.into_iter().fold(cmd, |cmd, flag| { + let arg = clap::Arg::new(flag.name) + .long(flag.long.unwrap_or(flag.name)) + .action(clap::ArgAction::from(flag.action)); + let arg = if let Some(help) = flag.help { + arg.help(help) + } else { + arg + }; + let arg = if let Some(help_heading) = flag.help_heading { + arg.help_heading(help_heading) + } else { + arg + }; + let arg = if let Some(short) = flag.short { + arg.short(short) + } else { + arg + }; + let arg = if let Some(env) = flag.env { + arg.env(env) + } else { + arg + }; + let arg = if let Some(value) = flag.default_missing_value { + arg.default_missing_value(value) + } else { + arg + }; + let arg = if let Some(value) = flag.default_value { + arg.default_value(value) + } else { + arg + }; + let arg = if let Some(value) = flag.hide { + arg.hide(value) + } else { + arg + }; + cmd.arg(arg) + }) +} diff --git a/midenc-session/src/inputs.rs b/midenc-session/src/inputs.rs index 04f5aa24..a0578ee0 100644 --- a/midenc-session/src/inputs.rs +++ b/midenc-session/src/inputs.rs @@ -1,10 +1,105 @@ +use alloc::{borrow::Cow, format, string::String, vec, vec::Vec}; +use core::fmt; use std::{ ffi::OsStr, - fmt, path::{Path, PathBuf}, }; -use miden_diagnostics::FileName; +#[derive(Clone)] +pub struct FileName { + name: Cow<'static, str>, + is_path: bool, +} +impl Eq for FileName {} +impl PartialEq for FileName { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} +impl PartialOrd for FileName { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for FileName { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.name.cmp(&other.name) + } +} +impl fmt::Debug for FileName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} +impl fmt::Display for FileName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} +#[cfg(feature = "std")] +impl AsRef for FileName { + fn as_ref(&self) -> &std::path::Path { + std::path::Path::new(self.name.as_ref()) + } +} +#[cfg(feature = "std")] +impl From for FileName { + fn from(path: std::path::PathBuf) -> Self { + Self { + name: path.to_string_lossy().into_owned().into(), + is_path: true, + } + } +} +impl From<&'static str> for FileName { + fn from(name: &'static str) -> Self { + Self { + name: Cow::Borrowed(name), + is_path: false, + } + } +} +impl From for FileName { + fn from(name: String) -> Self { + Self { + name: Cow::Owned(name), + is_path: false, + } + } +} +impl AsRef for FileName { + fn as_ref(&self) -> &str { + self.name.as_ref() + } +} +impl FileName { + pub fn is_path(&self) -> bool { + self.is_path + } + + #[cfg(feature = "std")] + pub fn as_path(&self) -> &std::path::Path { + self.as_ref() + } + + pub fn as_str(&self) -> &str { + self.name.as_ref() + } + + #[cfg(feature = "std")] + pub fn file_name(&self) -> Option<&str> { + self.as_path().file_name().and_then(|name| name.to_str()) + } + + #[cfg(not(feature = "std"))] + pub fn file_name(&self) -> Option<&str> { + match self.name.rsplit_once('/') { + Some((_, name)) => Some(name), + None => Some(self.name.as_ref()), + } + } +} /// An error that occurs when detecting the file type of an input #[derive(Debug, thiserror::Error)] @@ -44,7 +139,7 @@ impl InputFile { pub fn empty() -> Self { Self { file: InputType::Stdin { - name: FileName::Virtual("empty".into()), + name: "empty".into(), input: vec![], }, file_type: FileType::Wasm, @@ -88,7 +183,7 @@ impl InputFile { pub fn file_name(&self) -> FileName { match &self.file { - InputType::Real(ref path) => FileName::Real(path.clone()), + InputType::Real(ref path) => path.clone().into(), InputType::Stdin { name, .. } => name.clone(), } } @@ -134,12 +229,10 @@ impl clap::builder::TypedValueParser for InputFileParser { use clap::error::{Error, ErrorKind}; let input_file = match value.to_str() { - Some("-") => InputFile::from_stdin(FileName::Virtual("stdin".into())).map_err( - |err| match err { - InvalidInputError::Io(err) => Error::raw(ErrorKind::Io, err), - err => Error::raw(ErrorKind::ValueValidation, err), - }, - )?, + Some("-") => InputFile::from_stdin("stdin".into()).map_err(|err| match err { + InvalidInputError::Io(err) => Error::raw(ErrorKind::Io, err), + err => Error::raw(ErrorKind::ValueValidation, err), + })?, Some(_) | None => { InputFile::from_path(PathBuf::from(value)).map_err(|err| match err { InvalidInputError::Io(err) => Error::raw(ErrorKind::Io, err), diff --git a/midenc-session/src/lib.rs b/midenc-session/src/lib.rs index 068b576c..6c3b7695 100644 --- a/midenc-session/src/lib.rs +++ b/midenc-session/src/lib.rs @@ -1,22 +1,33 @@ #![feature(debug_closure_helpers)] #![feature(lazy_cell)] +#![feature(error_in_core)] +#![no_std] + extern crate alloc; +#[cfg(feature = "std")] +extern crate std; +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + vec::Vec, +}; +mod color; pub mod diagnostics; +#[cfg(feature = "std")] mod duration; mod emit; +mod emitter; pub mod flags; mod inputs; mod libs; mod options; mod outputs; +#[cfg(feature = "std")] mod statistics; -use std::{ - fmt, - path::{Path, PathBuf}, - sync::Arc, -}; +use alloc::{fmt, sync::Arc}; +use std::path::{Path, PathBuf}; /// The version associated with the current compiler toolchain pub const MIDENC_BUILD_VERSION: &str = env!("MIDENC_BUILD_VERSION"); @@ -28,10 +39,11 @@ use clap::ValueEnum; use midenc_hir_symbol::Symbol; pub use self::{ + color::ColorChoice, diagnostics::{DiagnosticsHandler, Emitter, SourceManager}, duration::HumanDuration, emit::Emit, - flags::{CompileFlag, FlagAction}, + flags::{CompileFlag, CompileFlags, FlagAction}, inputs::{FileType, InputFile, InputType, InvalidInputError}, libs::{LibraryKind, LinkLibrary}, options::*, @@ -78,6 +90,7 @@ pub struct Session { /// The outputs to be produced by the compiler during compilation pub output_files: OutputFiles, /// Statistics gathered from the current compiler session + #[cfg(feature = "std")] pub statistics: Statistics, } @@ -89,7 +102,6 @@ impl fmt::Debug for Session { .field("options", &self.options) .field("inputs", &inputs) .field("output_files", &self.output_files) - .field("statistics", &self.statistics) .finish_non_exhaustive() } } @@ -180,12 +192,12 @@ impl Session { }, ) => { let name = name.as_str(); - if matches!(name, Some("empty") | Some("stdin")) { + if matches!(name, "empty" | "stdin") { options .current_dir .file_stem() .and_then(|stem| stem.to_str()) - .unwrap_or(name.unwrap()) + .unwrap_or(name) .to_string() } else { input.filestem().to_owned() @@ -220,28 +232,28 @@ impl Session { } #[doc(hidden)] - pub fn with_arg_matches(mut self, matches: clap::ArgMatches) -> Self { - self.options.set_arg_matches(matches); + pub fn with_output_type(mut self, ty: OutputType, path: Option) -> Self { + self.output_files.outputs.insert(ty, path.clone()); + self.options.output_types.insert(ty, path.clone()); self } #[doc(hidden)] - pub fn with_output_type(mut self, ty: OutputType, path: Option) -> Self { - self.output_files.outputs.insert(ty, path.clone()); - self.options.output_types.insert(ty, path.clone()); + pub fn with_extra_flags(mut self, flags: CompileFlags) -> Self { + self.options.set_extra_flags(flags); self } /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse` #[inline] pub fn get_flag(&self, name: &str) -> bool { - self.options.get_flag(name) + self.options.flags.get_flag(name) } /// Get the count of a specific custom flag with action `FlagAction::Count` #[inline] pub fn get_flag_count(&self, name: &str) -> usize { - self.options.get_flag_count(name) + self.options.flags.get_flag_count(name) } /// Get the value of a specific custom flag @@ -250,22 +262,24 @@ impl Session { where T: core::any::Any + Clone + Send + Sync + 'static, { - self.options.get_flag_value(name) + self.options.flags.get_flag_value(name) } /// Iterate over values of a specific custom flag #[inline] + #[cfg(feature = "std")] pub fn get_flag_values(&self, name: &str) -> Option> where T: core::any::Any + Clone + Send + Sync + 'static, { - self.options.get_flag_values(name) + self.options.flags.get_flag_values(name) } /// Get the remaining [clap::ArgMatches] left after parsing the base session configuration #[inline] + #[cfg(feature = "std")] pub fn matches(&self) -> &clap::ArgMatches { - self.options.matches() + self.options.flags.matches() } /// The name of this session (used as the name of the project, output file, etc.) @@ -287,9 +301,10 @@ impl Session { fn check_file_is_writeable(&self, file: &Path) { if let Ok(m) = file.metadata() { if m.permissions().readonly() { - self.diagnostics - .fatal(format!("file is not writeable: {}", file.display())) - .raise(); + panic!( + "Compiler exited with a fatal error: file is not writeable: {}", + file.display() + ); } } } @@ -377,7 +392,8 @@ impl Session { } /// This enum describes the different target environments targetable by the compiler -#[derive(Debug, Copy, Clone, Default, ValueEnum)] +#[derive(Debug, Copy, Clone, Default)] +#[cfg_attr(feature = "std", derive(ValueEnum))] pub enum TargetEnv { /// The emulator environment, which has a more restrictive instruction set Emu, diff --git a/midenc-session/src/libs.rs b/midenc-session/src/libs.rs index 136141d0..e55847fb 100644 --- a/midenc-session/src/libs.rs +++ b/midenc-session/src/libs.rs @@ -1,9 +1,8 @@ +use alloc::{borrow::Cow, boxed::Box, format, str::FromStr, string::ToString}; use core::fmt; use std::{ - borrow::Cow, ffi::OsStr, path::{Path, PathBuf}, - str::FromStr, sync::LazyLock, }; diff --git a/midenc-session/src/options/mod.rs b/midenc-session/src/options/mod.rs index 3779e7a0..a2b17c1f 100644 --- a/midenc-session/src/options/mod.rs +++ b/midenc-session/src/options/mod.rs @@ -1,18 +1,13 @@ -use std::{ - fmt, - path::{Path, PathBuf}, - str::FromStr, - sync::Arc, -}; - -use clap::ValueEnum; +use alloc::{fmt, str::FromStr, string::String, sync::Arc, vec, vec::Vec}; +use std::path::{Path, PathBuf}; use crate::{ - diagnostics::{ColorChoice, DiagnosticsConfig, Emitter}, - LinkLibrary, OutputTypes, ProjectType, TargetEnv, + diagnostics::{DiagnosticsConfig, Emitter}, + ColorChoice, CompileFlags, LinkLibrary, OutputTypes, ProjectType, TargetEnv, }; /// This struct contains all of the configuration options for the compiler +#[derive(Debug)] pub struct Options { /// The name of the program being compiled pub name: Option, @@ -56,63 +51,7 @@ pub struct Options { pub save_temps: bool, /// We store any leftover argument matches in the session options for use /// by any downstream crates that register custom flags - arg_matches: clap::ArgMatches, -} - -impl fmt::Debug for Options { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Options") - .field("name", &self.name) - .field("project_type", &self.project_type) - .field("entrypoint", &self.entrypoint) - .field("target", &self.target) - .field("optimize", &self.optimize) - .field("debug", &self.debug) - .field("output_types", &self.output_types) - .field("search_paths", &self.search_paths) - .field("link_libraries", &self.link_libraries) - .field("sysroot", &self.sysroot) - .field("color", &self.color) - .field("diagnostics", &self.diagnostics) - .field("current_dir", &self.current_dir) - .field("parse_only", &self.parse_only) - .field("analyze_only", &self.analyze_only) - .field("link_only", &self.link_only) - .field("no_link", &self.no_link) - .field("save_temps", &self.save_temps) - .field("print_ir_after_all", &self.print_ir_after_all) - .field("print_ir_after_pass", &self.print_ir_after_pass) - .field_with("extra_arguments", |f| { - let mut map = f.debug_map(); - for id in self.arg_matches.ids() { - use clap::parser::ValueSource; - // Don't print CompilerOptions arg group - if id.as_str() == "CompilerOptions" { - continue; - } - // Don't print default values - if matches!( - self.arg_matches.value_source(id.as_str()), - Some(ValueSource::DefaultValue) - ) { - continue; - } - map.key(&id.as_str()).value_with(|f| { - let mut list = f.debug_list(); - if let Some(occurs) = self - .arg_matches - .try_get_raw_occurrences(id.as_str()) - .expect("expected flag") - { - list.entries(occurs.flatten()); - } - list.finish() - }); - } - map.finish() - }) - .finish() - } + pub flags: CompileFlags, } impl Default for Options { @@ -141,8 +80,6 @@ impl Options { }) }); - let arg_matches = crate::flags::default_arg_matches(None::<&str>).unwrap(); - Self { name, target, @@ -164,7 +101,7 @@ impl Options { save_temps: false, print_ir_after_all: false, print_ir_after_pass: vec![], - arg_matches, + flags: CompileFlags::default(), } } @@ -204,14 +141,14 @@ impl Options { } #[doc(hidden)] - pub fn with_arg_matches(mut self, matches: clap::ArgMatches) -> Self { - self.arg_matches = matches; + pub fn with_extra_flags(mut self, flags: CompileFlags) -> Self { + self.flags = flags; self } #[doc(hidden)] - pub fn set_arg_matches(&mut self, matches: clap::ArgMatches) { - self.arg_matches = matches; + pub fn set_extra_flags(&mut self, flags: CompileFlags) { + self.flags = flags; } /// Get a new [Emitter] based on the current options. @@ -241,41 +178,11 @@ impl Options { pub fn emit_debug_assertions(&self) -> bool { self.debug != DebugInfo::None && matches!(self.optimize, OptLevel::None | OptLevel::Basic) } - - /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse` - pub fn get_flag(&self, name: &str) -> bool { - self.arg_matches.get_flag(name) - } - - /// Get the count of a specific custom flag with action `FlagAction::Count` - pub fn get_flag_count(&self, name: &str) -> usize { - self.arg_matches.get_count(name) as usize - } - - /// Get the value of a specific custom flag - pub fn get_flag_value(&self, name: &str) -> Option<&T> - where - T: core::any::Any + Clone + Send + Sync + 'static, - { - self.arg_matches.get_one(name) - } - - /// Iterate over values of a specific custom flag - pub fn get_flag_values(&self, name: &str) -> Option> - where - T: core::any::Any + Clone + Send + Sync + 'static, - { - self.arg_matches.get_many(name) - } - - /// Get the remaining [clap::ArgMatches] left after parsing the base session configuration - pub fn matches(&self) -> &clap::ArgMatches { - &self.arg_matches - } } /// This enum describes the degree to which compiled programs will be optimized -#[derive(Debug, Copy, Clone, Default, ValueEnum)] +#[derive(Debug, Copy, Clone, Default)] +#[cfg_attr(feature = "std", derive(clap::ValueEnum))] pub enum OptLevel { /// No optimizations at all None, @@ -293,7 +200,8 @@ pub enum OptLevel { } /// This enum describes what type of debugging information to emit in compiled programs -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, ValueEnum)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(clap::ValueEnum))] pub enum DebugInfo { /// Do not emit debug info in the final output None, @@ -305,7 +213,8 @@ pub enum DebugInfo { } /// This enum represents the behavior of the compiler with regard to warnings -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, ValueEnum)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(clap::ValueEnum))] pub enum Warnings { /// Disable all warnings None, @@ -338,7 +247,8 @@ impl FromStr for Warnings { } /// This enum represents the type of messages produced by the compiler during execution -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "std", derive(clap::ValueEnum))] pub enum Verbosity { /// Emit additional debug/trace information during compilation Debug, diff --git a/midenc-session/src/outputs.rs b/midenc-session/src/outputs.rs index a40d77f0..3ba06ce8 100644 --- a/midenc-session/src/outputs.rs +++ b/midenc-session/src/outputs.rs @@ -1,13 +1,11 @@ +use alloc::{ + borrow::ToOwned, boxed::Box, collections::BTreeMap, fmt, format, str::FromStr, string::String, +}; use std::{ - collections::BTreeMap, ffi::OsStr, - fmt, path::{Path, PathBuf}, - str::FromStr, }; -use clap::ValueEnum; - /// The type of output to produce for a given [OutputType], when multiple options are available #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum OutputMode { @@ -18,7 +16,8 @@ pub enum OutputMode { } /// This enum represents the type of outputs the compiler can produce -#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "std", derive(clap::ValueEnum))] pub enum OutputType { /// The compiler will emit the parse tree of the input, if applicable Ast, @@ -126,13 +125,20 @@ impl OutputFile { matches!(self, Self::Stdout) } + #[cfg(feature = "std")] pub fn is_tty(&self) -> bool { + use std::io::IsTerminal; match self { Self::Real(_) => false, - Self::Stdout => atty::is(atty::Stream::Stdout), + Self::Stdout => std::io::stdout().is_terminal(), } } + #[cfg(not(feature = "std"))] + pub fn is_tty(&self) -> bool { + false + } + pub fn as_path(&self) -> Option<&Path> { match self { Self::Real(ref path) => Some(path.as_ref()), diff --git a/midenc/Cargo.toml b/midenc/Cargo.toml index 8e44c2c2..dafc3228 100644 --- a/midenc/Cargo.toml +++ b/midenc/Cargo.toml @@ -16,4 +16,4 @@ edition.workspace = true [dependencies] env_logger.workspace = true human-panic = "2.0" -midenc-driver.workspace = true +midenc-driver = { workspace = true, features = ["all"] } diff --git a/sdk/base-sys/Cargo.toml b/sdk/base-sys/Cargo.toml index 51594b44..c95d8410 100644 --- a/sdk/base-sys/Cargo.toml +++ b/sdk/base-sys/Cargo.toml @@ -24,6 +24,6 @@ miden-assembly.workspace = true default = [] # User facing Rust bindings -"bindings" = ["miden-stdlib-sys"] +"bindings" = ["dep:miden-stdlib-sys"] # MASL library for Miden rollup (tx kernel, etc.) used by the compiler in the link phase -"masl-lib" = [] \ No newline at end of file +"masl-lib" = [] diff --git a/sdk/base-sys/src/lib.rs b/sdk/base-sys/src/lib.rs index f088d34d..c716a301 100644 --- a/sdk/base-sys/src/lib.rs +++ b/sdk/base-sys/src/lib.rs @@ -1,5 +1,5 @@ // Enable no_std for the bindings module -#![cfg_attr(feature = "bindings", no_std)] +#![no_std] #[cfg(feature = "bindings")] pub mod bindings;