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
7 changes: 5 additions & 2 deletions lib/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ impl Default for FormatConfig {

/// Format a source file.
///
/// TODO: This is a stub. Full formatting requires AST integration
/// (parse → pretty-print). For now, it normalises whitespace only.
/// This is an intentional whitespace-only normaliser used by the
/// standalone heuristic tooling in `tools/`. The compiler-integrated
/// path goes through `ephapax-parser` + pretty-printer instead; this
/// shim exists to avoid linking the full compiler for lightweight
/// editor or MCP-cartridge use.
pub fn format_source(source: &str, config: &FormatConfig) -> String {
let mut output = String::with_capacity(source.len());
let mut prev_blank = false;
Expand Down
5 changes: 4 additions & 1 deletion lib/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ impl LintContext {

/// Lint a source file (text-based, pre-AST).
///
/// TODO: Replace with AST-based linting once ephapax-syntax crate is integrated.
/// Intentional text-based linter for the standalone tooling in
/// `tools/` and the BoJ lsp-mcp cartridge. The compiler-integrated
/// path runs the parser + typechecker directly and gets richer
/// diagnostics; this shim avoids linking the full compiler.
pub fn lint_source(file: &str, source: &str) -> Vec<Diagnostic> {
let mut ctx = LintContext::new(file);

Expand Down
187 changes: 159 additions & 28 deletions src/ephapax-lsp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! - Keyword and declaration completions

use ephapax_parser::parse_module;
use ephapax_syntax::{Decl, Expr, ExprKind, Module, Span, Ty};
use ephapax_syntax::{Decl, Expr, ExprKind, ExternItem, Module, Span, Ty};
use ephapax_typing::type_check_module;
use std::collections::HashMap;
use std::sync::Mutex;
Expand Down Expand Up @@ -53,6 +53,13 @@ struct DeclInfo {
enum DeclKind {
Function,
TypeAlias,
/// `extern "abi" { ... }` item — `Fn` or `Type` extern.
ExternFn,
ExternType,
/// `data` declaration (the parent type).
Data,
/// A constructor inside a `data` declaration.
Constructor,
}

// ============================================================================
Expand Down Expand Up @@ -209,7 +216,7 @@ impl Backend {
info.push_str(&format!("`{}`\n\n", decl.signature));

match decl.kind {
DeclKind::Function => {
DeclKind::Function | DeclKind::ExternFn => {
if !decl.params.is_empty() {
info.push_str("**Parameters:**\n");
for (name, ty) in &decl.params {
Expand All @@ -219,10 +226,22 @@ impl Backend {
if let Some(ret) = &decl.return_type {
info.push_str(&format!("\n**Returns:** `{}`\n", ret));
}
if matches!(decl.kind, DeclKind::ExternFn) {
info.push_str("\n*Foreign function (extern)*\n");
}
}
DeclKind::TypeAlias => {
info.push_str("*Type alias*\n");
}
DeclKind::ExternType => {
info.push_str("*Foreign opaque type (extern)*\n");
}
DeclKind::Data => {
info.push_str("*Data type*\n");
}
DeclKind::Constructor => {
info.push_str("*Constructor*\n");
}
}

return Some(info);
Expand Down Expand Up @@ -264,8 +283,10 @@ impl Backend {
.map(|decl| {
let range = span_to_range(&state.text, decl.span);
let kind = match decl.kind {
DeclKind::Function => SymbolKind::FUNCTION,
DeclKind::TypeAlias => SymbolKind::TYPE_PARAMETER,
DeclKind::Function | DeclKind::ExternFn => SymbolKind::FUNCTION,
DeclKind::TypeAlias | DeclKind::ExternType => SymbolKind::TYPE_PARAMETER,
DeclKind::Data => SymbolKind::ENUM,
DeclKind::Constructor => SymbolKind::ENUM_MEMBER,
};

#[allow(deprecated)] // DocumentSymbol::deprecated is itself deprecated
Expand Down Expand Up @@ -457,8 +478,13 @@ impl LanguageServer for Backend {
if let Some(state) = docs.get(uri) {
for decl in &state.declarations {
let kind = match decl.kind {
DeclKind::Function => CompletionItemKind::FUNCTION,
DeclKind::TypeAlias => CompletionItemKind::TYPE_PARAMETER,
DeclKind::Function | DeclKind::ExternFn => {
CompletionItemKind::FUNCTION
}
DeclKind::TypeAlias | DeclKind::ExternType | DeclKind::Data => {
CompletionItemKind::TYPE_PARAMETER
}
DeclKind::Constructor => CompletionItemKind::ENUM_MEMBER,
};
completions.push(CompletionItem {
label: decl.name.clone(),
Expand All @@ -478,12 +504,14 @@ impl LanguageServer for Backend {
// Helper functions
// ============================================================================

/// Extract declaration info from a parsed module.
/// Extract declaration info from a parsed module. Each top-level
/// `Decl` may yield one or more `DeclInfo` entries: extern blocks
/// expand into one entry per item; data decls expand into one entry
/// for the parent type plus one per constructor.
fn extract_declarations(module: &Module, _source: &str) -> Vec<DeclInfo> {
module
.decls
.iter()
.filter_map(|decl| match decl {
let mut out: Vec<DeclInfo> = Vec::new();
for decl in &module.decls {
match decl {
Decl::Fn {
name,
params,
Expand All @@ -508,24 +536,24 @@ fn extract_declarations(module: &Module, _source: &str) -> Vec<DeclInfo> {
format_ty(ret_ty)
);

Some(DeclInfo {
out.push(DeclInfo {
name: name.to_string(),
kind: DeclKind::Function,
span: body.span,
signature: sig,
params: param_strs,
return_type: Some(format_ty(ret_ty)),
})
});
}
Decl::Type { name, visibility: _, ty } => Some(DeclInfo {
Decl::Type { name, visibility: _, ty } => out.push(DeclInfo {
name: name.to_string(),
kind: DeclKind::TypeAlias,
span: Span::dummy(),
signature: format!("type {} = {}", name, format_ty(ty)),
params: Vec::new(),
return_type: None,
}),
Decl::Const { name, ty, value } => Some(DeclInfo {
Decl::Const { name, ty, value } => out.push(DeclInfo {
name: name.to_string(),
kind: DeclKind::TypeAlias, // closest existing variant
span: value.span,
Expand All @@ -537,19 +565,122 @@ fn extract_declarations(module: &Module, _source: &str) -> Vec<DeclInfo> {
params: Vec::new(),
return_type: ty.as_ref().map(|t| format_ty(t)),
}),
// TODO(ephapax#43 phase 2B): expose extern items as
// navigable LSP symbols. For phase 2A the LSP simply
// doesn't index extern declarations; the block parses
// and lives in the AST but isn't visible to hover /
// go-to-definition yet.
Decl::Extern { .. } => None,
// TODO(ephapax#60 follow-up): expose data decls + their
// constructors as LSP symbols (currently the surface
// pipeline materialises them only via the desugar
// registry; this arm covers the core-parser path).
Decl::Data { .. } => None,
})
.collect()
Decl::Extern { abi, items } => {
for item in items {
match item {
ExternItem::Fn {
name,
params,
ret_ty,
} => {
let param_strs: Vec<(String, String)> = params
.iter()
.map(|(n, t)| (n.to_string(), format_ty(t)))
.collect();
let sig = format!(
"extern \"{}\" fn {}({}) -> {}",
abi,
name,
param_strs
.iter()
.map(|(n, t)| format!("{}: {}", n, t))
.collect::<Vec<_>>()
.join(", "),
format_ty(ret_ty)
);
out.push(DeclInfo {
name: name.to_string(),
kind: DeclKind::ExternFn,
span: Span::dummy(),
signature: sig,
params: param_strs,
return_type: Some(format_ty(ret_ty)),
});
}
ExternItem::Type { name } => {
out.push(DeclInfo {
name: name.to_string(),
kind: DeclKind::ExternType,
span: Span::dummy(),
signature: format!("extern \"{}\" type {}", abi, name),
params: Vec::new(),
return_type: None,
});
}
}
}
}
Decl::Data {
name,
type_params,
constructors,
} => {
let header = if type_params.is_empty() {
format!("data {}", name)
} else {
let tps = type_params
.iter()
.map(|tp| tp.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("data {}({})", name, tps)
};
let ctor_summary = constructors
.iter()
.map(|c| {
if c.fields.is_empty() {
c.name.to_string()
} else {
let fs = c
.fields
.iter()
.map(format_ty)
.collect::<Vec<_>>()
.join(", ");
format!("{}({})", c.name, fs)
}
})
.collect::<Vec<_>>()
.join(" | ");
out.push(DeclInfo {
name: name.to_string(),
kind: DeclKind::Data,
span: Span::dummy(),
signature: format!("{} = {}", header, ctor_summary),
params: Vec::new(),
return_type: None,
});

for ctor in constructors {
let sig = if ctor.fields.is_empty() {
format!("{}: {}", ctor.name, name)
} else {
let fs = ctor
.fields
.iter()
.map(format_ty)
.collect::<Vec<_>>()
.join(", ");
format!("{}({}): {}", ctor.name, fs, name)
};
out.push(DeclInfo {
name: ctor.name.to_string(),
kind: DeclKind::Constructor,
span: Span::dummy(),
signature: sig,
params: ctor
.fields
.iter()
.enumerate()
.map(|(i, t)| (format!("f{}", i), format_ty(t)))
.collect(),
return_type: Some(name.to_string()),
});
}
}
}
}
out
}

/// Format a type for display.
Expand Down
14 changes: 6 additions & 8 deletions src/ephapax-typing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2336,15 +2336,13 @@ fn type_check_module_inner(
}
Decl::Type { .. } => {}
Decl::Const { .. } => {} // Constants are handled in module registration
// TODO(ephapax#43 phase 2B): typecheck extern items —
// register extern types as opaque nominal types and extern
// fns with their declared signatures. For phase 2A the
// declaration parses but does not affect the type
// environment.
// Extern fn signatures are registered earlier in the
// signature-collection pass; the body-checking pass has
// nothing more to do. Extern types are opaque.
Decl::Extern { .. } => {}
// Data semantics flow through the desugar registry —
// structured Decl::Data is preserved for tooling but the
// typechecker has nothing to do with it directly.
// Data ctor signatures flow through `DataCtorRegistry`
// (populated pre-pass); pattern-checking dispatches via the
// registry, so there is no per-decl body to walk here.
Decl::Data { .. } => {}
}
}
Expand Down
17 changes: 7 additions & 10 deletions src/ephapax-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,12 +770,9 @@ impl Codegen {
}
Decl::Type { .. } => { /* type aliases are erased at runtime */ }
Decl::Const { .. } => { /* constants inlined at compile time */ }
// TODO(ephapax#43 phase 2B): emit `(import "<abi>" "<name>"
// (func ...))` directives for `Decl::Extern { abi, items }`
// fn items and treat type items as opaque (i32) externs.
// For phase 2A the declaration is accepted by the parser
// and stored in the AST but codegen does not yet emit
// wasm imports for it.
// Extern fn imports are emitted via `collect_extern_imports`
// earlier in `compile_ast_module`; extern types are opaque
// (lowered as i32). Nothing to do here in user-fn collection.
Decl::Extern { .. } => {}
// Data types are erased at runtime — the desugar pass
// lowers them to the binary-sum encoding before codegen
Expand Down Expand Up @@ -1115,10 +1112,10 @@ impl Codegen {
}
Decl::Type { .. } => {}
Decl::Const { .. } => {} // constants inlined
// TODO(ephapax#43 phase 2B): emit no body for extern fns
// (they're resolved as `(import )` directives in the
// import section, not via the code section). Until that
// wiring lands, phase 2A just skips them here.
// Extern fns have no code-section body — they're
// imported via `(import ...)` directives emitted from
// `collect_extern_imports`, so the code-section pass
// has nothing to do here.
Decl::Extern { .. } => {}
// Data types have no code-section body; runtime
// representation comes from the desugar binary-sum
Expand Down
4 changes: 3 additions & 1 deletion tools/ephapax-dap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Error handling
anyhow = "1"

# TODO: Add ephapax compiler as dependency
# Intentionally NOT depending on the compiler crates — this DAP
# shim is the lightweight standalone counterpart to the future
# compiler-integrated debugger. Kept for reference:
# ephapax-syntax = { path = "../../src/ephapax-syntax" }
# ephapax-interp = { path = "../../src/ephapax-interp" }

Expand Down
Loading