[sc-503] Refactor error passing

This commit is contained in:
Nicolas Abril 2024-03-11 17:19:03 +01:00
parent d788c0a484
commit 3cc5a7a242
172 changed files with 1162 additions and 1188 deletions

View File

@ -13,7 +13,7 @@ cd hvm-lang
Install using cargo:
```bash
cargo install --path .
cargo install --path . --locked
```
## Usage

View File

@ -26,9 +26,3 @@ audit:
install:
cargo install --locked cargo-sort cargo-audit
llcheck:
cargo watch -x llcheck
extras:
cargo install --locked cargo-watch cargo-limit amber

View File

@ -1,16 +1,4 @@
use crate::term::{
check::{
repeated_bind::RepeatedBindWarn, set_entrypoint::EntryErr, shared_names::TopLevelErr,
unbound_pats::UnboundCtrErr, unbound_vars::UnboundVarErr,
},
display::DisplayFn,
transform::{
apply_args::PatternArgError, encode_pattern_matching::MatchErr, resolve_refs::ReferencedMainErr,
simplify_ref_to_ref::CyclicDefErr,
},
Name,
};
use itertools::Itertools;
use crate::term::{display::DisplayFn, Name};
use std::{
collections::BTreeMap,
fmt::{Display, Formatter},
@ -19,40 +7,129 @@ use std::{
pub const ERR_INDENT_SIZE: usize = 2;
#[derive(Debug, Clone, Default)]
pub struct Info {
pub struct Diagnostics {
err_counter: usize,
book_errs: Vec<Error>,
rule_errs: BTreeMap<Name, Vec<Error>>,
pub warns: Warnings,
pub diagnostics: BTreeMap<DiagnosticOrigin, Vec<Diagnostic>>,
config: DiagnosticsConfig,
}
impl Info {
pub fn error<E: Into<Error>>(&mut self, e: E) {
self.err_counter += 1;
self.book_errs.push(e.into())
#[derive(Debug, Clone, Copy)]
pub struct DiagnosticsConfig {
pub verbose: bool,
pub match_only_vars: Severity,
pub unused_definition: Severity,
pub repeated_bind: Severity,
pub mutual_recursion_cycle: Severity,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
message: String,
severity: Severity,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum DiagnosticOrigin {
/// An error from the relationship between multiple top-level definitions.
Book,
/// An error in a pattern-matching function definition rule.
Rule(Name),
/// An error in a compiled inet.
Inet(String),
/// An error during readback of hvm-core run results.
Readback,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Severity {
Error,
Warning,
Allow,
}
#[derive(Debug, Clone, Copy)]
pub enum WarningType {
MatchOnlyVars,
UnusedDefinition,
RepeatedBind,
MutualRecursionCycle,
}
pub trait ToStringVerbose {
fn to_string_verbose(&self, verbose: bool) -> String;
}
impl Diagnostics {
pub fn new(config: DiagnosticsConfig) -> Self {
Self { err_counter: 0, diagnostics: Default::default(), config }
}
pub fn def_error<E: Into<Error>>(&mut self, name: Name, e: E) {
pub fn add_book_error(&mut self, err: impl ToStringVerbose) {
self.err_counter += 1;
let entry = self.rule_errs.entry(name).or_default();
entry.push(e.into());
self.add_diagnostic(err, Severity::Error, DiagnosticOrigin::Book);
}
pub fn take_err<T, E: Into<Error>>(&mut self, result: Result<T, E>, def_name: Option<&Name>) -> Option<T> {
pub fn add_rule_error(&mut self, err: impl ToStringVerbose, def_name: Name) {
self.err_counter += 1;
self.add_diagnostic(err, Severity::Error, DiagnosticOrigin::Rule(def_name));
}
pub fn add_inet_error(&mut self, err: impl ToStringVerbose, def_name: String) {
self.err_counter += 1;
self.add_diagnostic(err, Severity::Error, DiagnosticOrigin::Inet(def_name));
}
pub fn add_rule_warning(&mut self, warn: impl ToStringVerbose, warn_type: WarningType, def_name: Name) {
let severity = self.config.warning_severity(warn_type);
if severity == Severity::Error {
self.err_counter += 1;
}
self.add_diagnostic(warn, severity, DiagnosticOrigin::Rule(def_name));
}
pub fn add_book_warning(&mut self, warn: impl ToStringVerbose, warn_type: WarningType) {
let severity = self.config.warning_severity(warn_type);
if severity == Severity::Error {
self.err_counter += 1;
}
self.add_diagnostic(warn, severity, DiagnosticOrigin::Book);
}
pub fn add_diagnostic(&mut self, msg: impl ToStringVerbose, severity: Severity, orig: DiagnosticOrigin) {
let diag = Diagnostic { message: msg.to_string_verbose(self.config.verbose), severity };
self.diagnostics.entry(orig).or_default().push(diag)
}
pub fn take_rule_err<T, E: ToStringVerbose>(&mut self, result: Result<T, E>, def_name: Name) -> Option<T> {
match result {
Ok(t) => Some(t),
Err(e) => {
match def_name {
None => self.error(e),
Some(def) => self.def_error(def.clone(), e),
}
self.add_rule_error(e, def_name);
None
}
}
}
pub fn take_inet_err<T, E: ToStringVerbose>(
&mut self,
result: Result<T, E>,
def_name: String,
) -> Option<T> {
match result {
Ok(t) => Some(t),
Err(e) => {
self.add_inet_error(e, def_name);
None
}
}
}
pub fn has_severity(&self, severity: Severity) -> bool {
self.diagnostics.values().any(|errs| errs.iter().any(|e| e.severity == severity))
}
pub fn has_errors(&self) -> bool {
!(self.book_errs.is_empty() && self.rule_errs.is_empty())
self.has_severity(Severity::Error)
}
/// Resets the internal counter
@ -63,172 +140,129 @@ impl Info {
/// Checks if any error was emitted since the start of the pass,
/// Returning all the current information as a `Err(Info)`, replacing `&mut self` with an empty one.
/// Otherwise, returns the given arg as an `Ok(T)`.
pub fn fatal<T>(&mut self, t: T) -> Result<T, Info> {
pub fn fatal<T>(&mut self, t: T) -> Result<T, Diagnostics> {
if self.err_counter == 0 { Ok(t) } else { Err(std::mem::take(self)) }
}
pub fn warning<W: Into<WarningType>>(&mut self, def_name: Name, warning: W) {
self.warns.0.entry(def_name).or_default().push(warning.into());
}
pub fn display(&self, verbose: bool) -> impl Display + '_ {
/// Returns a Display that prints the diagnostics with one of the given severities.
pub fn display_with_severity(&self, severity: Severity) -> impl std::fmt::Display + '_ {
DisplayFn(move |f| {
writeln!(f, "{}", self.book_errs.iter().map(|err| err.display(verbose)).join("\n"))?;
for (def_name, errs) in &self.rule_errs {
in_definition(def_name, f)?;
for err in errs {
writeln!(f, "{:ERR_INDENT_SIZE$}{}", "", err.display(verbose))?;
}
fn filter<'a>(
errs: impl IntoIterator<Item = &'a Diagnostic>,
severity: Severity,
) -> impl Iterator<Item = &'a Diagnostic> {
errs.into_iter().filter(move |err| err.severity == severity)
}
let mut has_msg = false;
for (orig, errs) in &self.diagnostics {
let mut errs = filter(errs, severity).peekable();
if errs.peek().is_some() {
match orig {
DiagnosticOrigin::Book => {
for err in errs {
writeln!(f, "{err}")?;
}
}
DiagnosticOrigin::Rule(nam) => {
writeln!(f, "In definition '{nam}':")?;
for err in errs {
writeln!(f, "{:ERR_INDENT_SIZE$}{err}", "")?;
}
}
DiagnosticOrigin::Inet(nam) => {
writeln!(f, "In compiled inet '{nam}':")?;
for err in errs {
writeln!(f, "{:ERR_INDENT_SIZE$}{err}", "")?;
}
}
DiagnosticOrigin::Readback => {
writeln!(f, "During readback:")?;
for err in errs {
writeln!(f, "{:ERR_INDENT_SIZE$}{err}", "")?;
}
}
}
has_msg = true;
}
}
if has_msg {
writeln!(f)?;
}
Ok(())
})
}
}
fn in_definition(def_name: &Name, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
writeln!(f, "In definition '{def_name}':")
}
impl Display for Info {
impl Display for Diagnostics {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display(false))
}
}
impl From<String> for Info {
fn from(value: String) -> Self {
Info { book_errs: vec![Error::Custom(value)], ..Default::default() }
}
}
impl From<&str> for Info {
fn from(value: &str) -> Self {
Info::from(value.to_string())
}
}
#[derive(Debug, Clone)]
pub enum Error {
MainRef(ReferencedMainErr),
Match(MatchErr),
UnboundVar(UnboundVarErr),
UnboundCtr(UnboundCtrErr),
Cyclic(CyclicDefErr),
EntryPoint(EntryErr),
TopLevel(TopLevelErr),
Custom(String),
PatternArgError(PatternArgError),
RepeatedBind(RepeatedBindWarn),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display(false))
}
}
impl Error {
pub fn display(&self, verbose: bool) -> impl Display + '_ {
DisplayFn(move |f| match self {
Error::Match(err) => write!(f, "{}", err.display(verbose)),
Error::UnboundVar(err) => write!(f, "{err}"),
Error::UnboundCtr(err) => write!(f, "{err}"),
Error::MainRef(err) => write!(f, "{err}"),
Error::Cyclic(err) => write!(f, "{err}"),
Error::EntryPoint(err) => write!(f, "{err}"),
Error::TopLevel(err) => write!(f, "{err}"),
Error::Custom(err) => write!(f, "{err}"),
Error::PatternArgError(err) => write!(f, "{err}"),
Error::RepeatedBind(err) => write!(f, "{err}"),
})
}
}
impl From<ReferencedMainErr> for Error {
fn from(value: ReferencedMainErr) -> Self {
Self::MainRef(value)
}
}
impl From<MatchErr> for Error {
fn from(value: MatchErr) -> Self {
Self::Match(value)
}
}
impl From<UnboundVarErr> for Error {
fn from(value: UnboundVarErr) -> Self {
Self::UnboundVar(value)
}
}
impl From<UnboundCtrErr> for Error {
fn from(value: UnboundCtrErr) -> Self {
Self::UnboundCtr(value)
}
}
impl From<CyclicDefErr> for Error {
fn from(value: CyclicDefErr) -> Self {
Self::Cyclic(value)
}
}
impl From<EntryErr> for Error {
fn from(value: EntryErr) -> Self {
Self::EntryPoint(value)
}
}
impl From<TopLevelErr> for Error {
fn from(value: TopLevelErr) -> Self {
Self::TopLevel(value)
}
}
impl From<PatternArgError> for Error {
fn from(value: PatternArgError) -> Self {
Self::PatternArgError(value)
}
}
#[derive(Debug, Clone, Default)]
pub struct Warnings(pub BTreeMap<Name, Vec<WarningType>>);
#[derive(Debug, Clone)]
pub enum WarningType {
MatchOnlyVars,
UnusedDefinition,
RepeatedBind(RepeatedBindWarn),
}
impl Display for WarningType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WarningType::MatchOnlyVars => write!(f, "Match expression at definition only uses var patterns."),
WarningType::UnusedDefinition => write!(f, "Definition is unused."),
WarningType::RepeatedBind(warn) => write!(f, "{warn}"),
if self.has_severity(Severity::Warning) {
write!(f, "Warnings:\n{}", self.display_with_severity(Severity::Warning))?;
}
}
}
impl Display for Warnings {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for (def_name, warns) in &self.0 {
in_definition(def_name, f)?;
for warn in warns {
writeln!(f, "{:ERR_INDENT_SIZE$}{}", "", warn)?;
}
if self.has_severity(Severity::Error) {
write!(f, "Errors:\n{}", self.display_with_severity(Severity::Error))?;
}
Ok(())
}
}
impl From<RepeatedBindWarn> for WarningType {
fn from(value: RepeatedBindWarn) -> Self {
Self::RepeatedBind(value)
impl From<String> for Diagnostics {
fn from(value: String) -> Self {
Self {
diagnostics: BTreeMap::from_iter([(DiagnosticOrigin::Book, vec![Diagnostic {
message: value,
severity: Severity::Error,
}])]),
..Default::default()
}
}
}
impl DiagnosticsConfig {
pub fn new(severity: Severity, verbose: bool) -> Self {
Self {
match_only_vars: severity,
unused_definition: severity,
repeated_bind: severity,
mutual_recursion_cycle: severity,
verbose,
}
}
pub fn warning_severity(&self, warn: WarningType) -> Severity {
match warn {
WarningType::MatchOnlyVars => self.match_only_vars,
WarningType::UnusedDefinition => self.unused_definition,
WarningType::RepeatedBind => self.repeated_bind,
WarningType::MutualRecursionCycle => self.mutual_recursion_cycle,
}
}
}
impl Default for DiagnosticsConfig {
fn default() -> Self {
Self::new(Severity::Warning, false)
}
}
impl Diagnostic {
pub fn error(msg: impl ToString) -> Self {
Diagnostic { message: msg.to_string(), severity: Severity::Error }
}
pub fn warning(msg: impl ToString) -> Self {
Diagnostic { message: msg.to_string(), severity: Severity::Warning }
}
}
impl Display for Diagnostic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl ToStringVerbose for &str {
fn to_string_verbose(&self, _verbose: bool) -> String {
self.to_string()
}
}

View File

@ -1,3 +1,4 @@
use crate::diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE};
use hvmc::ast::{Book, Tree};
use indexmap::{IndexMap, IndexSet};
use std::fmt::Debug;
@ -9,21 +10,27 @@ type RefSet = IndexSet<Ref>;
#[derive(Default)]
pub struct Graph(IndexMap<Ref, RefSet>);
pub fn check_cycles(book: &Book) -> Result<(), String> {
pub fn check_cycles(book: &Book, diagnostics: &mut Diagnostics) -> Result<(), Diagnostics> {
diagnostics.start_pass();
let graph = Graph::from(book);
let cycles = graph.cycles();
if cycles.is_empty() {
Ok(())
} else {
Err(format!(
"{}\n{}\n{}\n{}",
if !cycles.is_empty() {
let msg = format!(
"{}\n{}\n{:ERR_INDENT_SIZE$}{}\n{:ERR_INDENT_SIZE$}{}",
"Mutual recursion cycle detected in compiled functions:",
pretty_print_cycles(&cycles),
"",
"This program will expand infinitely in strict evaluation mode.",
"",
"Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information."
))
);
diagnostics.add_book_warning(msg.as_str(), WarningType::MutualRecursionCycle);
}
diagnostics.fatal(())
}
fn pretty_print_cycles(cycles: &[Vec<Ref>]) -> String {
@ -32,7 +39,7 @@ fn pretty_print_cycles(cycles: &[Vec<Ref>]) -> String {
.enumerate()
.map(|(i, cycle)| {
let cycle_str = cycle.iter().chain(cycle.first()).cloned().collect::<Vec<_>>().join(" -> ");
format!("Cycle {}: {}", 1 + i, cycle_str)
format!("{:ERR_INDENT_SIZE$}Cycle {}: {}", "", 1 + i, cycle_str)
})
.collect::<Vec<String>>()
.join("\n")

View File

@ -1,7 +1,7 @@
#![feature(box_patterns)]
#![feature(let_chains)]
use diagnostics::{Info, WarningType, Warnings};
use diagnostics::{DiagnosticOrigin, Diagnostics, DiagnosticsConfig, Severity};
use hvmc::{
ast::{self, Net},
dispatch_dyn_net,
@ -16,10 +16,7 @@ use std::{
sync::{Arc, Mutex},
time::Instant,
};
use term::{
book_to_nets, display::display_readback_errors, net_to_term::net_to_term, term_to_net::Labels, AdtEncoding,
Book, Ctx, ReadbackError, Term,
};
use term::{book_to_nets, net_to_term::net_to_term, term_to_net::Labels, AdtEncoding, Book, Ctx, Term};
pub mod diagnostics;
pub mod hvmc_net;
@ -52,7 +49,8 @@ pub fn create_host(book: Arc<Book>, labels: Arc<Labels>, adt_encoding: AdtEncodi
let tree = host.readback_tree(&wire);
let net = hvmc::ast::Net { root: tree, redexes: vec![] };
let (term, errs) = readback_hvmc(&net, &book, &labels, false, adt_encoding);
println!("{}{}", display_readback_errors(&errs), term);
eprint!("{errs}");
println!("{term}");
}
}))),
);
@ -79,37 +77,42 @@ pub fn create_host(book: Arc<Book>, labels: Arc<Labels>, adt_encoding: AdtEncodi
host
}
pub fn check_book(book: &mut Book) -> Result<(), Info> {
pub fn check_book(book: &mut Book) -> Result<(), Diagnostics> {
// TODO: Do the checks without having to do full compilation
// TODO: Shouldn't the check mode show warnings?
compile_book(book, true, CompileOpts::light(), None)?;
let res = compile_book(book, CompileOpts::light(), DiagnosticsConfig::new(Severity::Warning, false), None)?;
print!("{}", res.diagnostics);
Ok(())
}
pub fn compile_book(
book: &mut Book,
lazy_mode: bool,
opts: CompileOpts,
diagnostics_cfg: DiagnosticsConfig,
args: Option<Vec<Term>>,
) -> Result<CompileResult, Info> {
let warns = desugar_book(book, opts, args)?;
) -> Result<CompileResult, Diagnostics> {
let mut diagnostics = desugar_book(book, opts, diagnostics_cfg, args)?;
let (nets, labels) = book_to_nets(book);
let mut core_book = nets_to_hvmc(nets)?;
let mut core_book = nets_to_hvmc(nets, &mut diagnostics)?;
if opts.pre_reduce {
core_book.pre_reduce(&|x| x == book.hvmc_entrypoint(), 1 << 24, 100_000)?;
}
if opts.prune {
prune_defs(&mut core_book, book.hvmc_entrypoint().to_string());
}
if !lazy_mode {
mutual_recursion::check_cycles(&core_book)?;
}
Ok(CompileResult { core_book, labels, warns })
mutual_recursion::check_cycles(&core_book, &mut diagnostics)?;
Ok(CompileResult { core_book, labels, diagnostics })
}
pub fn desugar_book(book: &mut Book, opts: CompileOpts, args: Option<Vec<Term>>) -> Result<Warnings, Info> {
let mut ctx = Ctx::new(book);
pub fn desugar_book(
book: &mut Book,
opts: CompileOpts,
diagnostics_cfg: DiagnosticsConfig,
args: Option<Vec<Term>>,
) -> Result<Diagnostics, Diagnostics> {
let mut ctx = Ctx::new(book, diagnostics_cfg);
ctx.check_shared_names();
ctx.set_entrypoint();
@ -136,7 +139,7 @@ pub fn desugar_book(book: &mut Book, opts: CompileOpts, args: Option<Vec<Term>>)
ctx.simplify_matches()?;
if opts.linearize_matches.enabled() {
ctx.linearize_simple_matches(opts.linearize_matches.is_extra())?;
ctx.book.linearize_simple_matches(opts.linearize_matches.is_extra());
}
ctx.book.encode_simple_matches(opts.adt_encoding);
@ -173,19 +176,24 @@ pub fn desugar_book(book: &mut Book, opts: CompileOpts, args: Option<Vec<Term>>)
ctx.book.merge_definitions();
}
if !ctx.info.has_errors() { Ok(ctx.info.warns) } else { Err(ctx.info) }
if !ctx.info.has_errors() { Ok(ctx.info) } else { Err(ctx.info) }
}
pub fn run_book(
mut book: Book,
mem_size: usize,
max_memory: usize,
run_opts: RunOpts,
warning_opts: WarningOpts,
compile_opts: CompileOpts,
diagnostics_cfg: DiagnosticsConfig,
args: Option<Vec<Term>>,
) -> Result<(Term, RunInfo), Info> {
let CompileResult { core_book, labels, warns } =
compile_book(&mut book, run_opts.lazy_mode, compile_opts, args)?;
) -> Result<(Term, RunInfo), Diagnostics> {
let CompileResult { core_book, labels, diagnostics } =
compile_book(&mut book, compile_opts, diagnostics_cfg, args)?;
// TODO: Printing should be taken care by the cli module, but we'd
// like to print any warnings before running so that the user can
// cancel the run if a problem is detected.
eprint!("{diagnostics}");
// Turn the book into an Arc so that we can use it for logging, debugging, etc.
// from anywhere else in the program
@ -193,19 +201,17 @@ pub fn run_book(
let book = Arc::new(book);
let labels = Arc::new(labels);
display_warnings(warns, warning_opts)?;
// Run
let debug_hook = run_opts.debug_hook(&book, &labels);
let host = create_host(book.clone(), labels.clone(), compile_opts.adt_encoding);
host.lock().unwrap().insert_book(&core_book);
let (res_lnet, stats) = run_compiled(host, mem_size, run_opts, debug_hook, book.hvmc_entrypoint());
let (res_lnet, stats) = run_compiled(host, max_memory, run_opts, debug_hook, book.hvmc_entrypoint());
let (res_term, readback_errors) =
let (res_term, diagnostics) =
readback_hvmc(&res_lnet, &book, &labels, run_opts.linear, compile_opts.adt_encoding);
let info = RunInfo { stats, readback_errors, net: res_lnet, book, labels };
let info = RunInfo { stats, diagnostics, net: res_lnet, book, labels };
Ok((res_term, info))
}
@ -252,7 +258,7 @@ pub fn run_compiled(
) -> (Net, RunStats) {
let heap = Heap::new_bytes(mem_size);
let mut root = DynNet::new(&heap, run_opts.lazy_mode);
let max_rwts = run_opts.max_rewrites.map(|x| x.clamp(usize::MIN as u64, usize::MAX as u64) as usize);
let max_rwts = run_opts.max_rewrites.map(|x| x.clamp(usize::MIN, usize::MAX));
// Expect won't be reached because there's
// a pass that checks this.
dispatch_dyn_net!(&mut root => {
@ -309,16 +315,19 @@ pub fn readback_hvmc(
labels: &Arc<Labels>,
linear: bool,
adt_encoding: AdtEncoding,
) -> (Term, Vec<ReadbackError>) {
) -> (Term, Diagnostics) {
let mut diags = Diagnostics::default();
let net = hvmc_to_net(net);
let (mut term, mut readback_errors) = net_to_term(&net, book, labels, linear);
let mut term = net_to_term(&net, book, labels, linear, &mut diags);
let resugar_errs = term.resugar_adts(book, adt_encoding);
term.resugar_builtins();
readback_errors.extend(resugar_errs);
for err in resugar_errs {
diags.add_diagnostic(err, Severity::Warning, DiagnosticOrigin::Readback);
}
(term, readback_errors)
(term, diags)
}
#[derive(Clone, Copy, Debug, Default)]
@ -327,8 +336,8 @@ pub struct RunOpts {
pub debug: bool,
pub linear: bool,
pub lazy_mode: bool,
pub max_memory: u64,
pub max_rewrites: Option<u64>,
pub max_memory: usize,
pub max_rewrites: Option<usize>,
}
impl RunOpts {
@ -340,8 +349,10 @@ impl RunOpts {
self.debug.then_some({
|net: &_| {
let net = hvmc_to_net(net);
let (res_term, errors) = net_to_term(&net, book, labels, self.linear);
println!("{}{}\n---------------------------------------", display_readback_errors(&errors), res_term,)
let mut diags = Diagnostics::default();
let res_term = net_to_term(&net, book, labels, self.linear, &mut diags);
eprint!("{diags}");
println!("{}\n---------------------------------------", res_term);
}
})
}
@ -457,101 +468,15 @@ impl CompileOpts {
}
}
#[derive(Default, Clone, Copy)]
pub struct WarningOpts {
pub match_only_vars: WarnState,
pub unused_defs: WarnState,
pub repeated_bind: WarnState,
}
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum WarnState {
#[default]
Warn,
Allow,
Deny,
}
impl WarningOpts {
pub fn allow_all() -> Self {
Self { match_only_vars: WarnState::Allow, unused_defs: WarnState::Allow, repeated_bind: WarnState::Allow }
}
pub fn deny_all() -> Self {
Self { match_only_vars: WarnState::Deny, unused_defs: WarnState::Deny, repeated_bind: WarnState::Deny }
}
pub fn warn_all() -> Self {
Self { match_only_vars: WarnState::Warn, unused_defs: WarnState::Warn, repeated_bind: WarnState::Warn }
}
/// Split warnings into two based on its Warn or Deny WarnState.
pub fn split(&self, warns: Warnings) -> (Warnings, Warnings) {
let mut warn = Warnings::default();
let mut deny = Warnings::default();
warns
.0
.into_iter()
.flat_map(|(def, warns)| warns.into_iter().map(move |warn| (def.clone(), warn)))
.for_each(|(def, w)| {
let ws = match w {
WarningType::MatchOnlyVars => self.match_only_vars,
WarningType::UnusedDefinition => self.unused_defs,
WarningType::RepeatedBind(_) => self.repeated_bind,
};
match ws {
WarnState::Allow => {}
WarnState::Warn => warn.0.entry(def).or_default().push(w),
WarnState::Deny => deny.0.entry(def).or_default().push(w),
};
});
(warn, deny)
}
}
/// Either just prints warnings or returns Err when any denied was produced.
pub fn display_warnings(warnings: Warnings, warning_opts: WarningOpts) -> Result<(), String> {
let (warns, denies) = warning_opts.split(warnings);
if !warns.0.is_empty() {
eprintln!("Warnings:\n{}", warns);
}
if !denies.0.is_empty() {
return Err(format!("{denies}\nCould not run the code because of the previous warnings."));
}
Ok(())
}
pub struct CompileResult {
pub warns: Warnings,
pub diagnostics: Diagnostics,
pub core_book: hvmc::ast::Book,
pub labels: Labels,
}
impl std::fmt::Debug for CompileResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.warns.0.is_empty() {
writeln!(f, "// Warnings:\n{}", self.warns)?;
}
write!(f, "{}", self.core_book)
}
}
impl CompileResult {
pub fn display_with_warns(self, opts: WarningOpts) -> Result<String, String> {
display_warnings(self.warns, opts)?;
Ok(self.core_book.to_string())
}
}
pub struct RunInfo {
pub stats: RunStats,
pub readback_errors: Vec<ReadbackError>,
pub diagnostics: Diagnostics,
pub net: Net,
pub book: Arc<Book>,
pub labels: Arc<Labels>,

View File

@ -1,15 +1,12 @@
use clap::{Args, CommandFactory, Parser, Subcommand};
use hvml::{
check_book, compile_book, desugar_book,
diagnostics::Info,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
load_file_to_book, run_book,
term::{display::display_readback_errors, AdtEncoding, Book, Name},
CompileOpts, OptLevel, RunInfo, RunOpts, WarnState, WarningOpts,
};
use std::{
path::{Path, PathBuf},
vec::IntoIter,
term::{AdtEncoding, Book, Name},
CompileOpts, OptLevel, RunInfo, RunOpts,
};
use std::path::{Path, PathBuf};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
@ -54,10 +51,10 @@ enum Mode {
/// Compiles the program and runs it with the hvm.
Run {
#[arg(short = 'm', long = "mem", help = "How much memory to allocate for the runtime", default_value = "1G", value_parser = mem_parser)]
max_mem: u64,
max_memory: usize,
#[arg(short = 'r', long = "rwts", help = "Maximum amount of rewrites", value_parser = mem_parser)]
max_rwts: Option<u64>,
max_rewrites: Option<usize>,
#[arg(short = 'd', help = "Debug mode (print each reduction step)")]
debug: bool,
@ -98,9 +95,6 @@ enum Mode {
},
/// Runs the lambda-term level desugaring passes.
Desugar {
#[arg(short = 'L', help = "Lazy mode")]
lazy_mode: bool,
#[arg(
short = 'O',
value_delimiter = ' ',
@ -110,6 +104,12 @@ enum Mode {
)]
comp_opts: Vec<OptArgs>,
#[arg(short = 'L', help = "Lazy mode")]
lazy_mode: bool,
#[command(flatten)]
warn_opts: CliWarnOpts,
#[arg(help = "Path to the input file")]
path: PathBuf,
},
@ -146,149 +146,6 @@ struct CliWarnOpts {
pub allows: Vec<WarningArgs>,
}
fn mem_parser(arg: &str) -> Result<u64, String> {
let (base, mult) = match arg.to_lowercase().chars().last() {
None => return Err("Mem size argument is empty".to_string()),
Some('k') => (&arg[0 .. arg.len() - 1], 1 << 10),
Some('m') => (&arg[0 .. arg.len() - 1], 1 << 20),
Some('g') => (&arg[0 .. arg.len() - 1], 1 << 30),
Some(_) => (arg, 1),
};
let base = base.parse::<u64>().map_err(|e| e.to_string())?;
Ok(base * mult)
}
fn main() {
#[cfg(not(feature = "cli"))]
compile_error!("The 'cli' feature is needed for the hvm-lang cli");
let cli = Cli::parse();
let arg_verbose = cli.verbose;
if let Err(e) = execute_cli_mode(cli) {
eprintln!("{}", e.display(arg_verbose))
}
}
fn execute_cli_mode(mut cli: Cli) -> Result<(), Info> {
let arg_verbose = cli.verbose;
let entrypoint = cli.entrypoint.take();
let load_book = |path: &Path| -> Result<Book, Info> {
let mut book = load_file_to_book(path).map_err(Info::from)?;
book.entrypoint = entrypoint;
if arg_verbose {
println!("{book}");
}
Ok(book)
};
match cli.mode {
Mode::Check { path } => {
let mut book = load_book(&path)?;
check_book(&mut book)?;
}
Mode::Compile { path, comp_opts, warn_opts, lazy_mode } => {
let warning_opts = warn_opts.get_warning_opts(WarningOpts::default());
let mut opts = OptArgs::opts_from_cli(&comp_opts);
if lazy_mode {
opts.lazy_mode();
}
let mut book = load_book(&path)?;
let compiled = compile_book(&mut book, lazy_mode, opts, None)?;
println!("{}", compiled.display_with_warns(warning_opts)?);
}
Mode::Desugar { path, comp_opts, lazy_mode } => {
let mut opts = OptArgs::opts_from_cli(&comp_opts);
if lazy_mode {
opts.lazy_mode();
}
let mut book = load_book(&path)?;
// TODO: Shouldn't the desugar have `warn_opts` too? maybe WarningOpts::allow_all() by default
let _warns = desugar_book(&mut book, opts, None)?;
println!("{}", book);
}
Mode::Run {
path,
max_mem,
max_rwts,
debug,
mut single_core,
linear,
arg_stats,
comp_opts,
warn_opts,
lazy_mode,
arguments,
} => {
if debug && lazy_mode {
return Err("Unsupported configuration, can not use debug mode `-d` with lazy mode `-L`".into());
}
let warning_opts = warn_opts.get_warning_opts(WarningOpts::allow_all());
let mut opts = OptArgs::opts_from_cli(&comp_opts);
opts.check(lazy_mode);
if lazy_mode {
single_core = true;
opts.lazy_mode();
}
let book = load_book(&path)?;
let run_opts =
RunOpts { single_core, debug, linear, lazy_mode, max_memory: max_mem, max_rewrites: max_rwts };
let (res_term, RunInfo { stats, readback_errors, net, book: _, labels: _ }) =
run_book(book, max_mem as usize, run_opts, warning_opts, opts, arguments)?;
let total_rewrites = stats.rewrites.total() as f64;
let rps = total_rewrites / stats.run_time / 1_000_000.0;
let size = stats.used;
if cli.verbose {
println!("\n{}", net);
}
println!("{}{}", display_readback_errors(&readback_errors), res_term);
if arg_stats {
println!("\nRWTS : {}", total_rewrites);
println!("- ANNI : {}", stats.rewrites.anni);
println!("- COMM : {}", stats.rewrites.comm);
println!("- ERAS : {}", stats.rewrites.eras);
println!("- DREF : {}", stats.rewrites.dref);
println!("- OPER : {}", stats.rewrites.oper);
println!("TIME : {:.3} s", stats.run_time);
println!("RPS : {:.3} m", rps);
println!("SIZE : {} nodes", size);
}
}
};
Ok(())
}
impl CliWarnOpts {
fn get_warning_opts(self, mut warning_opts: WarningOpts) -> WarningOpts {
let cmd = Cli::command();
let matches = cmd.get_matches();
let subcmd_name = matches.subcommand_name().expect("To have a subcommand");
let arg_matches = matches.subcommand_matches(subcmd_name).expect("To have a subcommand");
if let Some(wopts_id_seq) = arg_matches.get_many::<clap::Id>("CliWarnOpts") {
let allows = &mut self.allows.into_iter();
let denies = &mut self.denies.into_iter();
let warns = &mut self.warns.into_iter();
WarningArgs::wopts_from_cli(&mut warning_opts, wopts_id_seq.collect(), allows, denies, warns);
}
warning_opts
}
}
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum OptArgs {
All,
@ -358,31 +215,195 @@ pub enum WarningArgs {
All,
UnusedDefs,
MatchOnlyVars,
RepeatedBind,
MutualRecursionCycle,
}
impl WarningArgs {
pub fn wopts_from_cli(
wopts: &mut WarningOpts,
wopts_id_seq: Vec<&clap::Id>,
allows: &mut IntoIter<WarningArgs>,
denies: &mut IntoIter<WarningArgs>,
warns: &mut IntoIter<WarningArgs>,
) {
for id in wopts_id_seq {
fn main() {
#[cfg(not(feature = "cli"))]
compile_error!("The 'cli' feature is needed for the hvm-lang cli");
let cli = Cli::parse();
if let Err(diagnostics) = execute_cli_mode(cli) {
eprint!("{diagnostics}")
}
}
fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
let arg_verbose = cli.verbose;
let entrypoint = cli.entrypoint.take();
let load_book = |path: &Path| -> Result<Book, Diagnostics> {
let mut book = load_file_to_book(path)?;
book.entrypoint = entrypoint;
if arg_verbose {
println!("{book}");
}
Ok(book)
};
match cli.mode {
Mode::Check { path } => {
let mut book = load_book(&path)?;
check_book(&mut book)?;
}
Mode::Compile { path, comp_opts, warn_opts, lazy_mode } => {
let diagnostics_cfg = set_warning_cfg_from_cli(
DiagnosticsConfig::new(Severity::Warning, arg_verbose),
lazy_mode,
warn_opts,
);
let mut opts = OptArgs::opts_from_cli(&comp_opts);
if lazy_mode {
opts.lazy_mode();
}
let mut book = load_book(&path)?;
let compile_res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
eprint!("{}", compile_res.diagnostics);
println!("{}", compile_res.core_book);
}
Mode::Desugar { path, comp_opts, warn_opts, lazy_mode } => {
let diagnostics_cfg = set_warning_cfg_from_cli(
DiagnosticsConfig::new(Severity::Warning, arg_verbose),
lazy_mode,
warn_opts,
);
let mut opts = OptArgs::opts_from_cli(&comp_opts);
if lazy_mode {
opts.lazy_mode();
}
let mut book = load_book(&path)?;
let diagnostics = desugar_book(&mut book, opts, diagnostics_cfg, None)?;
eprint!("{diagnostics}");
println!("{book}");
}
Mode::Run {
path,
max_memory,
max_rewrites,
debug,
mut single_core,
linear,
arg_stats,
comp_opts,
warn_opts,
lazy_mode,
arguments,
} => {
if debug && lazy_mode {
return Err(Diagnostics::from(
"Unsupported configuration, can not use debug mode `-d` with lazy mode `-L`".to_string(),
));
}
let diagnostics_cfg =
set_warning_cfg_from_cli(DiagnosticsConfig::new(Severity::Allow, arg_verbose), lazy_mode, warn_opts);
let mut compile_opts = OptArgs::opts_from_cli(&comp_opts);
compile_opts.check(lazy_mode);
if lazy_mode {
single_core = true;
compile_opts.lazy_mode();
}
let run_opts = RunOpts { single_core, debug, linear, lazy_mode, max_memory, max_rewrites };
let book = load_book(&path)?;
let (res_term, RunInfo { stats, diagnostics, net, book: _, labels: _ }) =
run_book(book, max_memory, run_opts, compile_opts, diagnostics_cfg, arguments)?;
let total_rewrites = stats.rewrites.total() as f64;
let rps = total_rewrites / stats.run_time / 1_000_000.0;
let size = stats.used;
if cli.verbose {
println!("{net}");
}
eprint!("{diagnostics}");
println!("{res_term}");
if arg_stats {
println!("\nRWTS : {}", total_rewrites);
println!("- ANNI : {}", stats.rewrites.anni);
println!("- COMM : {}", stats.rewrites.comm);
println!("- ERAS : {}", stats.rewrites.eras);
println!("- DREF : {}", stats.rewrites.dref);
println!("- OPER : {}", stats.rewrites.oper);
println!("TIME : {:.3} s", stats.run_time);
println!("RPS : {:.3} m", rps);
println!("SIZE : {} nodes", size);
}
}
};
Ok(())
}
fn mem_parser(arg: &str) -> Result<usize, String> {
let (base, mult) = match arg.to_lowercase().chars().last() {
None => return Err("Mem size argument is empty".to_string()),
Some('k') => (&arg[0 .. arg.len() - 1], 1 << 10),
Some('m') => (&arg[0 .. arg.len() - 1], 1 << 20),
Some('g') => (&arg[0 .. arg.len() - 1], 1 << 30),
Some(_) => (arg, 1),
};
let base = base.parse::<usize>().map_err(|e| e.to_string())?;
Ok(base * mult)
}
fn set_warning_cfg_from_cli(
mut cfg: DiagnosticsConfig,
lazy_mode: bool,
warn_opts: CliWarnOpts,
) -> DiagnosticsConfig {
fn set(cfg: &mut DiagnosticsConfig, severity: Severity, cli_val: WarningArgs, lazy_mode: bool) {
match cli_val {
WarningArgs::All => {
cfg.unused_definition = severity;
cfg.match_only_vars = severity;
cfg.repeated_bind = severity;
if !lazy_mode {
cfg.mutual_recursion_cycle = severity;
}
}
WarningArgs::UnusedDefs => cfg.unused_definition = severity,
WarningArgs::MatchOnlyVars => cfg.match_only_vars = severity,
WarningArgs::RepeatedBind => cfg.repeated_bind = severity,
WarningArgs::MutualRecursionCycle => cfg.mutual_recursion_cycle = severity,
}
}
if !lazy_mode {
cfg.mutual_recursion_cycle = Severity::Warning;
}
let cmd = Cli::command();
let matches = cmd.get_matches();
let subcmd_name = matches.subcommand_name().expect("To have a subcommand");
let arg_matches = matches.subcommand_matches(subcmd_name).expect("To have a subcommand");
if let Some(warn_opts_ids) = arg_matches.get_many::<clap::Id>("CliWarnOpts") {
let mut allows = warn_opts.allows.into_iter();
let mut warns = warn_opts.warns.into_iter();
let mut denies = warn_opts.denies.into_iter();
for id in warn_opts_ids {
match id.as_ref() {
"allows" => Self::set(wopts, allows.next().unwrap(), WarningOpts::allow_all, WarnState::Allow),
"denies" => Self::set(wopts, denies.next().unwrap(), WarningOpts::deny_all, WarnState::Deny),
"warns" => Self::set(wopts, warns.next().unwrap(), WarningOpts::warn_all, WarnState::Warn),
_ => {}
"allows" => set(&mut cfg, Severity::Allow, allows.next().unwrap(), lazy_mode),
"denies" => set(&mut cfg, Severity::Error, denies.next().unwrap(), lazy_mode),
"warns" => set(&mut cfg, Severity::Warning, warns.next().unwrap(), lazy_mode),
_ => unreachable!(),
}
}
}
fn set(wopts: &mut WarningOpts, val: WarningArgs, all: impl Fn() -> WarningOpts, switch: WarnState) {
match val {
WarningArgs::All => *wopts = all(),
WarningArgs::UnusedDefs => wopts.unused_defs = switch,
WarningArgs::MatchOnlyVars => wopts.match_only_vars = switch,
}
}
cfg
}

View File

@ -1,21 +1,31 @@
use super::{INet, NodeId, NodeKind, Port, ROOT};
use crate::term::num_to_name;
use crate::{
diagnostics::{Diagnostics, ToStringVerbose},
term::num_to_name,
};
use hvmc::ast::{Book, Net, Tree};
use std::collections::{HashMap, HashSet};
pub struct ViciousCycleErr;
/// Converts the inet-encoded definitions into an hvmc AST Book.
pub fn nets_to_hvmc(nets: HashMap<String, INet>) -> Result<Book, String> {
pub fn nets_to_hvmc(nets: HashMap<String, INet>, info: &mut Diagnostics) -> Result<Book, Diagnostics> {
info.start_pass();
let mut book = Book::default();
for (name, inet) in nets {
let net = net_to_hvmc(&inet)?;
book.insert(name, net);
let res = net_to_hvmc(&inet);
if let Some(net) = info.take_inet_err(res, name.clone()) {
book.insert(name, net);
}
}
Ok(book)
info.fatal(book)
}
/// Convert an inet-encoded definition into an hvmc AST inet.
pub fn net_to_hvmc(inet: &INet) -> Result<Net, String> {
pub fn net_to_hvmc(inet: &INet) -> Result<Net, ViciousCycleErr> {
let (net_root, net_redexes) = get_tree_roots(inet)?;
let mut port_to_var_id: HashMap<Port, VarId> = HashMap::new();
let root = if let Some(net_root) = net_root {
@ -98,7 +108,7 @@ type VarId = NodeId;
/// Finds the roots of all the trees in the inet.
/// Returns them as the root of the root tree and the active pairs of the net.
/// Active pairs are found by a right-to-left, depth-first search.
fn get_tree_roots(inet: &INet) -> Result<(Option<NodeId>, Vec<[NodeId; 2]>), String> {
fn get_tree_roots(inet: &INet) -> Result<(Option<NodeId>, Vec<[NodeId; 2]>), ViciousCycleErr> {
let mut redex_roots: Vec<[NodeId; 2]> = vec![];
let mut movements: Vec<Movement> = vec![];
let mut root_set = HashSet::from([ROOT.node()]);
@ -166,7 +176,7 @@ fn explore_side_link(
movements: &mut Vec<Movement>,
redex_roots: &mut Vec<[NodeId; 2]>,
root_set: &mut HashSet<NodeId>,
) -> Result<(), String> {
) -> Result<(), ViciousCycleErr> {
let new_roots = go_up_tree(inet, node_id)?;
// If this is a new tree, explore it downwards
if !root_set.contains(&new_roots[0]) && !root_set.contains(&new_roots[1]) {
@ -181,12 +191,12 @@ fn explore_side_link(
/// Goes up a node tree, starting from some given node.
/// Returns the active pair at the root of this tree.
fn go_up_tree(inet: &INet, start_node: NodeId) -> Result<[NodeId; 2], String> {
fn go_up_tree(inet: &INet, start_node: NodeId) -> Result<[NodeId; 2], ViciousCycleErr> {
let mut explored_nodes = HashSet::new();
let mut cur_node = start_node;
loop {
if !explored_nodes.insert(cur_node) {
return Err("Found term that compiles into an inet with a vicious cycle".to_string());
return Err(ViciousCycleErr);
}
let up = inet.enter_port(Port(cur_node, 0));
@ -198,3 +208,9 @@ fn go_up_tree(inet: &INet, start_node: NodeId) -> Result<[NodeId; 2], String> {
cur_node = up.node();
}
}
impl ToStringVerbose for ViciousCycleErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
"Found term that compiles into an inet with a vicious cycle".into()
}
}

View File

@ -1,16 +1,22 @@
use std::collections::HashMap;
use crate::{
diagnostics::Info,
term::{transform::encode_pattern_matching::MatchErr, Book, Ctx, Name, Pattern, Term},
diagnostics::{Diagnostics, ToStringVerbose},
term::{Book, Ctx, Name, Pattern, Term},
};
pub struct CtrArityMismatchErr {
ctr_name: Name,
expected: usize,
found: usize,
}
impl Ctx<'_> {
/// Checks if every constructor pattern of every definition rule has the same arity from the
/// defined adt constructor.
/// Checks if every constructor pattern of every definition rule
/// has the same arity from the defined adt constructor.
///
/// Constructors should be already resolved.
pub fn check_ctrs_arities(&mut self) -> Result<(), Info> {
pub fn check_ctrs_arities(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
let arities = self.book.ctr_arities();
@ -18,10 +24,10 @@ impl Ctx<'_> {
for rule in def.rules.iter() {
for pat in rule.pats.iter() {
let res = pat.check_ctrs_arities(&arities);
self.info.take_err(res, Some(def_name));
self.info.take_rule_err(res, def_name.clone());
}
let res = rule.body.check_ctrs_arities(&arities);
self.info.take_err(res, Some(def_name));
self.info.take_rule_err(res, def_name.clone());
}
}
@ -45,15 +51,15 @@ impl Book {
}
impl Pattern {
fn check_ctrs_arities(&self, arities: &HashMap<Name, usize>) -> Result<(), MatchErr> {
fn check_ctrs_arities(&self, arities: &HashMap<Name, usize>) -> Result<(), CtrArityMismatchErr> {
let mut to_check = vec![self];
while let Some(pat) = to_check.pop() {
if let Pattern::Ctr(name, args) = pat {
let expected = arities.get(name).unwrap();
if let Pattern::Ctr(ctr_name, args) = pat {
let expected = arities.get(ctr_name).unwrap();
let found = args.len();
if *expected != found {
return Err(MatchErr::CtrArityMismatch(name.clone(), found, *expected));
return Err(CtrArityMismatchErr { ctr_name: ctr_name.clone(), found, expected: *expected });
}
}
for child in pat.children() {
@ -65,7 +71,7 @@ impl Pattern {
}
impl Term {
pub fn check_ctrs_arities(&self, arities: &HashMap<Name, usize>) -> Result<(), MatchErr> {
pub fn check_ctrs_arities(&self, arities: &HashMap<Name, usize>) -> Result<(), CtrArityMismatchErr> {
Term::recursive_call(move || {
for pat in self.patterns() {
pat.check_ctrs_arities(arities)?;
@ -77,3 +83,12 @@ impl Term {
})
}
}
impl ToStringVerbose for CtrArityMismatchErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
format!(
"Constructor arity mismatch in pattern matching. Constructor '{}' expects {} fields, found {}.",
self.ctr_name, self.expected, self.found
)
}
}

View File

@ -1,17 +1,21 @@
use crate::{
diagnostics::Info,
term::{transform::encode_pattern_matching::MatchErr, Ctx, Definition, Term},
diagnostics::{Diagnostics, ToStringVerbose},
term::{Ctx, Definition, Term},
};
pub struct MatchArityMismatchErr {
expected: usize,
found: usize,
}
impl Ctx<'_> {
/// Checks that the number of arguments in every pattern matching rule is consistent.
pub fn check_match_arity(&mut self) -> Result<(), Info> {
pub fn check_match_arity(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
for (def_name, def) in self.book.defs.iter() {
if let Err(e) = def.check_match_arity() {
self.info.def_error(def_name.clone(), e)
};
let res = def.check_match_arity();
self.info.take_rule_err(res, def_name.clone());
}
self.info.fatal(())
@ -19,11 +23,12 @@ impl Ctx<'_> {
}
impl Definition {
pub fn check_match_arity(&self) -> Result<(), MatchErr> {
let expected_arity = self.arity();
pub fn check_match_arity(&self) -> Result<(), MatchArityMismatchErr> {
let expected = self.arity();
for rule in &self.rules {
if rule.arity() != expected_arity {
return Err(MatchErr::ArityMismatch(rule.arity(), expected_arity));
let found = rule.arity();
if found != expected {
return Err(MatchArityMismatchErr { found, expected });
}
rule.body.check_match_arity()?;
}
@ -32,14 +37,14 @@ impl Definition {
}
impl Term {
pub fn check_match_arity(&self) -> Result<(), MatchErr> {
pub fn check_match_arity(&self) -> Result<(), MatchArityMismatchErr> {
Term::recursive_call(move || {
if let Term::Mat { args, rules } = self {
let expected = args.len();
for rule in rules {
let found = rule.pats.len();
if found != expected {
return Err(MatchErr::ArityMismatch(found, expected));
return Err(MatchArityMismatchErr { found, expected });
}
}
}
@ -51,3 +56,9 @@ impl Term {
})
}
}
impl ToStringVerbose for MatchArityMismatchErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
format!("Arity mismatch in pattern matching. Expected {} patterns, found {}.", self.expected, self.found)
}
}

View File

@ -1,5 +1,8 @@
use crate::term::{Ctx, Name, Term};
use std::{collections::HashSet, fmt::Display};
use crate::{
diagnostics::{ToStringVerbose, WarningType},
term::{Ctx, Name, Term},
};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub enum RepeatedBindWarn {
@ -7,15 +10,6 @@ pub enum RepeatedBindWarn {
Match(Name),
}
impl Display for RepeatedBindWarn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RepeatedBindWarn::Rule(bind) => write!(f, "Repeated bind inside rule pattern: '{bind}'."),
RepeatedBindWarn::Match(bind) => write!(f, "Repeated bind inside match arm: '{bind}'."),
}
}
}
impl Ctx<'_> {
/// Checks that there are no unbound variables in all definitions.
pub fn check_repeated_binds(&mut self) {
@ -25,7 +19,11 @@ impl Ctx<'_> {
for pat in &rule.pats {
for nam in pat.binds().flatten() {
if !binds.insert(nam) {
self.info.warning(def_name.clone(), RepeatedBindWarn::Rule(nam.clone()));
self.info.add_rule_warning(
RepeatedBindWarn::Rule(nam.clone()),
WarningType::RepeatedBind,
def_name.clone(),
);
}
}
}
@ -33,8 +31,8 @@ impl Ctx<'_> {
let mut repeated_in_matches = Vec::new();
rule.body.check_repeated_binds(&mut repeated_in_matches);
for repeated in repeated_in_matches {
self.info.warning(def_name.clone(), repeated);
for warn in repeated_in_matches {
self.info.add_rule_warning(warn, WarningType::RepeatedBind, def_name.clone());
}
}
}
@ -68,3 +66,12 @@ impl Term {
})
}
}
impl ToStringVerbose for RepeatedBindWarn {
fn to_string_verbose(&self, _verbose: bool) -> String {
match self {
RepeatedBindWarn::Rule(bind) => format!("Repeated bind inside rule pattern: '{bind}'."),
RepeatedBindWarn::Match(bind) => format!("Repeated bind inside match arm: '{bind}'."),
}
}
}

View File

@ -1,8 +1,8 @@
use crate::{
diagnostics::ToStringVerbose,
term::{Book, Ctx, Definition, Name},
ENTRY_POINT, HVM1_ENTRY_POINT,
};
use std::fmt::Display;
#[derive(Debug, Clone)]
pub enum EntryErr {
@ -11,21 +11,6 @@ pub enum EntryErr {
MultipleRules,
}
impl Display for EntryErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EntryErr::NotFound(name) => write!(f, "File has no '{name}' definition."),
EntryErr::Multiple(fnd) if fnd.len() == 2 => {
write!(f, "File has both '{}' and '{}' definitions.", fnd[0], fnd[1])
}
EntryErr::Multiple(fnd) => {
write!(f, "File has '{}', '{}' and '{}' definitions.", fnd[0], fnd[1], fnd[2])
}
EntryErr::MultipleRules => write!(f, "Main definition can't have more than one rule."),
}
}
}
impl Ctx<'_> {
pub fn set_entrypoint(&mut self) {
let mut entrypoint = None;
@ -35,30 +20,31 @@ impl Ctx<'_> {
(Some(entry), None, None) | (None, Some(entry), None) | (None, None, Some(entry)) => {
match validate_entry_point(entry) {
Ok(name) => entrypoint = Some(name),
Err(err) => self.info.error(err),
Err(err) => self.info.add_book_error(err),
}
}
(Some(a), Some(b), None) | (None, Some(a), Some(b)) | (Some(a), None, Some(b)) => {
self.info.error(EntryErr::Multiple(vec![a.name.clone(), b.name.clone()]));
self.info.add_book_error(EntryErr::Multiple(vec![a.name.clone(), b.name.clone()]));
match validate_entry_point(a) {
Ok(name) => entrypoint = Some(name),
Err(err) => self.info.error(err),
Err(err) => self.info.add_book_error(err),
}
}
(Some(a), Some(b), Some(c)) => {
self.info.error(EntryErr::Multiple(vec![a.name.clone(), b.name.clone(), c.name.clone()]));
self.info.add_book_error(EntryErr::Multiple(vec![a.name.clone(), b.name.clone(), c.name.clone()]));
match validate_entry_point(a) {
Ok(name) => entrypoint = Some(name),
Err(err) => self.info.error(err),
Err(err) => self.info.add_book_error(err),
}
}
(None, None, None) => {
self.info.error(EntryErr::NotFound(self.book.entrypoint.clone().unwrap_or(Name::from(ENTRY_POINT))))
let entrypoint = self.book.entrypoint.clone().unwrap_or(Name::from(ENTRY_POINT));
self.info.add_book_error(EntryErr::NotFound(entrypoint))
}
}
@ -78,3 +64,18 @@ impl Book {
(custom, main, hvm1_main)
}
}
impl ToStringVerbose for EntryErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
match self {
EntryErr::NotFound(name) => format!("File has no '{name}' definition."),
EntryErr::Multiple(fnd) if fnd.len() == 2 => {
format!("File has both '{}' and '{}' definitions.", fnd[0], fnd[1])
}
EntryErr::Multiple(fnd) => {
format!("File has '{}', '{}' and '{}' definitions.", fnd[0], fnd[1], fnd[2])
}
EntryErr::MultipleRules => "Main definition can't have more than one rule.".to_string(),
}
}
}

View File

@ -3,86 +3,96 @@ use std::fmt::Display;
use indexmap::IndexMap;
use crate::{
diagnostics,
diagnostics::ToStringVerbose,
term::{Ctx, Name},
};
#[derive(Debug, Clone)]
pub struct TopLevelErr(Name);
impl Display for TopLevelErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Duplicated top-level name '{}'.", self.0)
}
pub struct RepeatedTopLevelNameErr {
kind_fst: NameKind,
kind_snd: NameKind,
name: Name,
}
impl Ctx<'_> {
/// Checks if exists shared names from definitions, adts and constructors, allowing constructors
/// share the adt name once.
/// Checks if there are any repeated top level names. Constructors
/// and functions can't share names and adts can't share names.
pub fn check_shared_names(&mut self) {
let mut checked = IndexMap::<&Name, NameInfo>::new();
let mut names = NameInfo::default();
for adt_name in self.book.adts.keys() {
checked.entry(adt_name).or_insert(NameInfo::new(NameKind::Adt)).with_adt(adt_name, &mut self.info);
names.add_name(adt_name, NameKind::Adt);
}
for ctr_name in self.book.ctrs.keys() {
checked.entry(ctr_name).or_insert(NameInfo::new(NameKind::Ctr)).with_ctr(ctr_name, &mut self.info);
names.add_name(ctr_name, NameKind::Ctr);
}
for def_name in self.book.defs.keys() {
checked.entry(def_name).or_insert(NameInfo::new(NameKind::Def)).with_def(def_name, &mut self.info);
names.add_name(def_name, NameKind::Def);
}
for (name, name_info) in checked.into_iter() {
if name_info.count > 1 {
self.info.error(TopLevelErr(name.clone()));
}
for err in names.into_errs() {
self.info.add_book_error(err);
}
}
}
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
enum NameKind {
Adt,
Def,
Ctr,
}
#[derive(Debug)]
struct NameInfo {
kind: NameKind,
count: usize,
}
#[derive(Debug, Default)]
struct NameInfo<'a>(IndexMap<&'a Name, Vec<NameKind>>);
impl NameInfo {
fn new(kind: NameKind) -> Self {
Self { kind, count: 0 }
impl<'a> NameInfo<'a> {
fn add_name(&mut self, name: &'a Name, kind: NameKind) {
self.0.entry(name).or_default().push(kind);
}
fn into_errs(self) -> Vec<RepeatedTopLevelNameErr> {
let mut errs = vec![];
for (name, kinds) in self.0 {
let mut num_adts = 0;
let mut fst_ctr_def = None;
for kind in kinds {
if let NameKind::Adt = kind {
num_adts += 1;
if num_adts >= 2 {
errs.push(RepeatedTopLevelNameErr {
kind_fst: NameKind::Adt,
kind_snd: NameKind::Adt,
name: name.clone(),
});
}
} else if let Some(fst) = fst_ctr_def {
errs.push(RepeatedTopLevelNameErr { kind_fst: fst, kind_snd: kind, name: name.clone() });
} else {
fst_ctr_def = Some(kind);
}
}
}
errs
}
}
impl NameInfo {
fn with_ctr(&mut self, current_name: &Name, info: &mut diagnostics::Info) {
match self.kind {
NameKind::Adt => {} // Error caught by the parser
NameKind::Def => info.error(TopLevelErr(current_name.clone())),
NameKind::Ctr => {} // Error caught by the parser
}
}
fn with_def(&mut self, current_name: &Name, info: &mut diagnostics::Info) {
match self.kind {
NameKind::Adt => self.count += 1,
NameKind::Def => {}
NameKind::Ctr => info.error(TopLevelErr(current_name.clone())),
}
}
fn with_adt(&mut self, current_name: &Name, info: &mut diagnostics::Info) {
match self.kind {
NameKind::Adt => self.count += 1,
NameKind::Def => info.error(TopLevelErr(current_name.clone())),
NameKind::Ctr => self.count += 1,
impl Display for NameKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NameKind::Adt => write!(f, "data type"),
NameKind::Def => write!(f, "function"),
NameKind::Ctr => write!(f, "constructor"),
}
}
}
impl ToStringVerbose for RepeatedTopLevelNameErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
let mut snd = self.kind_snd.to_string();
snd[0 .. 1].make_ascii_uppercase();
format!("{} '{}' has the same name as a previously defined {}", snd, self.name, self.kind_fst)
}
}

View File

@ -1,9 +1,22 @@
use crate::term::{transform::encode_pattern_matching::MatchErr, Constructors, Name, Pattern, Rule, Type};
use crate::{
diagnostics::ToStringVerbose,
term::{Constructors, Name, Pattern, Rule, Type},
};
use indexmap::IndexMap;
pub type DefinitionTypes = IndexMap<Name, Vec<Type>>;
pub fn infer_match_arg_type(rules: &[Rule], arg_idx: usize, ctrs: &Constructors) -> Result<Type, MatchErr> {
#[derive(Debug)]
pub struct TypeMismatchErr {
expected: Type,
found: Type,
}
pub fn infer_match_arg_type(
rules: &[Rule],
arg_idx: usize,
ctrs: &Constructors,
) -> Result<Type, TypeMismatchErr> {
infer_type(rules.iter().map(|r| &r.pats[arg_idx]), ctrs)
}
@ -11,7 +24,7 @@ pub fn infer_match_arg_type(rules: &[Rule], arg_idx: usize, ctrs: &Constructors)
pub fn infer_type<'a>(
pats: impl IntoIterator<Item = &'a Pattern>,
ctrs: &Constructors,
) -> Result<Type, MatchErr> {
) -> Result<Type, TypeMismatchErr> {
let mut arg_type = Type::Any;
for pat in pats.into_iter() {
arg_type = unify(arg_type, pat.to_type(ctrs))?;
@ -19,7 +32,7 @@ pub fn infer_type<'a>(
Ok(arg_type)
}
fn unify(old: Type, new: Type) -> Result<Type, MatchErr> {
fn unify(old: Type, new: Type) -> Result<Type, TypeMismatchErr> {
match (old, new) {
(Type::Any, new) => Ok(new),
(old, Type::Any) => Ok(old),
@ -34,6 +47,12 @@ fn unify(old: Type, new: Type) -> Result<Type, MatchErr> {
(Type::Tup(a), Type::Tup(b)) if a == b => Ok(Type::Tup(a)),
(old, new) => Err(MatchErr::TypeMismatch(new, old)),
(old, new) => Err(TypeMismatchErr { found: new, expected: old }),
}
}
impl ToStringVerbose for TypeMismatchErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
format!("Type mismatch in pattern matching. Expected '{}', found '{}'.", self.expected, self.found)
}
}

View File

@ -1,21 +1,15 @@
use crate::{
diagnostics::Info,
diagnostics::{Diagnostics, ToStringVerbose},
term::{Ctx, Name, Pattern, Term},
};
use std::{collections::HashSet, fmt::Display};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct UnboundCtrErr(Name);
impl Display for UnboundCtrErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unbound constructor '{}'.", self.0)
}
}
impl Ctx<'_> {
/// Check if the constructors in rule patterns or match patterns are defined.
pub fn check_unbound_pats(&mut self) -> Result<(), Info> {
pub fn check_unbound_pats(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
let is_ctr = |nam: &Name| self.book.ctrs.contains_key(nam);
@ -23,11 +17,11 @@ impl Ctx<'_> {
for rule in &def.rules {
for pat in &rule.pats {
let res = pat.check_unbounds(&is_ctr);
self.info.take_err(res, Some(def_name));
self.info.take_rule_err(res, def_name.clone());
}
let res = rule.body.check_unbound_pats(&is_ctr);
self.info.take_err(res, Some(def_name));
self.info.take_rule_err(res, def_name.clone());
}
}
@ -70,3 +64,9 @@ impl Term {
})
}
}
impl ToStringVerbose for UnboundCtrErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
format!("Unbound constructor '{}'.", self.0)
}
}

View File

@ -1,11 +1,8 @@
use crate::{
diagnostics::Info,
diagnostics::{Diagnostics, ToStringVerbose},
term::{Ctx, Name, Term},
};
use std::{
collections::{hash_map::Entry, HashMap},
fmt::Display,
};
use std::collections::{hash_map::Entry, HashMap};
#[derive(Debug, Clone)]
pub enum UnboundVarErr {
@ -13,26 +10,9 @@ pub enum UnboundVarErr {
Global { var: Name, declared: usize, used: usize },
}
impl Display for UnboundVarErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnboundVarErr::Local(var) => write!(f, "Unbound variable '{var}'."),
UnboundVarErr::Global { var, declared, used } => match (declared, used) {
(0, _) => write!(f, "Unbound unscoped variable '${var}'."),
(_, 0) => write!(f, "Unscoped variable from lambda 'λ${var}' is never used."),
(1, _) => write!(f, "Unscoped variable '${var}' used more than once."),
(_, 1) => write!(f, "Unscoped lambda 'λ${var}' declared more than once."),
(_, _) => {
write!(f, "Unscoped lambda 'λ${var}' and unscoped variable '${var}' used more than once.")
}
},
}
}
}
impl Ctx<'_> {
/// Checks that there are no unbound variables in all definitions.
pub fn check_unbound_vars(&mut self) -> Result<(), Info> {
pub fn check_unbound_vars(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
for (def_name, def) in self.book.defs.iter_mut() {
@ -47,7 +27,7 @@ impl Ctx<'_> {
}
for err in errs {
self.info.def_error(def_name.clone(), err);
self.info.add_rule_error(err, def_name.clone());
}
}
@ -128,3 +108,20 @@ fn pop_scope<'a>(nam: Option<&'a Name>, scope: &mut HashMap<&'a Name, u64>) {
}
}
}
impl ToStringVerbose for UnboundVarErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
match self {
UnboundVarErr::Local(var) => format!("Unbound variable '{var}'."),
UnboundVarErr::Global { var, declared, used } => match (declared, used) {
(0, _) => format!("Unbound unscoped variable '${var}'."),
(_, 0) => format!("Unscoped variable from lambda 'λ${var}' is never used."),
(1, _) => format!("Unscoped variable '${var}' used more than once."),
(_, 1) => format!("Unscoped lambda 'λ${var}' declared more than once."),
(_, _) => {
format!("Unscoped lambda 'λ${var}' and unscoped variable '${var}' used more than once.")
}
},
}
}
}

View File

@ -1,4 +1,4 @@
use super::{net_to_term::ReadbackError, Book, Definition, Name, NumCtr, Op, Pattern, Rule, Tag, Term, Type};
use super::{Book, Definition, Name, NumCtr, Op, Pattern, Rule, Tag, Term, Type};
use std::{fmt, ops::Deref};
/* Some aux structures for things that are not so simple to display */
@ -189,58 +189,6 @@ impl fmt::Display for Type {
}
}
impl fmt::Display for ReadbackError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ReadbackError::InvalidNumericMatch => write!(f, "Invalid Numeric Match"),
ReadbackError::InvalidNumericOp => write!(f, "Invalid Numeric Operation"),
ReadbackError::ReachedRoot => write!(f, "Reached Root"),
ReadbackError::Cyclic => write!(f, "Cyclic Term"),
ReadbackError::InvalidBind => write!(f, "Invalid Bind"),
ReadbackError::InvalidAdt => write!(f, "Invalid Adt"),
ReadbackError::UnexpectedTag(exp, fnd) => {
write!(f, "Unexpected tag found during Adt readback, expected '{exp}', but found ")?;
match fnd {
Tag::Static => write!(f, "no tag"),
_ => write!(f, "'{fnd}'"),
}
}
ReadbackError::InvalidAdtMatch => write!(f, "Invalid Adt Match"),
ReadbackError::InvalidStrTerm(term) => {
write!(f, "Invalid String Character value '{term}'")
}
}
}
}
pub fn display_readback_errors(errs: &[ReadbackError]) -> impl fmt::Display + '_ {
DisplayFn(move |f| {
if errs.is_empty() {
return Ok(());
}
writeln!(f, "Readback Warning:")?;
let mut err_counts = std::collections::HashMap::new();
for err in errs {
if err.can_count() {
*err_counts.entry(err).or_insert(0) += 1;
} else {
writeln!(f, "{err}")?;
}
}
for (err, count) in err_counts {
write!(f, "{err}")?;
if count > 1 {
writeln!(f, " ({count} occurrences)")?;
}
}
writeln!(f)
})
}
impl Term {
fn display_app<'a>(&'a self, tag: &'a Tag) -> impl fmt::Display + 'a {
DisplayFn(move |f| match self {

View File

@ -1,5 +1,9 @@
use self::{check::type_check::infer_match_arg_type, parser::lexer::STRINGS};
use crate::{diagnostics::Info, term::builtins::*, ENTRY_POINT};
use crate::{
diagnostics::{Diagnostics, DiagnosticsConfig},
term::builtins::*,
ENTRY_POINT,
};
use indexmap::{IndexMap, IndexSet};
use interner::global::GlobalString;
use itertools::Itertools;
@ -24,12 +28,12 @@ pub use term_to_net::{book_to_nets, term_to_compat_net};
#[derive(Debug)]
pub struct Ctx<'book> {
pub book: &'book mut Book,
pub info: Info,
pub info: Diagnostics,
}
impl Ctx<'_> {
pub fn new(book: &mut Book) -> Ctx {
Ctx { book, info: Info::default() }
pub fn new(book: &mut Book, diagnostics_cfg: DiagnosticsConfig) -> Ctx {
Ctx { book, info: Diagnostics::new(diagnostics_cfg) }
}
}

View File

@ -1,4 +1,5 @@
use crate::{
diagnostics::{DiagnosticOrigin, Diagnostics, Severity},
net::{INet, NodeId, NodeKind::*, Port, SlotId, ROOT},
term::{num_to_name, term_to_net::Labels, Book, Name, Op, Pattern, Tag, Term},
};
@ -6,7 +7,13 @@ use std::collections::{BTreeSet, HashMap, HashSet};
// TODO: Display scopeless lambdas as such
/// Converts an Interaction-INet to a Lambda Calculus term
pub fn net_to_term(net: &INet, book: &Book, labels: &Labels, linear: bool) -> (Term, Vec<ReadbackError>) {
pub fn net_to_term(
net: &INet,
book: &Book,
labels: &Labels,
linear: bool,
diagnostics: &mut Diagnostics,
) -> Term {
let mut reader = Reader {
net,
labels,
@ -38,7 +45,9 @@ pub fn net_to_term(net: &INet, book: &Book, labels: &Labels, linear: bool) -> (T
let result = term.insert_split(split, uses);
debug_assert_eq!(result, None);
}
(term, reader.errors)
reader.report_errors(diagnostics);
term
}
// BTreeSet for consistent readback of dups
@ -56,7 +65,7 @@ pub struct Reader<'a> {
errors: Vec<ReadbackError>,
}
impl<'a> Reader<'a> {
impl Reader<'_> {
fn read_term(&mut self, next: Port) -> Term {
Term::recursive_call(move || {
if self.dup_paths.is_none() && !self.seen.insert(next) {
@ -277,6 +286,19 @@ impl<'a> Reader<'a> {
pub fn error(&mut self, error: ReadbackError) {
self.errors.push(error);
}
pub fn report_errors(&mut self, diagnostics: &mut Diagnostics) {
let mut err_counts = std::collections::HashMap::new();
for err in &self.errors {
*err_counts.entry(*err).or_insert(0) += 1;
}
for (err, count) in err_counts {
let count_msg = if count > 1 { format!(" ({count} occurrences)") } else { "".to_string() };
let msg = format!("{}{}", err, count_msg);
diagnostics.add_diagnostic(msg.as_str(), Severity::Warning, DiagnosticOrigin::Readback);
}
}
}
/// Represents `let (fst, snd) = val` if `tag` is `None`, and `dup#tag fst snd = val` otherwise.
@ -473,33 +495,13 @@ fn is_op_swapped(op: hvmc::ops::Op) -> bool {
)
}
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub enum ReadbackError {
InvalidNumericMatch,
InvalidNumericOp,
ReachedRoot,
Cyclic,
InvalidBind,
InvalidAdt,
InvalidAdtMatch,
InvalidStrTerm(Term),
UnexpectedTag(Tag, Tag),
}
impl ReadbackError {
pub fn can_count(&self) -> bool {
match self {
ReadbackError::InvalidNumericMatch => true,
ReadbackError::InvalidNumericOp => true,
ReadbackError::ReachedRoot => true,
ReadbackError::Cyclic => true,
ReadbackError::InvalidBind => true,
ReadbackError::InvalidAdt => true,
ReadbackError::InvalidAdtMatch => true,
ReadbackError::InvalidStrTerm(_) => false,
ReadbackError::UnexpectedTag(..) => false,
}
}
}
impl PartialEq for ReadbackError {
@ -515,3 +517,15 @@ impl std::hash::Hash for ReadbackError {
core::mem::discriminant(self).hash(state);
}
}
impl std::fmt::Display for ReadbackError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ReadbackError::InvalidNumericMatch => write!(f, "Invalid Numeric Match."),
ReadbackError::InvalidNumericOp => write!(f, "Invalid Numeric Operation."),
ReadbackError::ReachedRoot => write!(f, "Reached Root."),
ReadbackError::Cyclic => write!(f, "Cyclic Term."),
ReadbackError::InvalidBind => write!(f, "Invalid Bind."),
}
}
}

View File

@ -61,7 +61,7 @@ struct EncodeTermState<'a> {
labels: &'a mut Labels,
}
impl<'a> EncodeTermState<'a> {
impl EncodeTermState<'_> {
/// Adds a subterm connected to `up` to the `inet`.
/// `scope` has the current variable scope.
/// `vars` has the information of which ports the variables are declared and used in.

View File

@ -1,18 +1,9 @@
use std::fmt::Display;
use crate::{
diagnostics::Info,
diagnostics::{Diagnostics, ToStringVerbose},
term::{Ctx, Pattern, Term},
};
#[derive(Clone, Debug)]
pub struct PatternArgError(Pattern);
impl Display for PatternArgError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Expected a variable pattern, found '{}'.", self.0)
}
}
struct PatternArgError(Pattern);
impl Ctx<'_> {
/// Applies the arguments to the program being run by applying them to the main function.
@ -25,7 +16,7 @@ impl Ctx<'_> {
/// ```hvm
/// main = (λx1 λx2 λx3 (MainBody x1 x2 x3) arg1 arg2 arg3)
/// ```
pub fn apply_args(&mut self, args: Option<Vec<Term>>) -> Result<(), Info> {
pub fn apply_args(&mut self, args: Option<Vec<Term>>) -> Result<(), Diagnostics> {
self.info.start_pass();
if let Some(entrypoint) = &self.book.entrypoint {
@ -33,7 +24,7 @@ impl Ctx<'_> {
for pat in &main_def.rules[0].pats {
if !matches!(pat, Pattern::Var(Some(..))) {
self.info.def_error(entrypoint.clone(), PatternArgError(pat.clone()));
self.info.add_rule_error(PatternArgError(pat.clone()), entrypoint.clone());
}
}
@ -48,3 +39,9 @@ impl Ctx<'_> {
self.info.fatal(())
}
}
impl ToStringVerbose for PatternArgError {
fn to_string_verbose(&self, _verbose: bool) -> String {
format!("Expected the entrypoint to only have variable pattern, found '{}'.", self.0)
}
}

View File

@ -1,11 +1,12 @@
use std::collections::{hash_map::Entry, HashMap};
use crate::{
diagnostics::WarningType,
diagnostics::{ToStringVerbose, WarningType},
term::{Adt, AdtEncoding, Book, Ctx, Name, Tag, Term, LIST, STRING},
CORE_BUILTINS,
};
use indexmap::IndexSet;
use std::collections::{hash_map::Entry, HashMap};
struct UnusedDefinitionWarning;
#[derive(Clone, Copy, Debug, PartialEq)]
enum Used {
@ -72,7 +73,7 @@ impl Ctx<'_> {
if prune_all || def.builtin {
self.book.defs.swap_remove(&def_name);
} else if !def_name.is_generated() {
self.info.warning(def_name.clone(), WarningType::UnusedDefinition);
self.info.add_rule_warning(UnusedDefinitionWarning, WarningType::UnusedDefinition, def_name);
}
}
}
@ -153,3 +154,9 @@ impl Book {
}
}
}
impl ToStringVerbose for UnusedDefinitionWarning {
fn to_string_verbose(&self, _verbose: bool) -> String {
"Definition is unused.".to_string()
}
}

View File

@ -1,13 +1,6 @@
use std::fmt::Display;
use itertools::Itertools;
use crate::{
diagnostics::ERR_INDENT_SIZE,
term::{
check::type_check::infer_match_arg_type, display::DisplayFn, AdtEncoding, Adts, Book, Constructors, Name,
NumCtr, Pattern, Rule, Tag, Term, Type,
},
use crate::term::{
check::type_check::infer_match_arg_type, AdtEncoding, Adts, Book, Constructors, Name, NumCtr, Pattern,
Rule, Tag, Term, Type,
};
impl Book {
@ -180,84 +173,3 @@ fn encode_adt(arg: Term, rules: Vec<Rule>, adt: Name, adt_encoding: AdtEncoding)
}
}
}
#[derive(Debug, Clone)]
pub enum MatchErr {
RepeatedBind(Name),
LetPat(Box<MatchErr>),
Linearize(Name),
NotExhaustive(ExhaustivenessErr),
TypeMismatch(Type, Type),
ArityMismatch(usize, usize),
CtrArityMismatch(Name, usize, usize),
MalformedNumSucc(Pattern, Pattern),
}
#[derive(Debug, Clone)]
pub struct ExhaustivenessErr(pub Vec<Name>);
const PATTERN_ERROR_LIMIT: usize = 5;
const ERROR_LIMIT_HINT: &str = "Use the --verbose option to see all cases.";
impl MatchErr {
pub fn display(&self, verbose: bool) -> impl std::fmt::Display + '_ {
DisplayFn(move |f| match self {
MatchErr::RepeatedBind(bind) => write!(f, "Repeated var name in a match block: {}", bind),
MatchErr::LetPat(err) => {
let let_err = err.to_string().replace("match block", "let bind");
write!(f, "{let_err}")?;
if matches!(err.as_ref(), MatchErr::NotExhaustive(..)) {
write!(f, "\nConsider using a match block instead")?;
}
Ok(())
}
MatchErr::Linearize(var) => write!(f, "Unable to linearize variable {var} in a match block."),
MatchErr::NotExhaustive(err) if verbose => write!(f, "{}", err.display_with_limit(usize::MAX)),
MatchErr::NotExhaustive(err) => write!(f, "{err}"),
MatchErr::TypeMismatch(got, exp) => {
write!(f, "Type mismatch in pattern matching. Expected '{exp}', found '{got}'.")
}
MatchErr::ArityMismatch(got, exp) => {
write!(f, "Arity mismatch in pattern matching. Expected {exp} patterns, found {got}.")
}
MatchErr::CtrArityMismatch(ctr, got, exp) => write!(
f,
"Constructor arity mismatch in pattern matching. Constructor '{ctr}' expects {exp} fields, found {got}."
),
MatchErr::MalformedNumSucc(got, exp) => {
write!(f, "Expected a sequence of incrementing numbers ending with '{exp}', found '{got}'.")
}
})
}
}
impl std::fmt::Display for MatchErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display(false))
}
}
impl ExhaustivenessErr {
pub fn display_with_limit(&self, limit: usize) -> String {
let ident = ERR_INDENT_SIZE * 2;
let hints =
self.0.iter().take(limit).map(|pat| format!("{:ident$}Case '{pat}' not covered.", "")).join("\n");
let mut str = format!("Non-exhaustive pattern matching. Hint:\n{}", hints);
let len = self.0.len();
if len > limit {
str.push_str(&format!(" ... and {} others.\n{:ident$}{}", len - limit, "", ERROR_LIMIT_HINT))
}
str
}
}
impl Display for ExhaustivenessErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display_with_limit(PATTERN_ERROR_LIMIT))
}
}

