Skip to content

Commit bc8fa74

Browse files
committed
refactor: sequential type indices in bytecode
Changes type system so builtins and custom types share a sequential pool of indices. Builtins (Void, Node, String) are emitted first if used, then custom types follow - no reserved slots for unused builtins. Key changes: - Add Void, Node, String variants to TypeKind enum - Remove QTypeId::VOID/NODE/STRING constants and is_builtin() method - Update TypeTableBuilder to emit builtins as TypeDefs when referenced - Update bytecode dump to use dynamic index width formatting - Update TypeScript emitter to use TypeKind checks instead of constants This eliminates index gaps in bytecode (e.g., T0, T1, T3 → T0, T1, T2).
1 parent 41f5649 commit bc8fa74

61 files changed

Lines changed: 1050 additions & 1069 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/plotnik-lib/src/analyze/type_check/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ pub use types::{
2020
};
2121
pub use unify::{UnifyError, unify_flow, unify_flows};
2222

23-
use std::collections::BTreeMap;
24-
2523
use indexmap::IndexMap;
2624

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

144142
fn flow_to_type_id(&mut self, flow: &TypeFlow) -> TypeId {
145143
match flow {
146-
TypeFlow::Void => self.ctx.intern_struct(BTreeMap::new()),
144+
TypeFlow::Void => TYPE_VOID,
147145
TypeFlow::Scalar(id) | TypeFlow::Bubble(id) => *id,
148146
}
149147
}

crates/plotnik-lib/src/bytecode/constants.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
//! Bytecode format constants.
22
3-
// Re-export primitive type constants from the shared type system
4-
pub use crate::type_system::{TYPE_CUSTOM_START, TYPE_NODE, TYPE_STRING, TYPE_VOID};
5-
63
/// Magic bytes identifying a Plotnik bytecode file.
74
pub const MAGIC: [u8; 4] = *b"PTKQ";
85

crates/plotnik-lib/src/bytecode/dump.rs

