mirror of
https://github.com/HigherOrderCO/Bend.git
synced 2024-09-11 03:45:29 +03:00
[sc-503] Refactor error passing
This commit is contained in:
parent
d788c0a484
commit
3cc5a7a242
@ -13,7 +13,7 @@ cd hvm-lang
|
||||
|
||||
Install using cargo:
|
||||
```bash
|
||||
cargo install --path .
|
||||
cargo install --path . --locked
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
6
justfile
6
justfile
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
181
src/lib.rs
181
src/lib.rs
@ -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>,
|
||||
|
373
src/main.rs
373
src/main.rs
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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}'."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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.")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
2
tests/golden_tests/cli/warn_and_err.args
Normal file
2
tests/golden_tests/cli/warn_and_err.args
Normal file
@ -0,0 +1,2 @@
|
||||
compile
|
||||
tests/golden_tests/cli/warn_and_err.hvm
|
3
tests/golden_tests/cli/warn_and_err.hvm
Normal file
3
tests/golden_tests/cli/warn_and_err.hvm
Normal file
@ -0,0 +1,3 @@
|
||||
Foo a a = a
|
||||
|
||||
Main = (Foo a)
|
3
tests/golden_tests/compile_file/warn_and_err.hvm
Normal file
3
tests/golden_tests/compile_file/warn_and_err.hvm
Normal file
@ -0,0 +1,3 @@
|
||||
Foo a a = a
|
||||
|
||||
Main = (Foo a)
|
@ -6,6 +6,5 @@ Warnings:
|
||||
In definition 'I':
|
||||
Definition is unused.
|
||||
|
||||
|
||||
@I = #5
|
||||
@main = *
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
11
tests/snapshots/cli__warn_and_err.hvm.snap
Normal file
11
tests/snapshots/cli__warn_and_err.hvm.snap
Normal 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'.
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
[0m 3 | data C = [4m[31m(B)[0m
|
||||
|
@ -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 '='
|
||||
[0m 1 | [4m[31masdf[0m
|
||||
|
@ -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>
|
||||
[0m 1 | data[0m
|
||||
|
@ -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>
|
||||
[0m 2 | [4m[31m([0m
|
||||
|
@ -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 =
|
||||
[0m 1 | (rule[4m[31m)[0m
|
||||
|
@ -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':
|
||||
|
@ -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':
|
||||
|
@ -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 '='
|
||||
[0m 1 | data Adt[0m
|
||||
|
@ -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
|
||||
[0m 1 | data Adt =[4m[31m[0m [0m
|
||||
|
@ -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>
|
||||
[0m 2 | [4m[31m:[0m *[0m
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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'.
|
||||
|
||||
|
@ -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'.
|
||||
|
||||
|
@ -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'.
|
||||
|
||||
|
@ -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'.
|
||||
|
||||
|
@ -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 '('
|
||||
[0m 1 | [4m[31m*[0m
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
11
tests/snapshots/compile_file__warn_and_err.hvm.snap
Normal file
11
tests/snapshots/compile_file__warn_and_err.hvm.snap
Normal 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'.
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
[0m 1 | data [4m[31mString[0m = S[0m
|
||||
|
@ -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'.
|
||||
|
||||
|
@ -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'.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -2,4 +2,5 @@
|
||||
source: tests/golden_tests.rs
|
||||
input_file: tests/golden_tests/compile_term/unbound_var.hvm
|
||||
---
|
||||
Errors:
|
||||
Unbound variable 'a'.
|
||||
|
@ -2,4 +2,5 @@
|
||||
source: tests/golden_tests.rs
|
||||
input_file: tests/golden_tests/compile_term/unbound_var_scope.hvm
|
||||
---
|
||||
Errors:
|
||||
Unbound variable 'b'.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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'
|
||||
[0m 2 | data [4m[31mFoo[0m = B[0m
|
||||
|
@ -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>
|
||||
|
@ -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> }
|
||||
|
@ -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>))
|
||||
|
@ -3,9 +3,7 @@ source: tests/golden_tests.rs
|
||||
input_file: tests/golden_tests/run_file/addition.hvm
|
||||
---
|
||||
Lazy mode:
|
||||
|
||||
10
|
||||
|
||||
Strict mode:
|
||||
|
||||
10
|
||||
|
@ -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)
|
||||
|
@ -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): * }
|
||||
|
@ -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 }
|
||||
|
@ -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): * }
|
||||
|
@ -3,9 +3,7 @@ source: tests/golden_tests.rs
|
||||
input_file: tests/golden_tests/run_file/and.hvm
|
||||
---
|
||||
Lazy mode:
|
||||
|
||||
false
|
||||
|
||||
Strict mode:
|
||||
|
||||
false
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -3,9 +3,7 @@ source: tests/golden_tests.rs
|
||||
input_file: tests/golden_tests/run_file/callcc.hvm
|
||||
---
|
||||
Lazy mode:
|
||||
|
||||
52
|
||||
|
||||
Strict mode:
|
||||
|
||||
52
|
||||
|
@ -3,9 +3,7 @@ source: tests/golden_tests.rs
|
||||
input_file: tests/golden_tests/run_file/chars.hvm
|
||||
---
|
||||
Lazy mode:
|
||||
|
||||
"ሴ!7"
|
||||
|
||||
Strict mode:
|
||||
|
||||
"ሴ!7"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user