View File

@ -1,39 +1,28 @@
use super::encode_pattern_matching::MatchErr;
use crate::{
diagnostics::Info,
term::{Ctx, Name, Term},
};
use crate::term::{Book, Name, Term};
use itertools::Itertools;
use std::collections::{BTreeMap, BTreeSet};
impl Ctx<'_> {
impl Book {
/// Linearizes the variables between match cases, transforming them into combinators when possible.
pub fn linearize_simple_matches(&mut self, lift_all_vars: bool) -> Result<(), Info> {
self.info.start_pass();
for (def_name, def) in self.book.defs.iter_mut() {
pub fn linearize_simple_matches(&mut self, lift_all_vars: bool) {
for def in self.defs.values_mut() {
for rule in def.rules.iter_mut() {
let res = rule.body.linearize_simple_matches(lift_all_vars);
self.info.take_err(res, Some(def_name));
rule.body.linearize_simple_matches(lift_all_vars);
}
}
self.info.fatal(())
}
}
impl Term {
fn linearize_simple_matches(&mut self, lift_all_vars: bool) -> Result<(), MatchErr> {
fn linearize_simple_matches(&mut self, lift_all_vars: bool) {
Term::recursive_call(move || {
for child in self.children_mut() {
child.linearize_simple_matches(lift_all_vars)?;
child.linearize_simple_matches(lift_all_vars);
}
if let Term::Mat { .. } = self {
lift_match_vars(self, lift_all_vars);
}
Ok(())
})
}
}

View File

@ -1,22 +1,13 @@
use crate::{
diagnostics::Info,
diagnostics::{Diagnostics, ToStringVerbose},
term::{Ctx, Name, Pattern, Term},
CORE_BUILTINS,
};
use std::{
collections::{HashMap, HashSet},
fmt::Display,
};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct ReferencedMainErr;
impl Display for ReferencedMainErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Main definition can't be referenced inside the program.")
}
}
impl Ctx<'_> {
/// Decides if names inside a term belong to a Var or to a Ref.
/// Converts `Term::Var(nam)` into `Term::Ref(nam)` when the name
@ -26,7 +17,7 @@ impl Ctx<'_> {
/// Precondition: Refs are encoded as vars, Constructors are resolved.
///
/// Postcondition: Refs are encoded as refs, with the correct def id.
pub fn resolve_refs(&mut self) -> Result<(), Info> {
pub fn resolve_refs(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
let def_names = self.book.defs.keys().cloned().collect::<HashSet<_>>();
@ -39,7 +30,7 @@ impl Ctx<'_> {
}
let res = rule.body.resolve_refs(&def_names, self.book.entrypoint.as_ref(), &mut scope);
self.info.take_err(res, Some(def_name));
self.info.take_rule_err(res, def_name.clone());
}
}
@ -105,3 +96,9 @@ fn is_var_in_scope<'a>(name: &'a Name, scope: &HashMap<&'a Name, usize>) -> bool
None => true,
}
}
impl ToStringVerbose for ReferencedMainErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
"Main definition can't be referenced inside the program.".to_string()
}
}

