use crate::lcnf::*;
use super::functions::*;
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Clone, PartialEq)]
pub enum HaskellDecl {
Data(HaskellDataDecl),
Newtype(HaskellNewtype),
TypeClass(HaskellTypeClass),
Instance(HaskellInstance),
Function(HaskellFunction),
TypeSynonym(String, Vec<String>, HaskellType),
Comment(String),
RawLine(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellDataDecl {
pub name: String,
pub type_params: Vec<String>,
pub constructors: Vec<(String, Vec<HaskellType>)>,
pub deriving_clauses: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellCaseAlt {
pub pattern: HaskellPattern,
pub guards: Vec<HaskellGuard>,
pub body: Option<HaskellExpr>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HaskellLit {
Int(i64),
Float(f64),
Char(char),
Str(String),
Bool(bool),
Unit,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HaskellDoStmt {
Bind(String, HaskellExpr),
Stmt(HaskellExpr),
LetBind(String, HaskellExpr),
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellNewtype {
pub name: String,
pub type_param: Option<String>,
pub constructor: String,
pub field: (String, HaskellType),
pub deriving_clauses: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellModule {
pub name: String,
pub exports: Vec<String>,
pub imports: Vec<HaskellImport>,
pub declarations: Vec<HaskellDecl>,
}
impl HaskellModule {
pub fn new(name: impl Into<String>) -> Self {
HaskellModule {
name: name.into(),
exports: Vec::new(),
imports: Vec::new(),
declarations: Vec::new(),
}
}
pub fn add_import(&mut self, imp: HaskellImport) {
self.imports.push(imp);
}
pub fn add_decl(&mut self, decl: HaskellDecl) {
self.declarations.push(decl);
}
pub fn emit(&self) -> String {
let mut out = String::new();
if !self.exports.is_empty() {
out.push_str(&format!("module {} (\n", self.name));
for (i, exp) in self.exports.iter().enumerate() {
if i > 0 {
out.push_str(",\n");
}
out.push_str(&format!(" {}", exp));
}
out.push_str("\n) where\n\n");
} else {
out.push_str(&format!("module {} where\n\n", self.name));
}
for imp in &self.imports {
out.push_str(&format!("{}\n", imp));
}
if !self.imports.is_empty() {
out.push('\n');
}
for decl in &self.declarations {
out.push_str(&format!("{}\n", decl));
}
out
}
}
#[derive(Debug, Clone, Default)]
pub struct HsExtEmitStats {
pub bytes_emitted: usize,
pub items_emitted: usize,
pub errors: usize,
pub warnings: usize,
pub elapsed_ms: u64,
}
impl HsExtEmitStats {
pub fn new() -> Self {
HsExtEmitStats::default()
}
pub fn throughput_bps(&self) -> f64 {
if self.elapsed_ms == 0 {
0.0
} else {
self.bytes_emitted as f64 / (self.elapsed_ms as f64 / 1000.0)
}
}
pub fn is_clean(&self) -> bool {
self.errors == 0
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HskLivenessInfo {
pub live_in: Vec<std::collections::HashSet<u32>>,
pub live_out: Vec<std::collections::HashSet<u32>>,
pub defs: Vec<std::collections::HashSet<u32>>,
pub uses: Vec<std::collections::HashSet<u32>>,
}
impl HskLivenessInfo {
#[allow(dead_code)]
pub fn new(block_count: usize) -> Self {
HskLivenessInfo {
live_in: vec![std::collections::HashSet::new(); block_count],
live_out: vec![std::collections::HashSet::new(); block_count],
defs: vec![std::collections::HashSet::new(); block_count],
uses: vec![std::collections::HashSet::new(); block_count],
}
}
#[allow(dead_code)]
pub fn add_def(&mut self, block: usize, var: u32) {
if block < self.defs.len() {
self.defs[block].insert(var);
}
}
#[allow(dead_code)]
pub fn add_use(&mut self, block: usize, var: u32) {
if block < self.uses.len() {
self.uses[block].insert(var);
}
}
#[allow(dead_code)]
pub fn is_live_in(&self, block: usize, var: u32) -> bool {
self.live_in
.get(block)
.map(|s| s.contains(&var))
.unwrap_or(false)
}
#[allow(dead_code)]
pub fn is_live_out(&self, block: usize, var: u32) -> bool {
self.live_out
.get(block)
.map(|s| s.contains(&var))
.unwrap_or(false)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HskDominatorTree {
pub idom: Vec<Option<u32>>,
pub dom_children: Vec<Vec<u32>>,
pub dom_depth: Vec<u32>,
}
impl HskDominatorTree {
#[allow(dead_code)]
pub fn new(size: usize) -> Self {
HskDominatorTree {
idom: vec![None; size],
dom_children: vec![Vec::new(); size],
dom_depth: vec![0; size],
}
}
#[allow(dead_code)]
pub fn set_idom(&mut self, node: usize, idom: u32) {
self.idom[node] = Some(idom);
}
#[allow(dead_code)]
pub fn dominates(&self, a: usize, b: usize) -> bool {
if a == b {
return true;
}
let mut cur = b;
loop {
match self.idom[cur] {
Some(parent) if parent as usize == a => return true,
Some(parent) if parent as usize == cur => return false,
Some(parent) => cur = parent as usize,
None => return false,
}
}
}
#[allow(dead_code)]
pub fn depth(&self, node: usize) -> u32 {
self.dom_depth.get(node).copied().unwrap_or(0)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HskWorklist {
pub(super) items: std::collections::VecDeque<u32>,
pub(super) in_worklist: std::collections::HashSet<u32>,
}
impl HskWorklist {
#[allow(dead_code)]
pub fn new() -> Self {
HskWorklist {
items: std::collections::VecDeque::new(),
in_worklist: std::collections::HashSet::new(),
}
}
#[allow(dead_code)]
pub fn push(&mut self, item: u32) -> bool {
if self.in_worklist.insert(item) {
self.items.push_back(item);
true
} else {
false
}
}
#[allow(dead_code)]
pub fn pop(&mut self) -> Option<u32> {
let item = self.items.pop_front()?;
self.in_worklist.remove(&item);
Some(item)
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.items.len()
}
#[allow(dead_code)]
pub fn contains(&self, item: u32) -> bool {
self.in_worklist.contains(&item)
}
}
#[derive(Debug, Default)]
pub struct HsExtSourceBuffer {
pub(super) buf: String,
pub(super) indent_level: usize,
pub(super) indent_str: String,
}
impl HsExtSourceBuffer {
pub fn new() -> Self {
HsExtSourceBuffer {
buf: String::new(),
indent_level: 0,
indent_str: " ".to_string(),
}
}
pub fn with_indent(mut self, indent: impl Into<String>) -> Self {
self.indent_str = indent.into();
self
}
pub fn push_line(&mut self, line: &str) {
for _ in 0..self.indent_level {
self.buf.push_str(&self.indent_str);
}
self.buf.push_str(line);
self.buf.push('\n');
}
pub fn push_raw(&mut self, s: &str) {
self.buf.push_str(s);
}
pub fn indent(&mut self) {
self.indent_level += 1;
}
pub fn dedent(&mut self) {
self.indent_level = self.indent_level.saturating_sub(1);
}
pub fn as_str(&self) -> &str {
&self.buf
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn line_count(&self) -> usize {
self.buf.lines().count()
}
pub fn into_string(self) -> String {
self.buf
}
pub fn reset(&mut self) {
self.buf.clear();
self.indent_level = 0;
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HskPassConfig {
pub phase: HskPassPhase,
pub enabled: bool,
pub max_iterations: u32,
pub debug_output: bool,
pub pass_name: String,
}
impl HskPassConfig {
#[allow(dead_code)]
pub fn new(name: impl Into<String>, phase: HskPassPhase) -> Self {
HskPassConfig {
phase,
enabled: true,
max_iterations: 10,
debug_output: false,
pass_name: name.into(),
}
}
#[allow(dead_code)]
pub fn disabled(mut self) -> Self {
self.enabled = false;
self
}
#[allow(dead_code)]
pub fn with_debug(mut self) -> Self {
self.debug_output = true;
self
}
#[allow(dead_code)]
pub fn max_iter(mut self, n: u32) -> Self {
self.max_iterations = n;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HsExtIncrKey {
pub content_hash: u64,
pub config_hash: u64,
}
impl HsExtIncrKey {
pub fn new(content: u64, config: u64) -> Self {
HsExtIncrKey {
content_hash: content,
config_hash: config,
}
}
pub fn combined_hash(&self) -> u64 {
self.content_hash.wrapping_mul(0x9e3779b97f4a7c15) ^ self.config_hash
}
pub fn matches(&self, other: &HsExtIncrKey) -> bool {
self.content_hash == other.content_hash && self.config_hash == other.config_hash
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellGuard {
pub condition: HaskellExpr,
pub body: HaskellExpr,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct HskPassStats {
pub total_runs: u32,
pub successful_runs: u32,
pub total_changes: u64,
pub time_ms: u64,
pub iterations_used: u32,
}
impl HskPassStats {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
self.total_runs += 1;
self.successful_runs += 1;
self.total_changes += changes;
self.time_ms += time_ms;
self.iterations_used = iterations;
}
#[allow(dead_code)]
pub fn average_changes_per_run(&self) -> f64 {
if self.total_runs == 0 {
return 0.0;
}
self.total_changes as f64 / self.total_runs as f64
}
#[allow(dead_code)]
pub fn success_rate(&self) -> f64 {
if self.total_runs == 0 {
return 0.0;
}
self.successful_runs as f64 / self.total_runs as f64
}
#[allow(dead_code)]
pub fn format_summary(&self) -> String {
format!(
"Runs: {}/{}, Changes: {}, Time: {}ms",
self.successful_runs, self.total_runs, self.total_changes, self.time_ms
)
}
}
pub struct HaskellBackend {
pub(super) module: HaskellModule,
}
impl HaskellBackend {
pub fn new(module_name: impl Into<String>) -> Self {
let mut module = HaskellModule::new(module_name);
module.add_import(HaskellImport {
module: "Prelude".to_string(),
qualified: false,
alias: None,
items: Vec::new(),
hiding: Vec::new(),
});
HaskellBackend { module }
}
pub fn compile_decl(&mut self, decl: &LcnfFunDecl) {
let hs_fn = self.compile_fun(decl);
self.module.add_decl(HaskellDecl::Function(hs_fn));
}
pub(super) fn compile_fun(&self, decl: &LcnfFunDecl) -> HaskellFunction {
let params: Vec<HaskellPattern> = decl
.params
.iter()
.map(|p| HaskellPattern::Var(p.name.clone()))
.collect();
let body = self.compile_expr(&decl.body);
HaskellFunction {
name: sanitize_hs_ident(&decl.name),
type_annotation: None,
equations: vec![HaskellEquation {
patterns: params,
guards: Vec::new(),
body: Some(body),
where_clause: Vec::new(),
}],
}
}
pub(super) fn compile_expr(&self, expr: &LcnfExpr) -> HaskellExpr {
match expr {
LcnfExpr::Return(arg) => self.compile_arg(arg),
LcnfExpr::Let {
name, value, body, ..
} => {
let rhs_expr = self.compile_let_value(value);
let cont_expr = self.compile_expr(body);
HaskellExpr::Let(name.clone(), Box::new(rhs_expr), Box::new(cont_expr))
}
LcnfExpr::Case {
scrutinee,
alts,
default,
..
} => {
let scrut = HaskellExpr::Var(format!("{}", scrutinee));
let mut hs_alts: Vec<HaskellCaseAlt> =
alts.iter().map(|alt| self.compile_alt(alt)).collect();
if let Some(def) = default {
let def_expr = self.compile_expr(def);
hs_alts.push(HaskellCaseAlt {
pattern: HaskellPattern::Wildcard,
guards: Vec::new(),
body: Some(def_expr),
});
}
HaskellExpr::Case(Box::new(scrut), hs_alts)
}
LcnfExpr::TailCall(func, args) => {
let func_expr = self.compile_arg(func);
if args.is_empty() {
func_expr
} else {
let arg_exprs: Vec<HaskellExpr> =
args.iter().map(|a| self.compile_arg(a)).collect();
HaskellExpr::App(Box::new(func_expr), arg_exprs)
}
}
LcnfExpr::Unreachable => HaskellExpr::Var("undefined".to_string()),
}
}
pub(super) fn compile_let_value(&self, val: &LcnfLetValue) -> HaskellExpr {
match val {
LcnfLetValue::App(func, args) => {
let func_expr = self.compile_arg(func);
if args.is_empty() {
func_expr
} else {
let arg_exprs: Vec<HaskellExpr> =
args.iter().map(|a| self.compile_arg(a)).collect();
HaskellExpr::App(Box::new(func_expr), arg_exprs)
}
}
LcnfLetValue::Ctor(name, _tag, args) => {
let ctor_expr = HaskellExpr::Var(name.clone());
if args.is_empty() {
ctor_expr
} else {
let arg_exprs: Vec<HaskellExpr> =
args.iter().map(|a| self.compile_arg(a)).collect();
HaskellExpr::App(Box::new(ctor_expr), arg_exprs)
}
}
LcnfLetValue::Proj(_name, idx, var) => {
let accessor = match idx {
0 => "fst",
1 => "snd",
n => return HaskellExpr::Var(format!("_proj{}_{}", n, var)),
};
HaskellExpr::App(
Box::new(HaskellExpr::Var(accessor.to_string())),
vec![HaskellExpr::Var(format!("{}", var))],
)
}
LcnfLetValue::Lit(lit) => match lit {
LcnfLit::Nat(n) => HaskellExpr::Lit(HaskellLit::Int(*n as i64)),
LcnfLit::Str(s) => HaskellExpr::Lit(HaskellLit::Str(s.clone())),
},
LcnfLetValue::Erased | LcnfLetValue::Reset(_) => HaskellExpr::Lit(HaskellLit::Unit),
LcnfLetValue::FVar(v) => HaskellExpr::Var(format!("{}", v)),
LcnfLetValue::Reuse(_, name, _tag, args) => {
let ctor_expr = HaskellExpr::Var(name.clone());
if args.is_empty() {
ctor_expr
} else {
let arg_exprs: Vec<HaskellExpr> =
args.iter().map(|a| self.compile_arg(a)).collect();
HaskellExpr::App(Box::new(ctor_expr), arg_exprs)
}
}
}
}
pub(super) fn compile_alt(&self, alt: &LcnfAlt) -> HaskellCaseAlt {
let body = self.compile_expr(&alt.body);
let pat = HaskellPattern::Constructor(
alt.ctor_name.clone(),
alt.params
.iter()
.map(|p| HaskellPattern::Var(p.name.clone()))
.collect(),
);
HaskellCaseAlt {
pattern: pat,
guards: Vec::new(),
body: Some(body),
}
}
pub(super) fn compile_arg(&self, arg: &LcnfArg) -> HaskellExpr {
match arg {
LcnfArg::Var(v) => HaskellExpr::Var(format!("{}", v)),
LcnfArg::Lit(lit) => match lit {
LcnfLit::Nat(n) => HaskellExpr::Lit(HaskellLit::Int(*n as i64)),
LcnfLit::Str(s) => HaskellExpr::Lit(HaskellLit::Str(s.clone())),
},
LcnfArg::Erased | LcnfArg::Type(_) => HaskellExpr::Lit(HaskellLit::Unit),
}
}
pub fn emit_module(&self) -> String {
self.module.emit()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellInstance {
pub class: String,
pub instance_type: HaskellType,
pub context: Vec<HaskellType>,
pub where_clause: Vec<HaskellFunction>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HsExtVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub pre: Option<String>,
}
impl HsExtVersion {
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
HsExtVersion {
major,
minor,
patch,
pre: None,
}
}
pub fn with_pre(mut self, pre: impl Into<String>) -> Self {
self.pre = Some(pre.into());
self
}
pub fn is_stable(&self) -> bool {
self.pre.is_none()
}
pub fn is_compatible_with(&self, other: &HsExtVersion) -> bool {
self.major == other.major && self.minor >= other.minor
}
}
#[allow(dead_code)]
pub struct HskConstantFoldingHelper;
impl HskConstantFoldingHelper {
#[allow(dead_code)]
pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
a.checked_add(b)
}
#[allow(dead_code)]
pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
a.checked_sub(b)
}
#[allow(dead_code)]
pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
a.checked_mul(b)
}
#[allow(dead_code)]
pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
if b == 0 {
None
} else {
a.checked_div(b)
}
}
#[allow(dead_code)]
pub fn fold_add_f64(a: f64, b: f64) -> f64 {
a + b
}
#[allow(dead_code)]
pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
a * b
}
#[allow(dead_code)]
pub fn fold_neg_i64(a: i64) -> Option<i64> {
a.checked_neg()
}
#[allow(dead_code)]
pub fn fold_not_bool(a: bool) -> bool {
!a
}
#[allow(dead_code)]
pub fn fold_and_bool(a: bool, b: bool) -> bool {
a && b
}
#[allow(dead_code)]
pub fn fold_or_bool(a: bool, b: bool) -> bool {
a || b
}
#[allow(dead_code)]
pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
a.checked_shl(b)
}
#[allow(dead_code)]
pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
a.checked_shr(b)
}
#[allow(dead_code)]
pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
if b == 0 {
None
} else {
Some(a % b)
}
}
#[allow(dead_code)]
pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
a & b
}
#[allow(dead_code)]
pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
a | b
}
#[allow(dead_code)]
pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
a ^ b
}
#[allow(dead_code)]
pub fn fold_bitnot_i64(a: i64) -> i64 {
!a
}
}
#[derive(Debug, Clone, Default)]
pub struct HsExtFeatures {
pub(super) flags: std::collections::HashSet<String>,
}
impl HsExtFeatures {
pub fn new() -> Self {
HsExtFeatures::default()
}
pub fn enable(&mut self, flag: impl Into<String>) {
self.flags.insert(flag.into());
}
pub fn disable(&mut self, flag: &str) {
self.flags.remove(flag);
}
pub fn is_enabled(&self, flag: &str) -> bool {
self.flags.contains(flag)
}
pub fn len(&self) -> usize {
self.flags.len()
}
pub fn is_empty(&self) -> bool {
self.flags.is_empty()
}
pub fn union(&self, other: &HsExtFeatures) -> HsExtFeatures {
HsExtFeatures {
flags: self.flags.union(&other.flags).cloned().collect(),
}
}
pub fn intersection(&self, other: &HsExtFeatures) -> HsExtFeatures {
HsExtFeatures {
flags: self.flags.intersection(&other.flags).cloned().collect(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum HsListQual {
Generator(String, HaskellExpr),
Guard(HaskellExpr),
LetBind(String, HaskellExpr),
}
#[derive(Debug)]
pub struct HsExtEventLog {
pub(super) entries: std::collections::VecDeque<String>,
pub(super) capacity: usize,
}
impl HsExtEventLog {
pub fn new(capacity: usize) -> Self {
HsExtEventLog {
entries: std::collections::VecDeque::with_capacity(capacity),
capacity,
}
}
pub fn push(&mut self, event: impl Into<String>) {
if self.entries.len() >= self.capacity {
self.entries.pop_front();
}
self.entries.push_back(event.into());
}
pub fn iter(&self) -> impl Iterator<Item = &String> {
self.entries.iter()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
#[allow(dead_code)]
pub struct HskPassRegistry {
pub(super) configs: Vec<HskPassConfig>,
pub(super) stats: std::collections::HashMap<String, HskPassStats>,
}
impl HskPassRegistry {
#[allow(dead_code)]
pub fn new() -> Self {
HskPassRegistry {
configs: Vec::new(),
stats: std::collections::HashMap::new(),
}
}
#[allow(dead_code)]
pub fn register(&mut self, config: HskPassConfig) {
self.stats
.insert(config.pass_name.clone(), HskPassStats::new());
self.configs.push(config);
}
#[allow(dead_code)]
pub fn enabled_passes(&self) -> Vec<&HskPassConfig> {
self.configs.iter().filter(|c| c.enabled).collect()
}
#[allow(dead_code)]
pub fn get_stats(&self, name: &str) -> Option<&HskPassStats> {
self.stats.get(name)
}
#[allow(dead_code)]
pub fn total_passes(&self) -> usize {
self.configs.len()
}
#[allow(dead_code)]
pub fn enabled_count(&self) -> usize {
self.enabled_passes().len()
}
#[allow(dead_code)]
pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
if let Some(stats) = self.stats.get_mut(name) {
stats.record_run(changes, time_ms, iter);
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum HskPassPhase {
Analysis,
Transformation,
Verification,
Cleanup,
}
impl HskPassPhase {
#[allow(dead_code)]
pub fn name(&self) -> &str {
match self {
HskPassPhase::Analysis => "analysis",
HskPassPhase::Transformation => "transformation",
HskPassPhase::Verification => "verification",
HskPassPhase::Cleanup => "cleanup",
}
}
#[allow(dead_code)]
pub fn is_modifying(&self) -> bool {
matches!(self, HskPassPhase::Transformation | HskPassPhase::Cleanup)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellFunction {
pub name: String,
pub type_annotation: Option<HaskellType>,
pub equations: Vec<HaskellEquation>,
}
#[derive(Debug, Default)]
pub struct HsExtNameScope {
pub(super) declared: std::collections::HashSet<String>,
pub(super) depth: usize,
pub(super) parent: Option<Box<HsExtNameScope>>,
}
impl HsExtNameScope {
pub fn new() -> Self {
HsExtNameScope::default()
}
pub fn declare(&mut self, name: impl Into<String>) -> bool {
self.declared.insert(name.into())
}
pub fn is_declared(&self, name: &str) -> bool {
self.declared.contains(name)
}
pub fn push_scope(self) -> Self {
HsExtNameScope {
declared: std::collections::HashSet::new(),
depth: self.depth + 1,
parent: Some(Box::new(self)),
}
}
pub fn pop_scope(self) -> Self {
*self.parent.unwrap_or_default()
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn len(&self) -> usize {
self.declared.len()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HskCacheEntry {
pub key: String,
pub data: Vec<u8>,
pub timestamp: u64,
pub valid: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HaskellPattern {
Wildcard,
Var(String),
Lit(HaskellLit),
Tuple(Vec<HaskellPattern>),
List(Vec<HaskellPattern>),
Cons(Box<HaskellPattern>, Box<HaskellPattern>),
Constructor(String, Vec<HaskellPattern>),
As(String, Box<HaskellPattern>),
LazyPat(Box<HaskellPattern>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum HaskellExpr {
Lit(HaskellLit),
Var(String),
App(Box<HaskellExpr>, Vec<HaskellExpr>),
Lambda(Vec<HaskellPattern>, Box<HaskellExpr>),
Let(String, Box<HaskellExpr>, Box<HaskellExpr>),
Where(Box<HaskellExpr>, Vec<HaskellFunction>),
If(Box<HaskellExpr>, Box<HaskellExpr>, Box<HaskellExpr>),
Case(Box<HaskellExpr>, Vec<HaskellCaseAlt>),
Do(Vec<HaskellDoStmt>),
ListComp(Box<HaskellExpr>, Vec<HsListQual>),
Tuple(Vec<HaskellExpr>),
List(Vec<HaskellExpr>),
Neg(Box<HaskellExpr>),
InfixApp(Box<HaskellExpr>, String, Box<HaskellExpr>),
Operator(String),
TypeAnnotation(Box<HaskellExpr>, HaskellType),
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellTypeClass {
pub name: String,
pub type_params: Vec<String>,
pub superclasses: Vec<HaskellType>,
pub methods: Vec<(String, HaskellType, Option<HaskellExpr>)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HaskellType {
Int,
Integer,
Double,
Float,
Bool,
Char,
HsString,
Unit,
IO(Box<HaskellType>),
List(Box<HaskellType>),
Maybe(Box<HaskellType>),
Either(Box<HaskellType>, Box<HaskellType>),
Tuple(Vec<HaskellType>),
Fun(Box<HaskellType>, Box<HaskellType>),
Custom(String),
Polymorphic(String),
Constraint(String, Vec<HaskellType>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellEquation {
pub patterns: Vec<HaskellPattern>,
pub guards: Vec<HaskellGuard>,
pub body: Option<HaskellExpr>,
pub where_clause: Vec<HaskellFunction>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HaskellImport {
pub module: String,
pub qualified: bool,
pub alias: Option<String>,
pub items: Vec<String>,
pub hiding: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct HsExtDiagMsg {
pub severity: HsExtDiagSeverity,
pub pass: String,
pub message: String,
}
impl HsExtDiagMsg {
pub fn error(pass: impl Into<String>, msg: impl Into<String>) -> Self {
HsExtDiagMsg {
severity: HsExtDiagSeverity::Error,
pass: pass.into(),
message: msg.into(),
}
}
pub fn warning(pass: impl Into<String>, msg: impl Into<String>) -> Self {
HsExtDiagMsg {
severity: HsExtDiagSeverity::Warning,
pass: pass.into(),
message: msg.into(),
}
}
pub fn note(pass: impl Into<String>, msg: impl Into<String>) -> Self {
HsExtDiagMsg {
severity: HsExtDiagSeverity::Note,
pass: pass.into(),
message: msg.into(),
}
}
}
#[derive(Debug, Default)]
pub struct HsExtDiagCollector {
pub(super) msgs: Vec<HsExtDiagMsg>,
}
impl HsExtDiagCollector {
pub fn new() -> Self {
HsExtDiagCollector::default()
}
pub fn emit(&mut self, d: HsExtDiagMsg) {
self.msgs.push(d);
}
pub fn has_errors(&self) -> bool {
self.msgs
.iter()
.any(|d| d.severity == HsExtDiagSeverity::Error)
}
pub fn errors(&self) -> Vec<&HsExtDiagMsg> {
self.msgs
.iter()
.filter(|d| d.severity == HsExtDiagSeverity::Error)
.collect()
}
pub fn warnings(&self) -> Vec<&HsExtDiagMsg> {
self.msgs
.iter()
.filter(|d| d.severity == HsExtDiagSeverity::Warning)
.collect()
}
pub fn len(&self) -> usize {
self.msgs.len()
}
pub fn is_empty(&self) -> bool {
self.msgs.is_empty()
}
pub fn clear(&mut self) {
self.msgs.clear();
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HskAnalysisCache {
pub(super) entries: std::collections::HashMap<String, HskCacheEntry>,
pub(super) max_size: usize,
pub(super) hits: u64,
pub(super) misses: u64,
}
impl HskAnalysisCache {
#[allow(dead_code)]
pub fn new(max_size: usize) -> Self {
HskAnalysisCache {
entries: std::collections::HashMap::new(),
max_size,
hits: 0,
misses: 0,
}
}
#[allow(dead_code)]
pub fn get(&mut self, key: &str) -> Option<&HskCacheEntry> {
if self.entries.contains_key(key) {
self.hits += 1;
self.entries.get(key)
} else {
self.misses += 1;
None
}
}
#[allow(dead_code)]
pub fn insert(&mut self, key: String, data: Vec<u8>) {
if self.entries.len() >= self.max_size {
if let Some(oldest) = self.entries.keys().next().cloned() {
self.entries.remove(&oldest);
}
}
self.entries.insert(
key.clone(),
HskCacheEntry {
key,
data,
timestamp: 0,
valid: true,
},
);
}
#[allow(dead_code)]
pub fn invalidate(&mut self, key: &str) {
if let Some(entry) = self.entries.get_mut(key) {
entry.valid = false;
}
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.entries.clear();
}
#[allow(dead_code)]
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
return 0.0;
}
self.hits as f64 / total as f64
}
#[allow(dead_code)]
pub fn size(&self) -> usize {
self.entries.len()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HskDepGraph {
pub(super) nodes: Vec<u32>,
pub(super) edges: Vec<(u32, u32)>,
}
impl HskDepGraph {
#[allow(dead_code)]
pub fn new() -> Self {
HskDepGraph {
nodes: Vec::new(),
edges: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_node(&mut self, id: u32) {
if !self.nodes.contains(&id) {
self.nodes.push(id);
}
}
#[allow(dead_code)]
pub fn add_dep(&mut self, dep: u32, dependent: u32) {
self.add_node(dep);
self.add_node(dependent);
self.edges.push((dep, dependent));
}
#[allow(dead_code)]
pub fn dependents_of(&self, node: u32) -> Vec<u32> {
self.edges
.iter()
.filter(|(d, _)| *d == node)
.map(|(_, dep)| *dep)
.collect()
}
#[allow(dead_code)]
pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
self.edges
.iter()
.filter(|(_, dep)| *dep == node)
.map(|(d, _)| *d)
.collect()
}
#[allow(dead_code)]
pub fn topological_sort(&self) -> Vec<u32> {
let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
for &n in &self.nodes {
in_degree.insert(n, 0);
}
for (_, dep) in &self.edges {
*in_degree.entry(*dep).or_insert(0) += 1;
}
let mut queue: std::collections::VecDeque<u32> = self
.nodes
.iter()
.filter(|&&n| in_degree[&n] == 0)
.copied()
.collect();
let mut result = Vec::new();
while let Some(node) = queue.pop_front() {
result.push(node);
for dep in self.dependents_of(node) {
let cnt = in_degree.entry(dep).or_insert(0);
*cnt = cnt.saturating_sub(1);
if *cnt == 0 {
queue.push_back(dep);
}
}
}
result
}
#[allow(dead_code)]
pub fn has_cycle(&self) -> bool {
self.topological_sort().len() < self.nodes.len()
}
}
#[derive(Debug, Default)]
pub struct HsExtProfiler {
pub(super) timings: Vec<HsExtPassTiming>,
}
impl HsExtProfiler {
pub fn new() -> Self {
HsExtProfiler::default()
}
pub fn record(&mut self, t: HsExtPassTiming) {
self.timings.push(t);
}
pub fn total_elapsed_us(&self) -> u64 {
self.timings.iter().map(|t| t.elapsed_us).sum()
}
pub fn slowest_pass(&self) -> Option<&HsExtPassTiming> {
self.timings.iter().max_by_key(|t| t.elapsed_us)
}
pub fn num_passes(&self) -> usize {
self.timings.len()
}
pub fn profitable_passes(&self) -> Vec<&HsExtPassTiming> {
self.timings.iter().filter(|t| t.is_profitable()).collect()
}
}
#[derive(Debug, Clone)]
pub struct HsExtPassTiming {
pub pass_name: String,
pub elapsed_us: u64,
pub items_processed: usize,
pub bytes_before: usize,
pub bytes_after: usize,
}
impl HsExtPassTiming {
pub fn new(
pass_name: impl Into<String>,
elapsed_us: u64,
items: usize,
before: usize,
after: usize,
) -> Self {
HsExtPassTiming {
pass_name: pass_name.into(),
elapsed_us,
items_processed: items,
bytes_before: before,
bytes_after: after,
}
}
pub fn throughput_mps(&self) -> f64 {
if self.elapsed_us == 0 {
0.0
} else {
self.items_processed as f64 / (self.elapsed_us as f64 / 1_000_000.0)
}
}
pub fn size_ratio(&self) -> f64 {
if self.bytes_before == 0 {
1.0
} else {
self.bytes_after as f64 / self.bytes_before as f64
}
}
pub fn is_profitable(&self) -> bool {
self.size_ratio() <= 1.05
}
}
#[derive(Debug, Default)]
pub struct HsExtIdGen {
pub(super) next: u32,
}
impl HsExtIdGen {
pub fn new() -> Self {
HsExtIdGen::default()
}
pub fn next_id(&mut self) -> u32 {
let id = self.next;
self.next += 1;
id
}
pub fn peek_next(&self) -> u32 {
self.next
}
pub fn reset(&mut self) {
self.next = 0;
}
pub fn skip(&mut self, n: u32) {
self.next += n;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum HsExtDiagSeverity {
Note,
Warning,
Error,
}
#[derive(Debug, Clone, Default)]
pub struct HsExtConfig {
pub(super) entries: std::collections::HashMap<String, String>,
}
impl HsExtConfig {
pub fn new() -> Self {
HsExtConfig::default()
}
pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.entries.insert(key.into(), value.into());
}
pub fn get(&self, key: &str) -> Option<&str> {
self.entries.get(key).map(|s| s.as_str())
}
pub fn get_bool(&self, key: &str) -> bool {
matches!(self.get(key), Some("true") | Some("1") | Some("yes"))
}
pub fn get_int(&self, key: &str) -> Option<i64> {
self.get(key)?.parse().ok()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}