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
4 changes: 1 addition & 3 deletions crates/plotnik-lib/src/analyze/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ pub use types::{
};
pub use unify::{UnifyError, unify_flow, unify_flows};

use std::collections::BTreeMap;

use indexmap::IndexMap;

use crate::analyze::dependencies::DependencyAnalysis;
Expand Down Expand Up @@ -143,7 +141,7 @@ impl<'a> InferencePass<'a> {

fn flow_to_type_id(&mut self, flow: &TypeFlow) -> TypeId {
match flow {
TypeFlow::Void => self.ctx.intern_struct(BTreeMap::new()),
TypeFlow::Void => TYPE_VOID,
TypeFlow::Scalar(id) | TypeFlow::Bubble(id) => *id,
}
}
Expand Down
3 changes: 0 additions & 3 deletions crates/plotnik-lib/src/bytecode/constants.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
//! Bytecode format constants.

// Re-export primitive type constants from the shared type system
pub use crate::type_system::{TYPE_CUSTOM_START, TYPE_NODE, TYPE_STRING, TYPE_VOID};

/// Magic bytes identifying a Plotnik bytecode file.
pub const MAGIC: [u8; 4] = *b"PTKQ";

Expand Down
127 changes: 83 additions & 44 deletions crates/plotnik-lib/src/bytecode/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn dump(module: &Module) -> String {
let ctx = DumpContext::new(module);

dump_header(&mut out, module);
dump_strings(&mut out, module);
dump_strings(&mut out, module, &ctx);
dump_types_defs(&mut out, module, &ctx);
dump_types_members(&mut out, module, &ctx);
dump_types_names(&mut out, module, &ctx);
Expand All @@ -35,6 +35,15 @@ fn dump_header(out: &mut String, module: &Module) {
out.push('\n');
}

/// Calculate the minimum width needed to display numbers up to `count - 1`.
fn width_for_count(count: usize) -> usize {
if count <= 1 {
1
} else {
((count - 1) as f64).log10().floor() as usize + 1
}
}

/// Context for dump formatting, precomputes lookups for O(1) access.
struct DumpContext {
/// Whether the bytecode is linked (contains grammar IDs vs StringIds).
Expand All @@ -47,6 +56,16 @@ struct DumpContext {
node_field_names: BTreeMap<u16, String>,
/// All strings (for unlinked mode lookups).
all_strings: Vec<String>,
/// Width for string indices (S#).
str_width: usize,
/// Width for type indices (T#).
type_width: usize,
/// Width for member indices (M#).
member_width: usize,
/// Width for name indices (N#).
name_width: usize,
/// Width for step indices.
step_width: usize,
}

impl DumpContext {
Expand Down Expand Up @@ -83,12 +102,26 @@ impl DumpContext {
.map(|i| strings.get(StringId(i as u16)).to_string())
.collect();

// Compute widths for index formatting
let types = module.types();
let type_count = 3 + types.defs_count(); // 3 builtins + custom types
let str_width = width_for_count(str_count);
let type_width = width_for_count(type_count);
let member_width = width_for_count(types.members_count());
let name_width = width_for_count(types.names_count());
let step_width = width_for_count(header.transitions_count as usize);

Self {
is_linked,
step_labels,
node_type_names,
node_field_names,
all_strings,
str_width,
type_width,
member_width,
name_width,
step_width,
}
}

Expand Down Expand Up @@ -123,46 +156,50 @@ impl DumpContext {
}
}

fn dump_strings(out: &mut String, module: &Module) {
fn dump_strings(out: &mut String, module: &Module, ctx: &DumpContext) {
let strings = module.strings();
let count = module.header().str_table_count as usize;
let w = ctx.str_width;

out.push_str("[strings]\n");
for i in 0..count {
let s = strings.get(StringId(i as u16));
writeln!(out, "S{i:02} {s:?}").unwrap();
writeln!(out, "S{i:0w$} {s:?}").unwrap();
}
out.push('\n');
}

fn dump_types_defs(out: &mut String, module: &Module, ctx: &DumpContext) {
let types = module.types();
let strings = module.strings();
let tw = ctx.type_width;
let mw = ctx.member_width;

out.push_str("[type_defs]\n");

// Builtins (T00-T02)
out.push_str("T00 = void\n");
out.push_str("T01 = Node\n");
out.push_str("T02 = str\n");

// Custom types (T03+)
// All types are now in type_defs, including builtins
for i in 0..types.defs_count() {
let def = types.get_def(i);
let type_id = i + 3; // Custom types start at index 3

let kind = def.type_kind().expect("valid type kind");

let formatted = match kind {
TypeKind::Struct => format!("Struct M{}[{}]", def.data, def.count),
TypeKind::Enum => format!("Enum M{}[{}]", def.data, def.count),
TypeKind::Optional => format!("Optional(T{:02})", def.data),
TypeKind::ArrayZeroOrMore => format!("ArrayStar(T{:02})", def.data),
TypeKind::ArrayOneOrMore => format!("ArrayPlus(T{:02})", def.data),
TypeKind::Alias => format!("Alias(T{:02})", def.data),
// Primitive types
TypeKind::Void => "<Void>".to_string(),
TypeKind::Node => "<Node>".to_string(),
TypeKind::String => "<String>".to_string(),
// Composite types
TypeKind::Struct => format!("Struct M{:0mw$}:{}", def.data, def.count),
TypeKind::Enum => format!("Enum M{:0mw$}:{}", def.data, def.count),
// Wrapper types
TypeKind::Optional => format!("Optional(T{:0tw$})", def.data),
TypeKind::ArrayZeroOrMore => format!("ArrayStar(T{:0tw$})", def.data),
TypeKind::ArrayOneOrMore => format!("ArrayPlus(T{:0tw$})", def.data),
TypeKind::Alias => format!("Alias(T{:0tw$})", def.data),
};

// Generate comment for composites
// Generate comment for non-primitives
let comment = match kind {
TypeKind::Void | TypeKind::Node | TypeKind::String => String::new(),
TypeKind::Struct => {
let fields: Vec<_> = types
.members_of(&def)
Expand Down Expand Up @@ -192,14 +229,17 @@ fn dump_types_defs(out: &mut String, module: &Module, ctx: &DumpContext) {
TypeKind::Alias => String::new(),
};

writeln!(out, "T{type_id:02} = {formatted}{comment}").unwrap();
writeln!(out, "T{i:0tw$} = {formatted}{comment}").unwrap();
}
out.push('\n');
}

fn dump_types_members(out: &mut String, module: &Module, ctx: &DumpContext) {
let types = module.types();
let strings = module.strings();
let mw = ctx.member_width;
let sw = ctx.str_width;
let tw = ctx.type_width;

out.push_str("[type_members]\n");
for i in 0..types.members_count() {
Expand All @@ -208,25 +248,28 @@ fn dump_types_members(out: &mut String, module: &Module, ctx: &DumpContext) {
let type_name = format_type_name(member.type_id, module, ctx);
writeln!(
out,
"M{i}: S{:02} → T{:02} ; {name}: {type_name}",
"M{i:0mw$}: S{:0sw$} → T{:0tw$} ; {name}: {type_name}",
member.name.0, member.type_id.0
)
.unwrap();
}
out.push('\n');
}

fn dump_types_names(out: &mut String, module: &Module, _ctx: &DumpContext) {
fn dump_types_names(out: &mut String, module: &Module, ctx: &DumpContext) {
let types = module.types();
let strings = module.strings();
let nw = ctx.name_width;
let sw = ctx.str_width;
let tw = ctx.type_width;

out.push_str("[type_names]\n");
for i in 0..types.names_count() {
let entry = types.get_name(i);
let name = strings.get(entry.name);
writeln!(
out,
"N{i}: S{:02} → T{:02} ; {name}",
"N{i:0nw$}: S{:0sw$} → T{:0tw$} ; {name}",
entry.name.0, entry.type_id.0
)
.unwrap();
Expand All @@ -235,34 +278,36 @@ fn dump_types_names(out: &mut String, module: &Module, _ctx: &DumpContext) {
}

/// Format a type ID as a human-readable name.
fn format_type_name(type_id: QTypeId, module: &Module, _ctx: &DumpContext) -> String {
if type_id.is_builtin() {
return match type_id.0 {
0 => "void".to_string(),
1 => "Node".to_string(),
2 => "str".to_string(),
_ => unreachable!(),
};
}

// Try to find a name in types.names
fn format_type_name(type_id: QTypeId, module: &Module, ctx: &DumpContext) -> String {
let types = module.types();
let strings = module.strings();

// Check if it's a primitive type
if let Some(def) = types.get(type_id)
&& let Some(kind) = def.type_kind()
&& let Some(name) = kind.primitive_name()
{
return format!("<{}>", name);
}

// Try to find a name in types.names
for i in 0..types.names_count() {
let entry = types.get_name(i);
if entry.type_id == type_id {
return strings.get(entry.name).to_string();
}
}

// Fall back to T## format
format!("T{:02}", type_id.0)
// Fall back to T# format
let tw = ctx.type_width;
format!("T{:0tw$}", type_id.0)
}

fn dump_entrypoints(out: &mut String, module: &Module, _ctx: &DumpContext) {
fn dump_entrypoints(out: &mut String, module: &Module, ctx: &DumpContext) {
let strings = module.strings();
let entrypoints = module.entrypoints();
let stw = ctx.step_width;
let tw = ctx.type_width;

out.push_str("[entrypoints]\n");

Expand All @@ -282,7 +327,7 @@ fn dump_entrypoints(out: &mut String, module: &Module, _ctx: &DumpContext) {
for (name, target, type_id) in entries {
writeln!(
out,
"{name:width$} = {:02} :: T{type_id:02}",
"{name:width$} = {:0stw$} :: T{type_id:0tw$}",
target,
width = max_len
)
Expand All @@ -294,13 +339,7 @@ fn dump_entrypoints(out: &mut String, module: &Module, _ctx: &DumpContext) {
fn dump_code(out: &mut String, module: &Module, ctx: &DumpContext) {
let header = module.header();
let transitions_count = header.transitions_count as usize;

// Calculate step number width based on total steps
let step_width = if transitions_count == 0 {
2
} else {
((transitions_count as f64).log10().floor() as usize + 1).max(2)
};
let step_width = ctx.step_width;

out.push_str("[transitions]\n");

Expand Down
43 changes: 2 additions & 41 deletions crates/plotnik-lib/src/bytecode/ids.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Bytecode index newtypes.

use super::constants::{STEP_ACCEPT, STEP_SIZE, TYPE_CUSTOM_START, TYPE_STRING};
use super::constants::{STEP_ACCEPT, STEP_SIZE};

/// Index into the Transitions section (8-byte steps).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
Expand All @@ -27,37 +27,11 @@ impl StepId {
pub struct StringId(pub u16);

/// Index into the Type Definition table.
/// Values 0-2 are builtins; 3+ index into TypeDefs.
/// All types (including builtins) are stored sequentially in TypeDefs.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[repr(transparent)]
pub struct QTypeId(pub u16);

impl QTypeId {
pub const VOID: Self = Self(super::constants::TYPE_VOID);
pub const NODE: Self = Self(super::constants::TYPE_NODE);
pub const STRING: Self = Self(TYPE_STRING);

#[inline]
pub fn is_builtin(self) -> bool {
self.0 <= TYPE_STRING
}

/// Index into TypeDefs array (only valid for non-builtins).
#[inline]
pub fn custom_index(self) -> Option<usize> {
if self.0 >= TYPE_CUSTOM_START {
Some((self.0 - TYPE_CUSTOM_START) as usize)
} else {
None
}
}

#[inline]
pub fn from_custom_index(idx: usize) -> Self {
Self(TYPE_CUSTOM_START + idx as u16)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -68,17 +42,4 @@ mod tests {
assert_eq!(StepId(1).byte_offset(), 8);
assert_eq!(StepId(10).byte_offset(), 80);
}

#[test]
fn bc_type_id_builtins() {
assert!(QTypeId::VOID.is_builtin());
assert!(QTypeId::NODE.is_builtin());
assert!(QTypeId::STRING.is_builtin());
assert!(!QTypeId(3).is_builtin());

assert_eq!(QTypeId::VOID.custom_index(), None);
assert_eq!(QTypeId(3).custom_index(), Some(0));
assert_eq!(QTypeId(5).custom_index(), Some(2));
assert_eq!(QTypeId::from_custom_index(0), QTypeId(3));
}
}
5 changes: 1 addition & 4 deletions crates/plotnik-lib/src/bytecode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ mod nav;
mod sections;
mod type_meta;

pub use constants::{
MAGIC, SECTION_ALIGN, STEP_ACCEPT, STEP_SIZE, TYPE_CUSTOM_START, TYPE_NODE, TYPE_STRING,
TYPE_VOID, VERSION,
};
pub use constants::{MAGIC, SECTION_ALIGN, STEP_ACCEPT, STEP_SIZE, VERSION};

pub use ids::{QTypeId, StepId, StringId};

Expand Down
7 changes: 6 additions & 1 deletion crates/plotnik-lib/src/bytecode/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,12 @@ impl<'a> TypesView<'a> {

/// Get a type definition by QTypeId.
pub fn get(&self, id: QTypeId) -> Option<TypeDef> {
id.custom_index().map(|idx| self.get_def(idx))
let idx = id.0 as usize;
if idx < self.defs_count {
Some(self.get_def(idx))
} else {
None
}
}

/// Get a type member by index.
Expand Down
Loading