View File

@ -1,7 +1,16 @@
use crate::term::{net_to_term::ReadbackError, Adt, AdtEncoding, Book, Name, Pattern, Rule, Tag, Term};
use crate::{
diagnostics::ToStringVerbose,
term::{Adt, AdtEncoding, Book, Name, Pattern, Rule, Tag, Term},
};
pub enum AdtReadbackError {
InvalidAdt,
InvalidAdtMatch,
UnexpectedTag(Tag, Tag),
}
impl Term {
pub fn resugar_adts(&mut self, book: &Book, adt_encoding: AdtEncoding) -> Vec<ReadbackError> {
pub fn resugar_adts(&mut self, book: &Book, adt_encoding: AdtEncoding) -> Vec<AdtReadbackError> {
let mut errs = Default::default();
match adt_encoding {
// No way of resugaring simple scott encoded terms.
@ -11,7 +20,7 @@ impl Term {
errs
}
fn resugar_tagged_scott(&mut self, book: &Book, errs: &mut Vec<ReadbackError>) {
fn resugar_tagged_scott(&mut self, book: &Book, errs: &mut Vec<AdtReadbackError>) {
Term::recursive_call(move || match self {
Term::Lam { tag: Tag::Named(adt_name), bod, .. } | Term::Chn { tag: Tag::Named(adt_name), bod, .. } => {
if let Some((adt_name, adt)) = book.adts.get_key_value(adt_name) {
@ -59,7 +68,7 @@ impl Term {
book: &Book,
adt: &Adt,
adt_name: &Name,
errs: &mut Vec<ReadbackError>,
errs: &mut Vec<AdtReadbackError>,
) {
let mut app = &mut *self;
let mut current_arm = None;
@ -69,7 +78,7 @@ impl Term {
Term::Lam { tag: Tag::Named(tag), nam, bod } if tag == adt_name => {
if let Some(nam) = nam {
if current_arm.is_some() {
errs.push(ReadbackError::InvalidAdt);
errs.push(AdtReadbackError::InvalidAdt);
return;
}
current_arm = Some((nam.clone(), ctr));
@ -77,14 +86,14 @@ impl Term {
app = bod;
}
_ => {
errs.push(ReadbackError::InvalidAdt);
errs.push(AdtReadbackError::InvalidAdt);
return;
}
}
}
let Some((arm_name, (ctr, ctr_args))) = current_arm else {
errs.push(ReadbackError::InvalidAdt);
errs.push(AdtReadbackError::InvalidAdt);
return;
};
@ -100,11 +109,11 @@ impl Term {
cur = fun.as_mut();
}
Term::App { tag, .. } => {
errs.push(ReadbackError::UnexpectedTag(expected_tag, tag.clone()));
errs.push(AdtReadbackError::UnexpectedTag(expected_tag, tag.clone()));
return;
}
_ => {
errs.push(ReadbackError::InvalidAdt);
errs.push(AdtReadbackError::InvalidAdt);
return;
}
}
@ -113,7 +122,7 @@ impl Term {
match cur {
Term::Var { nam } if nam == &arm_name => {}
_ => {
errs.push(ReadbackError::InvalidAdt);
errs.push(AdtReadbackError::InvalidAdt);
return;
}
}
@ -157,7 +166,7 @@ impl Term {
book: &Book,
adt_name: &Name,
adt: &Adt,
errs: &mut Vec<ReadbackError>,
errs: &mut Vec<AdtReadbackError>,
) {
let mut cur = &mut *self;
let mut arms = Vec::new();
@ -180,7 +189,7 @@ impl Term {
}
_ => {
if let Term::Lam { tag, .. } = arm {
errs.push(ReadbackError::UnexpectedTag(expected_tag.clone(), tag.clone()));
errs.push(AdtReadbackError::UnexpectedTag(expected_tag.clone(), tag.clone()));
}
let arg = Name::new(format!("{ctr}.{field}"));
@ -194,7 +203,7 @@ impl Term {
cur = &mut *fun;
}
_ => {
errs.push(ReadbackError::InvalidAdtMatch);
errs.push(AdtReadbackError::InvalidAdtMatch);
return;
}
}
@ -208,3 +217,16 @@ impl Term {
self.resugar_tagged_scott(book, errs);
}
}
impl ToStringVerbose for AdtReadbackError {
fn to_string_verbose(&self, _verbose: bool) -> String {
match self {
AdtReadbackError::InvalidAdt => "Invalid Adt.".to_string(),
AdtReadbackError::InvalidAdtMatch => "Invalid Adt Match.".to_string(),
AdtReadbackError::UnexpectedTag(expected, found) => {
let found = if let Tag::Static = found { "no tag".to_string() } else { format!("'{found}'") };
format!("Unexpected tag found during Adt readback, expected '{}', but found {}.", expected, found)
}
}
}
}

View File

@ -1,22 +1,27 @@
use indexmap::IndexSet;
use itertools::Itertools;
use crate::{
diagnostics::Info,
diagnostics::{Diagnostics, ToStringVerbose, ERR_INDENT_SIZE},
term::{
check::type_check::infer_match_arg_type,
transform::encode_pattern_matching::{ExhaustivenessErr, MatchErr},
check::type_check::{infer_match_arg_type, TypeMismatchErr},
display::{DisplayFn, DisplayJoin},
Adts, Constructors, Ctx, Definition, Name, NumCtr, Pattern, Rule, Term, Type,
},
};
use indexmap::IndexSet;
use itertools::Itertools;
pub enum SimplifyMatchErr {
NotExhaustive(Vec<Name>),
TypeMismatch(TypeMismatchErr),
MalformedNumSucc(Pattern, Pattern),
}
impl Ctx<'_> {
pub fn simplify_matches(&mut self) -> Result<(), Info> {
pub fn simplify_matches(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
for (def_name, def) in self.book.defs.iter_mut() {
let res = def.simplify_matches(&self.book.ctrs, &self.book.adts);
self.info.take_err(res, Some(def_name));
self.info.take_rule_err(res, def_name.clone());
}
self.info.fatal(())
@ -24,7 +29,7 @@ impl Ctx<'_> {
}
impl Definition {
pub fn simplify_matches(&mut self, ctrs: &Constructors, adts: &Adts) -> Result<(), MatchErr> {
pub fn simplify_matches(&mut self, ctrs: &Constructors, adts: &Adts) -> Result<(), SimplifyMatchErr> {
for rule in self.rules.iter_mut() {
rule.body.simplify_matches(ctrs, adts)?;
}
@ -38,7 +43,7 @@ impl Term {
/// simple (non-nested) patterns, and one rule for each constructor.
///
/// See `[simplify_match_expression]` for more information.
pub fn simplify_matches(&mut self, ctrs: &Constructors, adts: &Adts) -> Result<(), MatchErr> {
pub fn simplify_matches(&mut self, ctrs: &Constructors, adts: &Adts) -> Result<(), SimplifyMatchErr> {
Term::recursive_call(move || {
match self {
Term::Mat { args, rules } => {
@ -87,7 +92,7 @@ fn simplify_match_expression(
rules: Vec<Rule>,
ctrs: &Constructors,
adts: &Adts,
) -> Result<Term, MatchErr> {
) -> Result<Term, SimplifyMatchErr> {
let fst_row_irrefutable = rules[0].pats.iter().all(|p| p.is_wildcard());
let fst_col_type = infer_match_arg_type(&rules, 0, ctrs)?;
@ -108,7 +113,7 @@ fn irrefutable_fst_row_rule(
mut rules: Vec<Rule>,
ctrs: &Constructors,
adts: &Adts,
) -> Result<Term, MatchErr> {
) -> Result<Term, SimplifyMatchErr> {
rules.truncate(1);
let Rule { pats, body: mut term } = rules.pop().unwrap();
@ -132,7 +137,7 @@ fn var_rule(
rules: Vec<Rule>,
ctrs: &Constructors,
adts: &Adts,
) -> Result<Term, MatchErr> {
) -> Result<Term, SimplifyMatchErr> {
let mut new_rules = vec![];
for mut rule in rules {
let rest = rule.pats.split_off(1);
@ -201,7 +206,7 @@ fn switch_rule(
typ: Type,
ctrs: &Constructors,
adts: &Adts,
) -> Result<Term, MatchErr> {
) -> Result<Term, SimplifyMatchErr> {
let mut new_rules = vec![];
let adt_ctrs = match typ {
@ -225,7 +230,7 @@ fn switch_rule(
Type::Num => {
// Number match without + must have a default case
if !rules.iter().any(|r| r.pats[0].is_wildcard()) {
return Err(MatchErr::NotExhaustive(ExhaustivenessErr(vec![Name::from("+")])));
return Err(SimplifyMatchErr::NotExhaustive(vec![Name::from("+")]));
}
}
Type::NumSucc(exp) => {
@ -235,7 +240,7 @@ fn switch_rule(
if let Pattern::Num(NumCtr::Num(got)) = rule.pats[0]
&& got >= exp
{
return Err(MatchErr::MalformedNumSucc(
return Err(SimplifyMatchErr::MalformedNumSucc(
rule.pats[0].clone(),
Pattern::Num(NumCtr::Succ(exp, None)),
));
@ -291,7 +296,7 @@ fn switch_rule_submatch(
rules: &[Rule],
ctr: &Pattern,
nested_fields: &[Option<Name>],
) -> Result<Term, MatchErr> {
) -> Result<Term, SimplifyMatchErr> {
// Create the nested match expression.
let new_args = nested_fields.iter().cloned().map(Term::var_or_era);
let old_args = args[1 ..].iter().cloned();
@ -304,7 +309,7 @@ fn switch_rule_submatch(
if rules.is_empty() {
// TODO: Return the full pattern
return Err(MatchErr::NotExhaustive(ExhaustivenessErr(vec![ctr.ctr_name().unwrap()])));
return Err(SimplifyMatchErr::NotExhaustive(vec![ctr.ctr_name().unwrap()]));
}
let mat = Term::Mat { args, rules };
@ -384,3 +389,52 @@ fn bind_extracted_args(extracted: Vec<(Name, Term)>, term: Term) -> Term {
nxt: Box::new(term),
})
}
const PATTERN_ERROR_LIMIT: usize = 5;
const ERROR_LIMIT_HINT: &str = "Use the --verbose option to see all cases.";
impl SimplifyMatchErr {
pub fn display(&self, verbose: bool) -> impl std::fmt::Display + '_ {
let limit = if verbose { usize::MAX } else { PATTERN_ERROR_LIMIT };
DisplayFn(move |f| match self {
SimplifyMatchErr::NotExhaustive(cases) => {
let ident = ERR_INDENT_SIZE * 2;
let hints = DisplayJoin(
|| {
cases
.iter()
.take(limit)
.map(|pat| DisplayFn(move |f| write!(f, "{:ident$}Case '{pat}' not covered.", "")))
},
"\n",
);
write!(f, "Non-exhaustive pattern matching. Hint:\n{}", hints)?;
let len = cases.len();
if len > limit {
write!(f, " ... and {} others.\n{:ident$}{}", len - limit, "", ERROR_LIMIT_HINT)?;
}
Ok(())
}
SimplifyMatchErr::TypeMismatch(err) => {
write!(f, "{}", err.to_string_verbose(verbose))
}
SimplifyMatchErr::MalformedNumSucc(got, exp) => {
write!(f, "Expected a sequence of incrementing numbers ending with '{exp}', found '{got}'.")
}
})
}
}
impl ToStringVerbose for SimplifyMatchErr {
fn to_string_verbose(&self, verbose: bool) -> String {
format!("{}", self.display(verbose))
}
}
impl From<TypeMismatchErr> for SimplifyMatchErr {
fn from(value: TypeMismatchErr) -> Self {
SimplifyMatchErr::TypeMismatch(value)
}
}

View File

@ -1,25 +1,36 @@
// Pass for inlining functions that are just a reference to another one.
use crate::{
diagnostics::Info,
diagnostics::{Diagnostics, ToStringVerbose},
term::{Ctx, Name, Term},
};
use std::{collections::BTreeMap, fmt::Display};
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub struct CyclicDefErr;
impl Display for CyclicDefErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Definition is a reference to itself.")
}
}
impl Ctx<'_> {
// When we find a function that is simply directly calling another function,
// substitutes all occurrences of that function to the one being called, avoiding the unnecessary redirect.
// In case there is a long chain of ref-to-ref-to-ref, we substitute values by the last function in the chain.
pub fn simplify_ref_to_ref(&mut self) -> Result<(), Info> {
/// Substitutes all references of functions that are just
/// references to other functions by the function they reference.
///
/// In case there is a long chain of ref-to-ref-to-ref, we
/// substitute values by the last function in the chain.
///
/// ### Example:
/// ```hvm
/// A = @x @y (x y)
/// B = A
/// C = B
/// main = @x (C x)
/// ```
/// becomes
/// ```hvm
/// A = @x @y (x y)
/// B = A
/// C = A
/// main = @x (A x)
/// ```
/// Functions `B` and `C` will no longer be referenced anywhere in
/// the program.
pub fn simplify_ref_to_ref(&mut self) -> Result<(), Diagnostics> {
self.info.start_pass();
let mut ref_map: BTreeMap<Name, Name> = BTreeMap::new();
@ -29,7 +40,7 @@ impl Ctx<'_> {
let mut is_ref_to_ref = false;
while let Term::Ref { nam: next_ref } = &self.book.defs.get(ref_name).unwrap().rule().body {
if next_ref == ref_name {
self.info.def_error(def_name.clone(), CyclicDefErr);
self.info.add_rule_error(CyclicDefErr, def_name.clone());
continue 'outer;
}
ref_name = next_ref;
@ -70,3 +81,9 @@ pub fn subst_ref_to_ref(term: &mut Term, ref_map: &BTreeMap<Name, Name>) -> bool
}
})
}
impl ToStringVerbose for CyclicDefErr {
fn to_string_verbose(&self, _verbose: bool) -> String {
"Definition is a reference to itself.".to_string()
}
}

View File

@ -1,20 +1,19 @@
use hvml::{
compile_book, desugar_book,
diagnostics::Info,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity, ToStringVerbose},
net::{hvmc_to_net::hvmc_to_net, net_to_hvmc::net_to_hvmc},
run_book,
term::{
display::display_readback_errors, load_book::do_parse_book, net_to_term::net_to_term, parser::parse_term,
term_to_compat_net, term_to_net::Labels, AdtEncoding, Book, Ctx, Name, Term,
load_book::do_parse_book, net_to_term::net_to_term, parser::parse_term, term_to_compat_net,
term_to_net::Labels, AdtEncoding, Book, Ctx, Name, Term,
},
CompileOpts, RunOpts, WarningOpts,
CompileOpts, RunOpts,
};
use insta::assert_snapshot;
use itertools::Itertools;
use std::{
collections::HashMap,
fmt::Write,
fs,
io::Read,
path::{Path, PathBuf},
str::FromStr,
@ -24,7 +23,7 @@ use stdext::function_name;
use walkdir::WalkDir;
fn format_output(output: std::process::Output) -> String {
format!("{}\n{}", String::from_utf8_lossy(&output.stderr), String::from_utf8_lossy(&output.stdout))
format!("{}{}", String::from_utf8_lossy(&output.stderr), String::from_utf8_lossy(&output.stdout))
}
fn do_parse_term(code: &str) -> Result<Term, String> {
@ -39,9 +38,9 @@ const TESTS_PATH: &str = "/tests/golden_tests/";
fn run_single_golden_test(
path: &Path,
run: &[&dyn Fn(&str, &Path) -> Result<String, Info>],
run: &[&dyn Fn(&str, &Path) -> Result<String, Diagnostics>],
) -> Result<(), String> {
let code = fs::read_to_string(path).map_err(|e| e.to_string())?;
let code = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
let file_name = path.to_str().and_then(|path| path.rsplit_once(TESTS_PATH)).unwrap().1;
// unfortunately we need to do this
@ -69,11 +68,14 @@ fn run_single_golden_test(
Ok(())
}
fn run_golden_test_dir(test_name: &str, run: &dyn Fn(&str, &Path) -> Result<String, Info>) {
fn run_golden_test_dir(test_name: &str, run: &dyn Fn(&str, &Path) -> Result<String, Diagnostics>) {
run_golden_test_dir_multiple(test_name, &[run])
}
fn run_golden_test_dir_multiple(test_name: &str, run: &[&dyn Fn(&str, &Path) -> Result<String, Info>]) {
fn run_golden_test_dir_multiple(
test_name: &str,
run: &[&dyn Fn(&str, &Path) -> Result<String, Diagnostics>],
) {
let root = PathBuf::from(format!(
"{}{TESTS_PATH}{}",
env!("CARGO_MANIFEST_DIR"),
@ -103,13 +105,13 @@ fn compile_term() {
term.check_unbound_vars(&mut HashMap::new(), &mut vec);
if !vec.is_empty() {
return Err(vec.into_iter().join("\n").into());
return Err(vec.into_iter().map(|e| e.to_string_verbose(true)).join("\n").into());
}
term.make_var_names_unique();
term.linearize_vars();
let compat_net = term_to_compat_net(&term, &mut Default::default());
let net = net_to_hvmc(&compat_net)?;
let net = net_to_hvmc(&compat_net).map_err(|e| e.to_string_verbose(true))?;
Ok(format!("{}", net))
})
@ -118,33 +120,36 @@ fn compile_term() {
#[test]
fn compile_file_o_all() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Warning, true);
let mut book = do_parse_book(code, path)?;
let compiled = compile_book(&mut book, true /*ignore check_cycles*/, CompileOpts::heavy(), None)?;
Ok(format!("{:?}", compiled))
let res = compile_book(&mut book, CompileOpts::heavy(), diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
})
}
#[test]
fn compile_file() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Warning, true);
let mut book = do_parse_book(code, path)?;
let compiled = compile_book(&mut book, true /*ignore check_cycles*/, CompileOpts::light(), None)?;
Ok(format!("{:?}", compiled))
let res = compile_book(&mut book, CompileOpts::light(), diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
})
}
#[test]
fn linear_readback() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let book = do_parse_book(code, path)?;
let (res, info) = run_book(
book,
1 << 20,
RunOpts { linear: true, ..Default::default() },
WarningOpts::deny_all(),
CompileOpts::heavy(),
diagnostics_cfg,
None,
)?;
Ok(format!("{}{}", display_readback_errors(&info.readback_errors), res))
Ok(format!("{}{}", info.diagnostics, res))
});
}
#[test]
@ -172,6 +177,10 @@ fn run_file() {
#[test]
fn run_lazy() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig {
mutual_recursion_cycle: Severity::Allow,
..DiagnosticsConfig::new(Severity::Error, true)
};
let book = do_parse_book(code, path)?;
let mut desugar_opts = CompileOpts::heavy();
@ -179,8 +188,8 @@ fn run_lazy() {
desugar_opts.lazy_mode();
// 1 million nodes for the test runtime. Smaller doesn't seem to make it any faster
let (res, info) = run_book(book, 1 << 24, run_opts, WarningOpts::deny_all(), desugar_opts, None)?;
Ok(format!("{}{}", display_readback_errors(&info.readback_errors), res))
let (res, info) = run_book(book, 1 << 24, run_opts, desugar_opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", info.diagnostics, res))
})
}
@ -190,16 +199,18 @@ fn readback_lnet() {
let net = do_parse_net(code)?;
let book = Book::default();
let compat_net = hvmc_to_net(&net);
let (term, errors) = net_to_term(&compat_net, &book, &Labels::default(), false);
Ok(format!("{}{}", display_readback_errors(&errors), term))
let mut diags = Diagnostics::default();
let term = net_to_term(&compat_net, &book, &Labels::default(), false, &mut diags);
Ok(format!("{}{}", diags, term))
})
}
#[test]
fn simplify_matches() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let mut book = do_parse_book(code, path)?;
let mut ctx = Ctx::new(&mut book);
let mut ctx = Ctx::new(&mut book, diagnostics_cfg);
ctx.check_shared_names();
ctx.set_entrypoint();
ctx.book.encode_adts(AdtEncoding::TaggedScott);
@ -214,7 +225,7 @@ fn simplify_matches() {
ctx.check_ctrs_arities()?;
ctx.check_unbound_vars()?;
ctx.simplify_matches()?;
ctx.linearize_simple_matches(true)?;
ctx.book.linearize_simple_matches(true);
ctx.check_unbound_vars()?;
ctx.book.make_var_names_unique();
ctx.book.linearize_vars();
@ -236,8 +247,9 @@ fn encode_pattern_match() {
run_golden_test_dir(function_name!(), &|code, path| {
let mut result = String::new();
for adt_encoding in [AdtEncoding::TaggedScott, AdtEncoding::Scott] {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let mut book = do_parse_book(code, path)?;
let mut ctx = Ctx::new(&mut book);
let mut ctx = Ctx::new(&mut book, diagnostics_cfg);
ctx.check_shared_names();
ctx.set_entrypoint();
ctx.book.encode_adts(adt_encoding);
@ -252,7 +264,7 @@ fn encode_pattern_match() {
ctx.check_ctrs_arities()?;
ctx.check_unbound_vars()?;
ctx.simplify_matches()?;
ctx.linearize_simple_matches(true)?;
ctx.book.linearize_simple_matches(true);
ctx.book.encode_simple_matches(adt_encoding);
ctx.check_unbound_vars()?;
ctx.book.make_var_names_unique();
@ -269,8 +281,9 @@ fn encode_pattern_match() {
#[test]
fn desugar_file() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let mut book = do_parse_book(code, path)?;
desugar_book(&mut book, CompileOpts::light(), None)?;
desugar_book(&mut book, CompileOpts::light(), diagnostics_cfg, None)?;
Ok(book.to_string())
})
}
@ -281,40 +294,42 @@ fn hangs() {
let expected_normalization_time = 5;
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let book = do_parse_book(code, path)?;
let lck = Arc::new(RwLock::new(false));
let got = lck.clone();
std::thread::spawn(move || {
let _ =
run_book(book, 1 << 20, RunOpts::default(), WarningOpts::deny_all(), CompileOpts::heavy(), None);
let _ = run_book(book, 1 << 20, RunOpts::default(), CompileOpts::heavy(), diagnostics_cfg, None);
*got.write().unwrap() = true;
});
std::thread::sleep(std::time::Duration::from_secs(expected_normalization_time));
if !*lck.read().unwrap() { Ok("Hangs".into()) } else { Err("Doesn't hang".into()) }
if !*lck.read().unwrap() { Ok("Hangs".into()) } else { Err("Doesn't hang".to_string().into()) }
})
}
#[test]
fn compile_entrypoint() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let mut book = do_parse_book(code, path)?;
book.entrypoint = Some(Name::from("foo"));
let compiled = compile_book(&mut book, true /*ignore check_cycles*/, CompileOpts::light(), None)?;
Ok(format!("{:?}", compiled))
let res = compile_book(&mut book, CompileOpts::light(), diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
})
}
#[test]
fn run_entrypoint() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let mut book = do_parse_book(code, path)?;
book.entrypoint = Some(Name::from("foo"));
// 1 million nodes for the test runtime. Smaller doesn't seem to make it any faster
let (res, info) =
run_book(book, 1 << 24, RunOpts::default(), WarningOpts::deny_all(), CompileOpts::heavy(), None)?;
Ok(format!("{}{}", display_readback_errors(&info.readback_errors), res))
run_book(book, 1 << 24, RunOpts::default(), CompileOpts::heavy(), diagnostics_cfg, None)?;
Ok(format!("{}{}", info.diagnostics, res))
})
}
@ -325,7 +340,7 @@ fn cli() {
assert!(args_path.set_extension("args"));
let mut args_buf = String::with_capacity(16);
let mut args_file = fs::File::open(args_path).expect("File exists");
let mut args_file = std::fs::File::open(args_path).expect("File exists");
args_file.read_to_string(&mut args_buf).expect("Read args");
let args = args_buf.lines();
@ -339,8 +354,12 @@ fn cli() {
#[test]
fn mutual_recursion() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig {
mutual_recursion_cycle: Severity::Error,
..DiagnosticsConfig::new(Severity::Allow, true)
};
let mut book = do_parse_book(code, path)?;
let compiled = compile_book(&mut book, false, CompileOpts::light(), None)?;
Ok(format!("{:?}", compiled))
let res = compile_book(&mut book, CompileOpts::light(), diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
})
}

View File

@ -0,0 +1,2 @@
compile
tests/golden_tests/cli/warn_and_err.hvm

View File

@ -0,0 +1,3 @@
Foo a a = a
Main = (Foo a)

View File

@ -0,0 +1,3 @@
Foo a a = a
Main = (Foo a)

View File

@ -6,6 +6,5 @@ Warnings:
In definition 'I':
Definition is unused.
@I = #5
@main = *

View File

@ -2,6 +2,10 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/cli/desugar_merge.hvm
---
Warnings:
In definition 'Z':
Definition is unused.
(F_$_Z) = λ* λb b
(main) = F_$_Z

View File

@ -2,6 +2,10 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/cli/desugar_ref_to_ref.hvm
---
Warnings:
In definition 'Bar':
Definition is unused.
(Foo) = λ* 0
(Bar) = Foo

View File

@ -2,6 +2,10 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/cli/desugar_simplify_main.hvm
---
Warnings:
In definition 'id':
Definition is unused.
(id) = λa a
(main) = λa a

View File

@ -0,0 +1,11 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/cli/warn_and_err.hvm
---
Warnings:
In definition 'Foo':
Repeated bind inside rule pattern: 'a'.
Errors:
In definition 'Main':
Unbound variable 'a'.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/crlf.hvm
---
// Warnings:
Warnings:
In definition 'a':
Definition is unused.

View File

@ -2,4 +2,5 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/error_data_def_name.hvm
---
Duplicated top-level name 'A'.
Errors:
Function 'A' has the same name as a previously defined constructor

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/error_messages.hvm
---
Errors:
At tests/golden_tests/compile_file/error_messages.hvm:3:10: Repeated constructor 'B'
 3 | data C = (B)

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_a_name.hvm
---
Errors:
At tests/golden_tests/compile_file/just_a_name.hvm:1:1: found end of input expected '+', '(', '[', or '='
 1 | asdf

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_data.hvm
---
Errors:
At tests/golden_tests/compile_file/just_data.hvm:1:5: found end of input expected <Name>
 1 | data

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_paren.hvm
---
Errors:
At tests/golden_tests/compile_file/just_paren.hvm:2:1: found end of input expected <Name>
 2 | (

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_rule_paren.hvm
---
Errors:
At tests/golden_tests/compile_file/just_rule_paren.hvm:1:6: found end of input expected =
 1 | (rule)

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/match_num_all_patterns.hvm
---
// Warnings:
Warnings:
In definition 'succ_var':
Definition is unused.
In definition 'succ_var_zero':

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/match_num_unscoped_lambda.hvm
---
// Warnings:
Warnings:
In definition 'lambda_in':
Definition is unused.
In definition 'lambda_out':

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/missing_adt_eq.hvm
---
Errors:
At tests/golden_tests/compile_file/missing_adt_eq.hvm:1:9: found end of input expected '='
 1 | data Adt

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/missing_ctrs.hvm
---
Errors:
At tests/golden_tests/compile_file/missing_ctrs.hvm:1:12: found end of input expected constructor
 1 | data Adt = 

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/missing_pat.hvm
---
Errors:
At tests/golden_tests/compile_file/missing_pat.hvm:2:3: found ':' expected '(', '#', '$', <Name>, '[', '{', 'λ', 'let', 'match', '*', '|', <Num>+, <Num>, <Char>, or <String>
 2 | : *

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/nested_ctr_wrong_arity.hvm
---
Errors:
In definition 'fst_fst':
Constructor arity mismatch in pattern matching. Constructor 'Pair' expects 2 fields, found 1.

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/ref_to_main.hvm
---
Errors:
In definition 'Foo':
Main definition can't be referenced inside the program.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/repeated_bind_match.hvm
---
// Warnings:
Warnings:
In definition 'main':
Repeated bind inside match arm: 'x'.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/repeated_bind_rule.hvm
---
// Warnings:
Warnings:
In definition 'Foo':
Repeated bind inside rule pattern: 'a'.

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/unbound_unscoped_var.hvm
---
Errors:
In definition 'main':
Unbound unscoped variable '$a'.

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/unbound_with_tup_pattern.hvm
---
Errors:
In definition 'Foo':
Unbound variable 'a'.

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/unexpected_top_char.hvm
---
Errors:
At tests/golden_tests/compile_file/unexpected_top_char.hvm:1:1: found end of input expected data, <Name>, or '('
 1 | *

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/unscoped_dup_use.hvm
---
Errors:
In definition 'main':
Unscoped variable '$a' used more than once.

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/unused_unscoped_bind.hvm
---
Errors:
In definition 'main':
Unscoped variable from lambda 'λ$a' is never used.

View File

@ -0,0 +1,11 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/warn_and_err.hvm
---
Warnings:
In definition 'Foo':
Repeated bind inside rule pattern: 'a'.
Errors:
In definition 'Main':
Unbound variable 'a'.

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/wrong_ctr_arity.hvm
---
Errors:
In definition 'Bar':
Constructor arity mismatch in pattern matching. Constructor 'Box' expects 1 fields, found 2.

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/wrong_ctr_var_arity.hvm
---
Errors:
In definition 'foo':
Constructor arity mismatch in pattern matching. Constructor 'pair' expects 2 fields, found 0.

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/adt_string.hvm
---
Errors:
At tests/golden_tests/compile_file_o_all/adt_string.hvm:1:6: String is a built-in datatype and should not be overridden.
 1 | data String = S

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/bad_parens_making_erased_let.hvm
---
Errors:
In definition 'main':
Unbound variable 'two'.
Unbound variable 'qua'.

View File

@ -2,6 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/cyclic_dup.hvm
---
Errors:
In definition 'main':
Unbound variable 'y1'.

View File

@ -2,4 +2,5 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/double_main.hvm
---
Errors:
File has both 'main' and 'Main' definitions.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/let_adt_non_exhaustive.hvm
---
Errors:
In definition 'main':
Non-exhaustive pattern matching. Hint:
Case 'None' not covered.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/match_adt_non_exhaustive.hvm
---
Errors:
In definition 'main':
Non-exhaustive pattern matching. Hint:
Case 'Some' not covered.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/non_exhaustive_and.hvm
---
Errors:
In definition 'Bool.and':
Non-exhaustive pattern matching. Hint:
Case 'F' not covered.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/non_exhaustive_different_types.hvm
---
Errors:
In definition 'foo':
Non-exhaustive pattern matching. Hint:
Case 't3' not covered.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/non_exhaustive_pattern.hvm
---
Errors:
In definition 'Foo':
Non-exhaustive pattern matching. Hint:
Case 'A' not covered.

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/non_exhaustive_tree.hvm
---
Errors:
In definition 'Warp':
Non-exhaustive pattern matching. Hint:
Case 'Both' not covered.

View File

@ -2,8 +2,13 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/repeated_name_trucation.hvm
---
Warnings:
Mutual recursion cycle detected in compiled functions:
Cycle 1: long_name_that_truncates -> long_name_that_truncates$S0 -> long_name_that_truncates
This program will expand infinitely in strict evaluation mode.
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information.
@long_name_that_truncates = (* @long_name_that_truncates$S0)
@long_name_that_truncates$S0 = (* @long_name_that_truncates)
@main = a
& @long_name_that_truncates ~ ((b b) a)

View File

@ -2,8 +2,8 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/self_ref.hvm
---
Errors:
In definition 'Foo':
Definition is a reference to itself.
In definition 'main':
Definition is a reference to itself.

View File

@ -2,4 +2,5 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_term/cyclic_global_lam.hvm
---
Errors:
Found term that compiles into an inet with a vicious cycle

View File

@ -2,4 +2,5 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_term/unbound_var.hvm
---
Errors:
Unbound variable 'a'.

View File

@ -2,4 +2,5 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_term/unbound_var_scope.hvm
---
Errors:
Unbound variable 'b'.

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_term/wrong_nums.hvm
---
Errors:
found invalid number literal expected number
found invalid number literal expected number

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/desugar_file/non_exaustive_limit.hvm
---
Errors:
In definition 'Bar':
Non-exhaustive pattern matching. Hint:
Case 'B' not covered.

View File

@ -0,0 +1,7 @@
---
source: tests/golden_tests.rs
assertion_line: 64
input_file: tests/golden_tests/hangs/recursive_with_unscoped.hvm
---
Errors:
Doesn't hang

View File

@ -2,7 +2,8 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/mutual_recursion/a_b_c.hvm
---
Errors:
Mutual recursion cycle detected in compiled functions:
Cycle 1: A -> B -> C -> A
This program will expand infinitely in strict evaluation mode.
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information.
Cycle 1: A -> B -> C -> A
This program will expand infinitely in strict evaluation mode.
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information.

View File

@ -2,10 +2,11 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/mutual_recursion/multiple.hvm
---
Errors:
Mutual recursion cycle detected in compiled functions:
Cycle 1: A -> B -> C -> A
Cycle 2: H -> I -> H
Cycle 3: M -> M
Cycle 4: N -> N
This program will expand infinitely in strict evaluation mode.
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information.
Cycle 1: A -> B -> C -> A
Cycle 2: H -> I -> H
Cycle 3: M -> M
Cycle 4: N -> N
This program will expand infinitely in strict evaluation mode.
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information.

View File

@ -2,7 +2,8 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/mutual_recursion/odd_even.hvm
---
Errors:
Mutual recursion cycle detected in compiled functions:
Cycle 1: isEven -> isOdd -> isEven
This program will expand infinitely in strict evaluation mode.
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information.
Cycle 1: isEven -> isOdd -> isEven
This program will expand infinitely in strict evaluation mode.
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information.

View File

@ -2,5 +2,6 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/parse_file/repeated_adt_name.hvm
---
Errors:
At tests/golden_tests/parse_file/repeated_adt_name.hvm:2:6: Repeated datatype 'Foo'
 2 | data Foo = B

View File

@ -2,6 +2,8 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/readback_lnet/bad_net.hvm
---
Readback Warning:
Reached Root
Warnings:
During readback:
Reached Root.
<Invalid>

View File

@ -2,7 +2,8 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/readback_lnet/invalid_mat_mat.hvm
---
Readback Warning:
Invalid Numeric Match (4 occurrences)
Warnings:
During readback:
Invalid Numeric Match. (4 occurrences)
λa match match <Invalid> { 0: 3; 1+*: 4 } { 0: <Invalid>; 1+: <Invalid> }

View File

@ -2,7 +2,8 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/readback_lnet/invalid_op2_op2.hvm
---
Readback Warning:
Invalid Numeric Operation (2 occurrences)
Warnings:
During readback:
Invalid Numeric Operation. (2 occurrences)
λa (+ (+ <Invalid> 1) (1 <Invalid>))

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/addition.hvm
---
Lazy mode:
10
Strict mode:
10

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/adt_match.hvm
---
Lazy mode:
(Some 2)
Strict mode:
(Some 2)

View File

@ -3,15 +3,17 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/adt_match_wrong_tag.hvm
---
Lazy mode:
Warnings:
During readback:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'.
Invalid Adt Match.
Readback Warning:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'
Invalid Adt Match
λa match a { (Some Some.val): #Option (#wrong_tag λb b Some.val); (None): * }
Strict mode:
Warnings:
During readback:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'.
Invalid Adt Match.
Readback Warning:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'
Invalid Adt Match
λa match a { (Some Some.val): #Option (#wrong_tag λb b Some.val); (None): * }

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/adt_option_and.hvm
---
Lazy mode:
λa λb match a { (Some c): match b { (Some d): (Some (c, d)); (None): None }; (None): None }
Strict mode:
λa match a { (Some b): λc (match c { (Some d): λe (Some (e, d)); (None): λ* None } b); (None): λ* None }

View File

@ -3,15 +3,17 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/adt_wrong_tag.hvm
---
Lazy mode:
Warnings:
During readback:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'.
Invalid Adt Match.
Readback Warning:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'
Invalid Adt Match
λa match a { (Some Some.val): #Option (#wrong_tag λb b Some.val); (None): * }
Strict mode:
Warnings:
During readback:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'.
Invalid Adt Match.
Readback Warning:
Unexpected tag found during Adt readback, expected '#Option', but found '#wrong_tag'
Invalid Adt Match
λa match a { (Some Some.val): #Option (#wrong_tag λb b Some.val); (None): * }

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/and.hvm
---
Lazy mode:
false
Strict mode:
false

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/bitonic_sort.hvm
---
Lazy mode:
120
Strict mode:
120

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/bitonic_sort_lam.hvm
---
Lazy mode:
32640
Strict mode:
32640

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/box.hvm
---
Lazy mode:
(Box 10)
Strict mode:
(Box 10)

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/box2.hvm
---
Lazy mode:
(Box 4)
Strict mode:
(Box 4)

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/callcc.hvm
---
Lazy mode:
52
Strict mode:
52

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/chars.hvm
---
Lazy mode:
"ሴ!7"
Strict mode:
"ሴ!7"

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/chars_forall.hvm
---
Lazy mode:
8704
Strict mode:
8704

View File

@ -3,9 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/chars_lambda.hvm
---
Lazy mode:
955
Strict mode:
955

View File

@ -3,15 +3,14 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/def_bool_num.hvm
---
Lazy mode:
Errors:
In definition 'go':
Non-exhaustive pattern matching. Hint:
Case '+' not covered.
Strict mode:
Errors:
In definition 'go':
Non-exhaustive pattern matching. Hint:
Case '+' not covered.

Some files were not shown because too many files have changed in this diff Show More