Skip to content

Commit 48c0a80

Browse files
authored
refactor: Add builder pattern and private fields to core structs (#302)
1 parent d79cd90 commit 48c0a80

32 files changed

Lines changed: 1319 additions & 768 deletions

crates/plotnik-cli/src/commands/exec.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
use std::path::PathBuf;
44

55
use plotnik_lib::Colors;
6-
use plotnik_lib::engine::{
7-
FuelLimits, Materializer, RuntimeError, VM, ValueMaterializer, debug_verify_type,
8-
};
6+
use plotnik_lib::engine::{Materializer, RuntimeError, VM, ValueMaterializer, debug_verify_type};
97

108
use super::run_common::{self, PreparedQuery, QueryInput};
119

@@ -37,7 +35,7 @@ pub fn run(args: ExecArgs) {
3735
color: args.color,
3836
});
3937

40-
let vm = VM::new(&tree, trivia_types, FuelLimits::default());
38+
let vm = VM::builder(&tree).trivia_types(trivia_types).build();
4139
let effects = match vm.execute(&module, 0, &entrypoint) {
4240
Ok(effects) => effects,
4341
Err(RuntimeError::NoMatch) => {
@@ -50,12 +48,12 @@ pub fn run(args: ExecArgs) {
5048
};
5149

5250
let materializer = ValueMaterializer::new(&source_code, module.types(), module.strings());
53-
let value = materializer.materialize(effects.as_slice(), entrypoint.result_type);
51+
let value = materializer.materialize(effects.as_slice(), entrypoint.result_type());
5452

5553
let colors = Colors::new(args.color);
5654

5755
// Debug-only: verify output matches declared type
58-
debug_verify_type(&value, entrypoint.result_type, &module, colors);
56+
debug_verify_type(&value, entrypoint.result_type(), &module, colors);
5957

6058
let output = value.format(args.pretty, colors);
6159
println!("{}", output);

crates/plotnik-cli/src/commands/infer.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::fs;
22
use std::io::{self, Write};
33
use std::path::PathBuf;
44

5-
use plotnik_lib::Colors;
65
use plotnik_lib::QueryBuilder;
76
use plotnik_lib::bytecode::Module;
87
use plotnik_lib::typegen::typescript;
@@ -106,13 +105,12 @@ pub fn run(args: InferArgs) {
106105
};
107106
// Only use colors when outputting to stdout (not to file)
108107
let use_colors = args.color && args.output.is_none();
109-
let config = typescript::Config {
110-
export: args.export,
111-
emit_node_type: !args.no_node_type,
112-
verbose_nodes: args.verbose_nodes,
113-
void_type,
114-
colors: Colors::new(use_colors),
115-
};
108+
let config = typescript::Config::new()
109+
.export(args.export)
110+
.emit_node_type(!args.no_node_type)
111+
.verbose_nodes(args.verbose_nodes)
112+
.void_type(void_type)
113+
.colored(use_colors);
116114
let output = typescript::emit_with_config(&module, config);
117115

118116
// Write output

crates/plotnik-cli/src/commands/run_common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub fn validate(
122122
pub fn build_trivia_types(module: &Module) -> Vec<u16> {
123123
let trivia_view = module.trivia();
124124
(0..trivia_view.len())
125-
.map(|i| trivia_view.get(i).node_type)
125+
.map(|i| trivia_view.get(i).node_type())
126126
.collect()
127127
}
128128

crates/plotnik-cli/src/commands/trace.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use std::path::PathBuf;
44

55
use plotnik_lib::Colors;
66
use plotnik_lib::engine::{
7-
FuelLimits, Materializer, PrintTracer, RuntimeError, VM, ValueMaterializer, Verbosity,
8-
debug_verify_type,
7+
Materializer, PrintTracer, RuntimeError, VM, ValueMaterializer, Verbosity, debug_verify_type,
98
};
109

1110
use super::run_common::{self, PreparedQuery, QueryInput};
@@ -40,13 +39,15 @@ pub fn run(args: TraceArgs) {
4039
color: args.color,
4140
});
4241

43-
let limits = FuelLimits {
44-
exec_fuel: args.fuel,
45-
..Default::default()
46-
};
47-
let vm = VM::new(&tree, trivia_types, limits);
42+
let vm = VM::builder(&tree)
43+
.trivia_types(trivia_types)
44+
.exec_fuel(args.fuel)
45+
.build();
4846
let colors = Colors::new(args.color);
49-
let mut tracer = PrintTracer::new(&source_code, &module, args.verbosity, colors);
47+
let mut tracer = PrintTracer::builder(&source_code, &module)
48+
.verbosity(args.verbosity)
49+
.colored(args.color)
50+
.build();
5051

5152
let effects = match vm.execute_with(&module, 0, &entrypoint, &mut tracer) {
5253
Ok(effects) => {
@@ -70,10 +71,10 @@ pub fn run(args: TraceArgs) {
7071

7172
println!("{}---{}", colors.dim, colors.reset);
7273
let materializer = ValueMaterializer::new(&source_code, module.types(), module.strings());
73-
let value = materializer.materialize(effects.as_slice(), entrypoint.result_type);
74+
let value = materializer.materialize(effects.as_slice(), entrypoint.result_type());
7475

7576
// Debug-only: verify output matches declared type
76-
debug_verify_type(&value, entrypoint.result_type, &module, colors);
77+
debug_verify_type(&value, entrypoint.result_type(), &module, colors);
7778

7879
let output = value.format(true, colors);
7980
println!("{}", output);

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

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use super::format::{LineBuilder, Symbol, format_effect, nav_symbol_epsilon, widt
1212
use super::ids::TypeId;
1313
use super::instructions::StepId;
1414
use super::module::{Instruction, Module};
15-
use super::type_meta::TypeKind;
15+
use super::type_meta::{TypeData, TypeKind};
1616
use super::{Call, Match, Return, Trampoline};
1717

1818
/// Generate a human-readable dump of the bytecode module.
@@ -79,20 +79,20 @@ impl DumpContext {
7979
step_labels.insert(0, "_ObjWrap".to_string());
8080
for i in 0..entrypoints.len() {
8181
let ep = entrypoints.get(i);
82-
let name = strings.get(ep.name).to_string();
83-
step_labels.insert(ep.target, name);
82+
let name = strings.get(ep.name()).to_string();
83+
step_labels.insert(ep.target(), name);
8484
}
8585

8686
let mut node_type_names = BTreeMap::new();
8787
for i in 0..node_types.len() {
8888
let t = node_types.get(i);
89-
node_type_names.insert(t.id, strings.get(t.name).to_string());
89+
node_type_names.insert(t.id(), strings.get(t.name()).to_string());
9090
}
9191

9292
let mut node_field_names = BTreeMap::new();
9393
for i in 0..node_fields.len() {
9494
let f = node_fields.get(i);
95-
node_field_names.insert(f.id, strings.get(f.name).to_string());
95+
node_field_names.insert(f.id(), strings.get(f.name()).to_string());
9696
}
9797

9898
// Collect all strings for unlinked mode lookups
@@ -182,53 +182,74 @@ fn dump_types_defs(out: &mut String, module: &Module, ctx: &DumpContext) {
182182
// All types are now in type_defs, including builtins
183183
for i in 0..types.defs_count() {
184184
let def = types.get_def(i);
185-
let kind = def.type_kind().expect("valid type kind");
186-
187-
let formatted = match kind {
188-
// Primitive types
189-
TypeKind::Void => "<Void>".to_string(),
190-
TypeKind::Node => "<Node>".to_string(),
191-
TypeKind::String => "<String>".to_string(),
192-
// Composite types
193-
TypeKind::Struct => format!("Struct M{:0mw$}:{}", def.data, def.count),
194-
TypeKind::Enum => format!("Enum M{:0mw$}:{}", def.data, def.count),
195-
// Wrapper types
196-
TypeKind::Optional => format!("Optional(T{:0tw$})", def.data),
197-
TypeKind::ArrayZeroOrMore => format!("ArrayStar(T{:0tw$})", def.data),
198-
TypeKind::ArrayOneOrMore => format!("ArrayPlus(T{:0tw$})", def.data),
199-
TypeKind::Alias => format!("Alias(T{:0tw$})", def.data),
200-
};
201185

202-
// Generate comment for non-primitives (comments are dim)
203-
let comment = match kind {
204-
TypeKind::Void | TypeKind::Node | TypeKind::String => String::new(),
205-
TypeKind::Struct => {
206-
let fields: Vec<_> = types
207-
.members_of(&def)
208-
.map(|m| strings.get(m.name).to_string())
209-
.collect();
210-
format!("{} ; {{ {} }}{}", c.dim, fields.join(", "), c.reset)
211-
}
212-
TypeKind::Enum => {
213-
let variants: Vec<_> = types
214-
.members_of(&def)
215-
.map(|m| strings.get(m.name).to_string())
216-
.collect();
217-
format!("{} ; {}{}", c.dim, variants.join(" | "), c.reset)
218-
}
219-
TypeKind::Optional => {
220-
let inner_name = format_type_name(TypeId(def.data), module, ctx);
221-
format!("{} ; {}?{}", c.dim, inner_name, c.reset)
186+
let (formatted, comment) = match def.classify() {
187+
TypeData::Primitive(kind) => {
188+
let name = match kind {
189+
TypeKind::Void => "<Void>",
190+
TypeKind::Node => "<Node>",
191+
TypeKind::String => "<String>",
192+
_ => unreachable!(),
193+
};
194+
(name.to_string(), String::new())
222195
}
223-
TypeKind::ArrayZeroOrMore => {
224-
let inner_name = format_type_name(TypeId(def.data), module, ctx);
225-
format!("{} ; {}*{}", c.dim, inner_name, c.reset)
196+
TypeData::Wrapper { kind, inner } => {
197+
let formatted = match kind {
198+
TypeKind::Optional => format!("Optional(T{:0tw$})", inner.0),
199+
TypeKind::ArrayZeroOrMore => format!("ArrayStar(T{:0tw$})", inner.0),
200+
TypeKind::ArrayOneOrMore => format!("ArrayPlus(T{:0tw$})", inner.0),
201+
TypeKind::Alias => format!("Alias(T{:0tw$})", inner.0),
202+
_ => unreachable!(),
203+
};
204+
let comment = match kind {
205+
TypeKind::Optional => {
206+
let inner_name = format_type_name(inner, module, ctx);
207+
format!("{} ; {}?{}", c.dim, inner_name, c.reset)
208+
}
209+
TypeKind::ArrayZeroOrMore => {
210+
let inner_name = format_type_name(inner, module, ctx);
211+
format!("{} ; {}*{}", c.dim, inner_name, c.reset)
212+
}
213+
TypeKind::ArrayOneOrMore => {
214+
let inner_name = format_type_name(inner, module, ctx);
215+
format!("{} ; {}+{}", c.dim, inner_name, c.reset)
216+
}
217+
TypeKind::Alias => String::new(),
218+
_ => unreachable!(),
219+
};
220+
(formatted, comment)
226221
}
227-
TypeKind::ArrayOneOrMore => {
228-
let inner_name = format_type_name(TypeId(def.data), module, ctx);
229-
format!("{} ; {}+{}", c.dim, inner_name, c.reset)
222+
TypeData::Composite {
223+
kind,
224+
member_start,
225+
member_count,
226+
} => {
227+
let formatted = match kind {
228+
TypeKind::Struct => {
229+
format!("Struct M{:0mw$}:{}", member_start, member_count)
230+
}
231+
TypeKind::Enum => format!("Enum M{:0mw$}:{}", member_start, member_count),
232+
_ => unreachable!(),
233+
};
234+
let comment = match kind {
235+
TypeKind::Struct => {
236+
let fields: Vec<_> = types
237+
.members_of(&def)
238+
.map(|m| strings.get(m.name()).to_string())
239+
.collect();
240+
format!("{} ; {{ {} }}{}", c.dim, fields.join(", "), c.reset)
241+
}
242+
TypeKind::Enum => {
243+
let variants: Vec<_> = types
244+
.members_of(&def)
245+
.map(|m| strings.get(m.name()).to_string())
246+
.collect();
247+
format!("{} ; {}{}", c.dim, variants.join(" | "), c.reset)
248+
}
249+
_ => unreachable!(),
250+
};
251+
(formatted, comment)
230252
}
231-
TypeKind::Alias => String::new(),
232253
};
233254

234255
writeln!(out, "T{i:0tw$} = {formatted}{comment}").unwrap();
@@ -288,7 +309,7 @@ fn format_type_name(type_id: TypeId, module: &Module, ctx: &DumpContext) -> Stri
288309

289310
// Check if it's a primitive type
290311
if let Some(def) = types.get(type_id)
291-
&& let Some(kind) = def.type_kind()
312+
&& let TypeData::Primitive(kind) = def.classify()
292313
&& let Some(name) = kind.primitive_name()
293314
{
294315
return format!("<{}>", name);
@@ -297,8 +318,8 @@ fn format_type_name(type_id: TypeId, module: &Module, ctx: &DumpContext) -> Stri
297318
// Try to find a name in types.names
298319
for i in 0..types.names_count() {
299320
let entry = types.get_name(i);
300-
if entry.type_id == type_id {
301-
return strings.get(entry.name).to_string();
321+
if entry.type_id() == type_id {
322+
return strings.get(entry.name()).to_string();
302323
}
303324
}
304325

@@ -320,8 +341,8 @@ fn dump_entrypoints(out: &mut String, module: &Module, ctx: &DumpContext) {
320341
let mut entries: Vec<_> = (0..entrypoints.len())
321342
.map(|i| {
322343
let ep = entrypoints.get(i);
323-
let name = strings.get(ep.name);
324-
(name, ep.target, ep.result_type.0)
344+
let name = strings.get(ep.name());
345+
(name, ep.target(), ep.result_type().0)
325346
})
326347
.collect();
327348
entries.sort_by_key(|(name, _, _)| *name);

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,16 @@ impl EffectOpcode {
4343

4444
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
4545
pub struct EffectOp {
46-
pub opcode: EffectOpcode,
47-
pub payload: usize,
46+
pub(crate) opcode: EffectOpcode,
47+
pub(crate) payload: usize,
4848
}
4949

5050
impl EffectOp {
51+
/// Create a new effect operation.
52+
pub fn new(opcode: EffectOpcode, payload: usize) -> Self {
53+
Self { opcode, payload }
54+
}
55+
5156
pub fn from_bytes(bytes: [u8; 2]) -> Self {
5257
let raw = u16::from_le_bytes(bytes);
5358
let opcode = EffectOpcode::from_u8((raw >> 10) as u8);
@@ -64,4 +69,11 @@ impl EffectOp {
6469
let raw = ((self.opcode as u16) << 10) | ((self.payload as u16) & 0x3FF);
6570
raw.to_le_bytes()
6671
}
72+
73+
pub fn opcode(&self) -> EffectOpcode {
74+
self.opcode
75+
}
76+
pub fn payload(&self) -> usize {
77+
self.payload
78+
}
6779
}

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

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,28 @@ use super::*;
22

33
#[test]
44
fn roundtrip_with_payload() {
5-
let op = EffectOp {
6-
opcode: EffectOpcode::Set,
7-
payload: 42,
8-
};
5+
let op = EffectOp::new(EffectOpcode::Set, 42);
96
let bytes = op.to_bytes();
107
let decoded = EffectOp::from_bytes(bytes);
11-
assert_eq!(decoded.opcode, EffectOpcode::Set);
12-
assert_eq!(decoded.payload, 42);
8+
assert_eq!(decoded.opcode(), EffectOpcode::Set);
9+
assert_eq!(decoded.payload(), 42);
1310
}
1411

1512
#[test]
1613
fn roundtrip_no_payload() {
17-
let op = EffectOp {
18-
opcode: EffectOpcode::Node,
19-
payload: 0,
20-
};
14+
let op = EffectOp::new(EffectOpcode::Node, 0);
2115
let bytes = op.to_bytes();
2216
let decoded = EffectOp::from_bytes(bytes);
23-
assert_eq!(decoded.opcode, EffectOpcode::Node);
24-
assert_eq!(decoded.payload, 0);
17+
assert_eq!(decoded.opcode(), EffectOpcode::Node);
18+
assert_eq!(decoded.payload(), 0);
2519
}
2620

2721
#[test]
2822
fn max_payload() {
29-
let op = EffectOp {
30-
opcode: EffectOpcode::Enum,
31-
payload: 1023,
32-
};
23+
let op = EffectOp::new(EffectOpcode::Enum, 1023);
3324
let bytes = op.to_bytes();
3425
let decoded = EffectOp::from_bytes(bytes);
35-
assert_eq!(decoded.payload, 1023);
26+
assert_eq!(decoded.payload(), 1023);
3627
}
3728

3829
#[test]

0 commit comments

Comments
 (0)