Lines changed: 84 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub fn dump(module: &Module) -> String {
1818
let ctx = DumpContext::new(module);
1919

2020
dump_header(&mut out, module);
21-
dump_strings(&mut out, module);
21+
dump_strings(&mut out, module, &ctx);
2222
dump_types_defs(&mut out, module, &ctx);
2323
dump_types_members(&mut out, module, &ctx);
2424
dump_types_names(&mut out, module, &ctx);
@@ -35,6 +35,15 @@ fn dump_header(out: &mut String, module: &Module) {
3535
out.push('\n');
3636
}
3737

38+
/// Calculate the minimum width needed to display numbers up to `count - 1`.
39+
fn width_for_count(count: usize) -> usize {
40+
if count <= 1 {
41+
1
42+
} else {
43+
((count - 1) as f64).log10().floor() as usize + 1
44+
}
45+
}
46+
3847
/// Context for dump formatting, precomputes lookups for O(1) access.
3948
struct DumpContext {
4049
/// Whether the bytecode is linked (contains grammar IDs vs StringIds).
@@ -47,6 +56,16 @@ struct DumpContext {
4756
node_field_names: BTreeMap<u16, String>,
4857
/// All strings (for unlinked mode lookups).
4958
all_strings: Vec<String>,
59+
/// Width for string indices (S#).
60+
str_width: usize,
61+
/// Width for type indices (T#).
62+
type_width: usize,
63+
/// Width for member indices (M#).
64+
member_width: usize,
65+
/// Width for name indices (N#).
66+
name_width: usize,
67+
/// Width for step indices.
68+
step_width: usize,
5069
}
5170

5271
impl DumpContext {
@@ -83,12 +102,26 @@ impl DumpContext {
83102
.map(|i| strings.get(StringId(i as u16)).to_string())
84103
.collect();
85104

105+
// Compute widths for index formatting
106+
let types = module.types();
107+
let type_count = 3 + types.defs_count(); // 3 builtins + custom types
108+
let str_width = width_for_count(str_count);
109+
let type_width = width_for_count(type_count);
110+
let member_width = width_for_count(types.members_count());
111+
let name_width = width_for_count(types.names_count());
112+
let step_width = width_for_count(header.transitions_count as usize);
113+
86114
Self {
87115
is_linked,
88116
step_labels,
89117
node_type_names,
90118
node_field_names,
91119
all_strings,
120+
str_width,
121+
type_width,
122+
member_width,
123+
name_width,
124+
step_width,
92125
}
93126
}
94127

@@ -123,46 +156,50 @@ impl DumpContext {
123156
}
124157
}
125158

126-
fn dump_strings(out: &mut String, module: &Module) {
159+
fn dump_strings(out: &mut String, module: &Module, ctx: &DumpContext) {
127160
let strings = module.strings();
128161
let count = module.header().str_table_count as usize;
162+
let w = ctx.str_width;
129163

130164
out.push_str("[strings]\n");
131165
for i in 0..count {
132166
let s = strings.get(StringId(i as u16));
133-
writeln!(out, "S{i:02} {s:?}").unwrap();
167+
writeln!(out, "S{i:0w$} {s:?}").unwrap();
134168
}
135169
out.push('\n');
136170
}
137171

138172
fn dump_types_defs(out: &mut String, module: &Module, ctx: &DumpContext) {
139173
let types = module.types();
140174
let strings = module.strings();
175+
let tw = ctx.type_width;
176+
let mw = ctx.member_width;
141177

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

144-
// Builtins (T00-T02)
145-
out.push_str("T00 = void\n");
146-
out.push_str("T01 = Node\n");
147-
out.push_str("T02 = str\n");
148-
149-
// Custom types (T03+)
180+
// All types are now in type_defs, including builtins
150181
for i in 0..types.defs_count() {
151182
let def = types.get_def(i);
152-
let type_id = i + 3; // Custom types start at index 3
153-
154183
let kind = def.type_kind().expect("valid type kind");
184+
155185
let formatted = match kind {
156-
TypeKind::Struct => format!("Struct M{}[{}]", def.data, def.count),
157-
TypeKind::Enum => format!("Enum M{}[{}]", def.data, def.count),
158-
TypeKind::Optional => format!("Optional(T{:02})", def.data),
159-
TypeKind::ArrayZeroOrMore => format!("ArrayStar(T{:02})", def.data),
160-
TypeKind::ArrayOneOrMore => format!("ArrayPlus(T{:02})", def.data),
161-
TypeKind::Alias => format!("Alias(T{:02})", def.data),
186+
// Primitive types
187+
TypeKind::Void => "<Void>".to_string(),
188+
TypeKind::Node => "<Node>".to_string(),
189+
TypeKind::String => "<String>".to_string(),
190+
// Composite types
191+
TypeKind::Struct => format!("Struct M{:0mw$}:{}", def.data, def.count),
192+
TypeKind::Enum => format!("Enum M{:0mw$}:{}", def.data, def.count),
193+
// Wrapper types
194+
TypeKind::Optional => format!("Optional(T{:0tw$})", def.data),
195+
TypeKind::ArrayZeroOrMore => format!("ArrayStar(T{:0tw$})", def.data),
196+
TypeKind::ArrayOneOrMore => format!("ArrayPlus(T{:0tw$})", def.data),
197+
TypeKind::Alias => format!("Alias(T{:0tw$})", def.data),
162198
};
163199

164-
// Generate comment for composites
200+
// Generate comment for non-primitives
165201
let comment = match kind {
202+
TypeKind::Void | TypeKind::Node | TypeKind::String => String::new(),
166203
TypeKind::Struct => {
167204
let fields: Vec<_> = types
168205
.members_of(&def)
@@ -192,14 +229,17 @@ fn dump_types_defs(out: &mut String, module: &Module, ctx: &DumpContext) {
192229
TypeKind::Alias => String::new(),
193230
};
194231

195-
writeln!(out, "T{type_id:02} = {formatted}{comment}").unwrap();
232+
writeln!(out, "T{i:0tw$} = {formatted}{comment}").unwrap();
196233
}
197234
out.push('\n');
198235
}
199236

200237
fn dump_types_members(out: &mut String, module: &Module, ctx: &DumpContext) {
201238
let types = module.types();
202239
let strings = module.strings();
240+
let mw = ctx.member_width;
241+
let sw = ctx.str_width;
242+
let tw = ctx.type_width;
203243

204244
out.push_str("[type_members]\n");
205245
for i in 0..types.members_count() {
@@ -208,25 +248,28 @@ fn dump_types_members(out: &mut String, module: &Module, ctx: &DumpContext) {
208248
let type_name = format_type_name(member.type_id, module, ctx);
209249
writeln!(
210250
out,
211-
"M{i}: S{:02} → T{:02} ; {name}: {type_name}",
251+
"M{i:0mw$}: S{:0sw$} → T{:0tw$} ; {name}: {type_name}",
212252
member.name.0, member.type_id.0
213253
)
214254
.unwrap();
215255
}
216256
out.push('\n');
217257
}
218258

219-
fn dump_types_names(out: &mut String, module: &Module, _ctx: &DumpContext) {
259+
fn dump_types_names(out: &mut String, module: &Module, ctx: &DumpContext) {
220260
let types = module.types();
221261
let strings = module.strings();
262+
let nw = ctx.name_width;
263+
let sw = ctx.str_width;
264+
let tw = ctx.type_width;
222265

223266
out.push_str("[type_names]\n");
224267
for i in 0..types.names_count() {
225268
let entry = types.get_name(i);
226269
let name = strings.get(entry.name);
227270
writeln!(
228271
out,
229-
"N{i}: S{:02} → T{:02} ; {name}",
272+
"N{i:0nw$}: S{:0sw$} → T{:0tw$} ; {name}",
230273
entry.name.0, entry.type_id.0
231274
)
232275
.unwrap();
@@ -235,34 +278,37 @@ fn dump_types_names(out: &mut String, module: &Module, _ctx: &DumpContext) {
235278
}
236279

237280
/// Format a type ID as a human-readable name.
238-
fn format_type_name(type_id: QTypeId, module: &Module, _ctx: &DumpContext) -> String {
239-
if type_id.is_builtin() {
240-
return match type_id.0 {
241-
0 => "void".to_string(),
242-
1 => "Node".to_string(),
243-
2 => "str".to_string(),
244-
_ => unreachable!(),
245-
};
246-
}
247-
248-
// Try to find a name in types.names
281+
fn format_type_name(type_id: QTypeId, module: &Module, ctx: &DumpContext) -> String {
249282
let types = module.types();
250283
let strings = module.strings();
251284

285+
// Check if it's a primitive type
286+
if let Some(def) = types.get(type_id) {
287+
if let Some(kind) = def.type_kind() {
288+
if let Some(name) = kind.primitive_name() {
289+
return format!("<{}>", name);
290+
}
291+
}
292+
}
293+
294+
// Try to find a name in types.names
252295
for i in 0..types.names_count() {
253296
let entry = types.get_name(i);
254297
if entry.type_id == type_id {
255298
return strings.get(entry.name).to_string();
256299
}
257300
}
258301

259-
// Fall back to T## format
260-
format!("T{:02}", type_id.0)
302+
// Fall back to T# format
303+
let tw = ctx.type_width;
304+
format!("T{:0tw$}", type_id.0)
261305
}
262306

263-
fn dump_entrypoints(out: &mut String, module: &Module, _ctx: &DumpContext) {
307+
fn dump_entrypoints(out: &mut String, module: &Module, ctx: &DumpContext) {
264308
let strings = module.strings();
265309
let entrypoints = module.entrypoints();
310+
let stw = ctx.step_width;
311+
let tw = ctx.type_width;
266312

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

@@ -282,7 +328,7 @@ fn dump_entrypoints(out: &mut String, module: &Module, _ctx: &DumpContext) {
282328
for (name, target, type_id) in entries {
283329
writeln!(
284330
out,
285-
"{name:width$} = {:02} :: T{type_id:02}",
331+
"{name:width$} = {:0stw$} :: T{type_id:0tw$}",
286332
target,
287333
width = max_len
288334
)
@@ -294,13 +340,7 @@ fn dump_entrypoints(out: &mut String, module: &Module, _ctx: &DumpContext) {
294340
fn dump_code(out: &mut String, module: &Module, ctx: &DumpContext) {
295341
let header = module.header();
296342
let transitions_count = header.transitions_count as usize;
297-
298-
// Calculate step number width based on total steps
299-
let step_width = if transitions_count == 0 {
300-
2
301-
} else {
302-
((transitions_count as f64).log10().floor() as usize + 1).max(2)
303-
};
343+
let step_width = ctx.step_width;
304344

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

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Bytecode index newtypes.
22
3-
use super::constants::{STEP_ACCEPT, STEP_SIZE, TYPE_CUSTOM_START, TYPE_STRING};
3+
use super::constants::{STEP_ACCEPT, STEP_SIZE};
44

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

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

35-
impl QTypeId {
36-
pub const VOID: Self = Self(super::constants::TYPE_VOID);
37-
pub const NODE: Self = Self(super::constants::TYPE_NODE);
38-
pub const STRING: Self = Self(TYPE_STRING);
39-
40-
#[inline]
41-
pub fn is_builtin(self) -> bool {
42-
self.0 <= TYPE_STRING
43-
}
44-
45-
/// Index into TypeDefs array (only valid for non-builtins).
46-
#[inline]
47-
pub fn custom_index(self) -> Option<usize> {
48-
if self.0 >= TYPE_CUSTOM_START {
49-
Some((self.0 - TYPE_CUSTOM_START) as usize)
50-
} else {
51-
None
52-
}
53-
}
54-
55-
#[inline]
56-
pub fn from_custom_index(idx: usize) -> Self {
57-
Self(TYPE_CUSTOM_START + idx as u16)
58-
}
59-
}
60-
6135
#[cfg(test)]
6236
mod tests {
6337
use super::*;
@@ -68,17 +42,4 @@ mod tests {
6842
assert_eq!(StepId(1).byte_offset(), 8);
6943
assert_eq!(StepId(10).byte_offset(), 80);
7044
}
71-
72-
#[test]
73-
fn bc_type_id_builtins() {
74-
assert!(QTypeId::VOID.is_builtin());
75-
assert!(QTypeId::NODE.is_builtin());
76-
assert!(QTypeId::STRING.is_builtin());
77-
assert!(!QTypeId(3).is_builtin());
78-
79-
assert_eq!(QTypeId::VOID.custom_index(), None);
80-
assert_eq!(QTypeId(3).custom_index(), Some(0));
81-
assert_eq!(QTypeId(5).custom_index(), Some(2));
82-
assert_eq!(QTypeId::from_custom_index(0), QTypeId(3));
83-
}
8445
}

crates/plotnik-lib/src/bytecode/mod.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ mod nav;
1515
mod sections;
1616
mod type_meta;
1717

18-
pub use constants::{
19-
MAGIC, SECTION_ALIGN, STEP_ACCEPT, STEP_SIZE, TYPE_CUSTOM_START, TYPE_NODE, TYPE_STRING,
20-
TYPE_VOID, VERSION,
21-
};
18+
pub use constants::{MAGIC, SECTION_ALIGN, STEP_ACCEPT, STEP_SIZE, VERSION};
2219

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

crates/plotnik-lib/src/bytecode/module.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,12 @@ impl<'a> TypesView<'a> {
430430

431431
/// Get a type definition by QTypeId.
432432
pub fn get(&self, id: QTypeId) -> Option<TypeDef> {
433-
id.custom_index().map(|idx| self.get_def(idx))
433+
let idx = id.0 as usize;
434+
if idx < self.defs_count {
435+
Some(self.get_def(idx))
436+
} else {
437+
None
438+
}
434439
}
435440

436441
/// Get a type member by index.

0 commit comments

Comments
 (0)