Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::semantic::resolve::Resolution;
use crate::semantic::symbols::SymbolId;
use crate::semantic::types::Type;
use crate::semantic::SemanticProgram;
use crate::span::{FileId, Span};
use crate::span::{FileId, SourceMap, Span};
use crate::syntax::parse_source;
use crate::syntax::token::Token;
use std::path::Path;
Expand Down Expand Up @@ -62,6 +62,15 @@ pub fn validate_hir(hir: &HirProgram) -> Vec<Diagnostic> {
crate::hir::validate::validate(hir)
}

/// Lower validated HIR into canonical Workshop WIR while preserving source
/// provenance through the supplied project source registry.
pub fn lower_to_wir(
hir: &HirProgram,
sources: &SourceMap,
) -> (workshop_rs::wir::Program, Vec<Diagnostic>) {
crate::workshop::lower_to_wir(hir, sources)
}

// ---- queries ----

/// The symbol bound at `offset` in `file` (via the resolution table).
Expand Down
1 change: 1 addition & 0 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub const DIAGNOSTIC_CODES: &[(&str, &str)] = &[
("HI015", "auto-for variable storage conflict"),
("HI016", "field initializer type mismatch"),
("HI017", "interpolation/async/hook shape violation"),
("HI018", "HIR construct cannot be lowered to canonical Workshop WIR"),
("HI099", "HIR has validation errors; oracle refuses to execute"),
// Oracle
("OR001", "stale reference: use of a deleted object"),
Expand Down
78 changes: 66 additions & 12 deletions src/hir/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub fn lower(program: &SemanticProgram) -> (HirProgram, Vec<crate::diagnostics::
classes: Vec::new(),
enums: Vec::new(),
vars: Vec::new(),
reservations: Vec::new(),
rules: Vec::new(),
exprs: Vec::new(),
top: Vec::new(),
Expand Down Expand Up @@ -121,19 +122,41 @@ impl<'a> Lowerer<'a> {
ValueSemantics::Value
};
let vid = self.hir.vars.len() as HirVarId;
let explicit_id = self
.program
.var_symbol_of(v.name.id)
.and_then(|sid| self.program.tables.symbol(sid).flags.var_id)
.and_then(|id| u32::try_from(id).ok());
self.hir.vars.push(HirVar {
name: v.name.name.clone(),
ty,
storage,
semantics,
is_const: v.is_const_init,
explicit_id,
span: v.name.span,
});
if let Some(sid) = self.program.var_symbol_of(v.name.id) {
self.symbol_var.insert(sid, vid);
}
self.local_vars.insert(v.name.id, vid);
}
ItemKind::VarReservation(reservation) => {
let storage = match reservation.storage {
StorageModifier::GlobalVar => StorageIntent::Global,
StorageModifier::PlayerVar => StorageIntent::Player,
};
self.hir.reservations.push(HirReservation {
storage,
names: reservation
.names
.iter()
.map(NameText::name_text)
.filter(|name| !name.is_empty())
.collect(),
span: item.span,
});
}
ItemKind::Function(f) => {
let sid = self.program.function_symbol_of(f.name.id);
let is_macro = matches!(f.body, FuncBody::Expr(_));
Expand Down Expand Up @@ -338,6 +361,7 @@ impl<'a> Lowerer<'a> {
let body = self.lower_stmt_block(&r.body);
self.hir.rules.push(HirRule {
name: Some(r.name.name_text()),
name_span: string_content_span(&r.name),
disabled: r.disabled,
sort_order: r.sort_order.as_ref().and_then(number_i64),
event,
Expand All @@ -350,6 +374,7 @@ impl<'a> Lowerer<'a> {
ItemKind::VanillaRule(v) => {
self.hir.rules.push(HirRule {
name: v.name.as_ref().map(|e| e.name_text()),
name_span: v.name.as_ref().and_then(string_content_span),
disabled: false,
sort_order: None,
event: None,
Expand Down Expand Up @@ -426,6 +451,7 @@ impl<'a> Lowerer<'a> {
storage: StorageIntent::Local,
semantics: ValueSemantics::Value,
is_const: false,
explicit_id: None,
span,
});
vid
Expand Down Expand Up @@ -683,13 +709,21 @@ impl<'a> Lowerer<'a> {
HirExprKind::External {
name: self.ident_name(e),
namespace: Vec::new(),
binding: None,
}
}
ExprKind::Member { base, name } => HirExprKind::Member {
base: self.expr(base),
member: self.lower_member_target(name),
},
ExprKind::Member { base, name } => {
if matches!(self.program.resolution.get(&e.id), Some(Resolution::External(_))) {
HirExprKind::External {
name: name.name.clone(),
namespace: self.member_namespace(base),
}
} else {
HirExprKind::Member {
base: self.expr(base),
member: self.lower_member_target(name),
}
}
}
ExprKind::Index { base, index } => HirExprKind::Index {
base: self.expr(base),
index: self.expr(index),
Expand Down Expand Up @@ -757,7 +791,6 @@ impl<'a> Lowerer<'a> {
HirExprKind::External {
name: String::new(),
namespace: Vec::new(),
binding: None,
}
}
ExprKind::This => HirExprKind::This { class: 0 },
Expand Down Expand Up @@ -895,7 +928,7 @@ impl<'a> Lowerer<'a> {
target: CallTarget::External {
name: id.name.clone(),
namespace: Vec::new(),
binding: None,
span: call.callee.span,
},
args,
}
Expand All @@ -904,7 +937,7 @@ impl<'a> Lowerer<'a> {
target: CallTarget::External {
name: id.name.clone(),
namespace: Vec::new(),
binding: None,
span: call.callee.span,
},
args,
},
Expand Down Expand Up @@ -972,7 +1005,7 @@ impl<'a> Lowerer<'a> {
target: CallTarget::External {
name: name.name.clone(),
namespace: self.member_namespace(base),
binding: None,
span: call.callee.span,
},
args,
},
Expand Down Expand Up @@ -1112,9 +1145,8 @@ impl NameText for Expr {
fn name_text(&self) -> String {
match &self.kind {
ExprKind::Str(s) => {
let len = s.raw.len();
if len >= 2 {
s.raw[1..len - 1].to_string()
if let Some((start, end)) = string_content_bounds(s) {
s.raw[start..end].to_string()
} else {
s.raw.clone()
}
Expand All @@ -1123,3 +1155,25 @@ impl NameText for Expr {
}
}
}

fn string_content_span(expr: &Expr) -> Option<Span> {
let ExprKind::Str(string) = &expr.kind else {
return None;
};
let (start, end) = string_content_bounds(string)?;
Some(Span::new(
expr.span.file,
expr.span.start + start as u32,
expr.span.start + end as u32,
))
}

fn string_content_bounds(string: &crate::syntax::ast::StrLit) -> Option<(usize, usize)> {
let prefix = usize::from(matches!(
string.quote,
crate::syntax::ast::QuoteKind::Localized | crate::syntax::ast::QuoteKind::Interpolated
));
let start = prefix + 1;
let end = string.raw.len().checked_sub(1)?;
(start <= end).then_some((start, end))
}
17 changes: 14 additions & 3 deletions src/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
//! intent — without any Workshop encoding (no slots, no helper rules, no
//! reference bit layouts).

use crate::semantic::provider::ExternalBinding;
use crate::semantic::types::Type;
use crate::span::Span;
use std::collections::HashMap;
Expand All @@ -27,6 +26,7 @@ pub struct HirProgram {
pub classes: Vec<HirClass>,
pub enums: Vec<HirEnum>,
pub vars: Vec<HirVar>,
pub reservations: Vec<HirReservation>,
pub rules: Vec<HirRule>,
/// Expression registry: id -> node (HirExprId - 1 indexes this).
pub exprs: Vec<HirExpr>,
Expand Down Expand Up @@ -146,12 +146,22 @@ pub struct HirVar {
pub storage: StorageIntent,
pub semantics: ValueSemantics,
pub is_const: bool,
pub explicit_id: Option<u32>,
pub span: Span,
}

#[derive(Clone, Debug)]
pub struct HirReservation {
pub storage: StorageIntent,
pub names: Vec<String>,
pub span: Span,
}

#[derive(Clone, Debug)]
pub struct HirRule {
pub name: Option<String>,
/// The exact source span of the rule name inside its string literal.
pub name_span: Option<Span>,
pub disabled: bool,
pub sort_order: Option<i64>,
pub event: Option<HirExprId>,
Expand Down Expand Up @@ -258,7 +268,6 @@ pub enum HirExprKind {
External {
name: String,
namespace: Vec<String>,
binding: Option<ExternalBinding>,
},
Error,
}
Expand Down Expand Up @@ -297,7 +306,9 @@ pub enum CallTarget {
External {
name: String,
namespace: Vec<String>,
binding: Option<ExternalBinding>,
/// The source span of the external callee. Provider bindings are
/// resolved later by the DEL-owned lowering context.
span: Span,
},
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod project;
pub mod semantic;
pub mod hir;
pub mod api;
pub mod workshop;
pub mod workshop_source;

pub use diagnostics::{Diagnostic, Phase, RelatedSpan, Severity};
Expand Down
19 changes: 15 additions & 4 deletions src/semantic/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ impl CatalogProvider {
let canonical = match name {
"OngoingGlobal" => "global",
"OngoingPlayer" => "eachPlayer",
"OnElimination" => "playerEarnedElimination",
"OnFinalBlow" => "playerDealtFinalBlow",
"OnDamageDealt" => "playerDealtDamage",
"OnDamageTaken" => "playerTookDamage",
"OnDeath" => "playerDied",
"OnHealingDealt" => "playerDealtHealing",
"OnHealingTaken" => "playerReceivedHealing",
"OnPlayerJoin" => "playerJoined",
"OnPlayerLeave" => "playerLeft",
"Subroutine" => "subroutine",
_ => name,
};
Expand Down Expand Up @@ -240,10 +249,12 @@ impl WorkshopProvider for CatalogProvider {
let Some((entry, canonical_id)) = self.resolve_event(&query.name) else {
return ExternalResolution::NotFound;
};
let context = match canonical_id.as_str() {
"global" => Some(EventContext::Global),
"eachPlayer" => Some(EventContext::Player),
_ => None,
let context = if canonical_id == "global" {
Some(EventContext::Global)
} else if canonical_id == "eachPlayer" || canonical_id.starts_with("player") {
Some(EventContext::Player)
} else {
None
};
return ExternalResolution::Known(ExternalBinding::Event(ExternalEventInfo {
canonical_id: entry.id.clone(),
Expand Down
Loading