1use harn_parser::{Node, SNode, TypeExpr};
2
3mod closures;
4mod concurrency;
5mod decls;
6mod error;
7mod error_handling;
8mod expressions;
9mod hitl;
10mod optimizer;
11mod patterns;
12mod pipe;
13mod state;
14mod statements;
15#[cfg(test)]
16mod tests;
17mod type_facts;
18mod yield_scan;
19
20pub use error::CompileError;
21
22use crate::chunk::{Chunk, Constant, Op};
23
24pub const HARN_DISABLE_OPTIMIZATIONS_ENV: &str = "HARN_DISABLE_OPTIMIZATIONS";
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct CompilerOptions {
34 optimize: bool,
35}
36
37impl CompilerOptions {
38 pub fn optimized() -> Self {
39 Self { optimize: true }
40 }
41
42 pub fn without_optimizations() -> Self {
43 Self { optimize: false }
44 }
45
46 pub fn from_env() -> Self {
47 if std::env::var_os(HARN_DISABLE_OPTIMIZATIONS_ENV).is_some() {
48 Self::without_optimizations()
49 } else {
50 Self::optimized()
51 }
52 }
53
54 pub fn optimizations_enabled(self) -> bool {
55 self.optimize
56 }
57}
58
59impl Default for CompilerOptions {
60 fn default() -> Self {
61 Self::optimized()
62 }
63}
64
65fn peel_node(sn: &SNode) -> &Node {
69 match &sn.node {
70 Node::AttributedDecl { inner, .. } => &inner.node,
71 other => other,
72 }
73}
74
75#[derive(Clone, Debug)]
78enum FinallyEntry {
79 Finally(Vec<SNode>),
80 CatchBarrier,
81}
82
83struct LoopContext {
85 start_offset: usize,
87 break_patches: Vec<usize>,
89 has_iterator: bool,
91 handler_depth: usize,
93 finally_depth: usize,
95 scope_depth: usize,
97}
98
99#[derive(Clone, Copy, Debug)]
100struct LocalBinding {
101 slot: u16,
102 mutable: bool,
103}
104
105pub struct Compiler {
107 options: CompilerOptions,
108 chunk: Chunk,
109 line: u32,
110 column: u32,
111 enum_names: std::collections::HashSet<String>,
113 struct_layouts: std::collections::HashMap<String, Vec<String>>,
115 interface_methods: std::collections::HashMap<String, Vec<String>>,
117 loop_stack: Vec<LoopContext>,
119 handler_depth: usize,
121 finally_bodies: Vec<FinallyEntry>,
133 temp_counter: usize,
135 scope_depth: usize,
137 type_aliases: std::collections::HashMap<String, TypeExpr>,
140 type_scopes: Vec<std::collections::HashMap<String, TypeExpr>>,
145 local_scopes: Vec<std::collections::HashMap<String, LocalBinding>>,
149 module_level: bool,
155}
156
157impl Compiler {
158 fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
162 self.line = snode.span.line as u32;
163 self.column = snode.span.column as u32;
164 self.chunk.set_column(self.column);
165 if self.options.optimizations_enabled() {
166 if let Some(folded) = optimizer::fold_constant_expr(snode) {
167 if folded.node != snode.node {
168 return self.compile_node(&folded);
169 }
170 }
171 }
172 match &snode.node {
173 Node::IntLiteral(n) => {
174 let idx = self.chunk.add_constant(Constant::Int(*n));
175 self.chunk.emit_u16(Op::Constant, idx, self.line);
176 }
177 Node::FloatLiteral(n) => {
178 let idx = self.chunk.add_constant(Constant::Float(*n));
179 self.chunk.emit_u16(Op::Constant, idx, self.line);
180 }
181 Node::StringLiteral(s) | Node::RawStringLiteral(s) => {
182 let idx = self.chunk.add_constant(Constant::String(s.clone()));
183 self.chunk.emit_u16(Op::Constant, idx, self.line);
184 }
185 Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
186 Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
187 Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
188 Node::DurationLiteral(ms) => {
189 let ms = i64::try_from(*ms).map_err(|_| CompileError {
190 message: "duration literal is too large".to_string(),
191 line: self.line,
192 })?;
193 let idx = self.chunk.add_constant(Constant::Duration(ms));
194 self.chunk.emit_u16(Op::Constant, idx, self.line);
195 }
196 Node::Identifier(name) => {
197 self.emit_get_binding(name);
198 }
199 Node::LetBinding { pattern, value, .. } => {
200 let binding_type = match &snode.node {
201 Node::LetBinding {
202 type_ann: Some(type_ann),
203 ..
204 } => Some(type_ann.clone()),
205 _ => self.infer_expr_type(value),
206 };
207 self.compile_node(value)?;
208 self.compile_destructuring(pattern, false)?;
209 self.record_binding_type(pattern, binding_type);
210 }
211 Node::VarBinding { pattern, value, .. } => {
212 let binding_type = match &snode.node {
213 Node::VarBinding {
214 type_ann: Some(type_ann),
215 ..
216 } => Some(type_ann.clone()),
217 _ => self.infer_expr_type(value),
218 };
219 self.compile_node(value)?;
220 self.compile_destructuring(pattern, true)?;
221 self.record_binding_type(pattern, binding_type);
222 }
223 Node::Assignment {
224 target, value, op, ..
225 } => {
226 self.compile_assignment(target, value, op)?;
227 }
228 Node::BinaryOp { op, left, right } => {
229 self.compile_binary_op(op, left, right)?;
230 }
231 Node::UnaryOp { op, operand } => {
232 self.compile_node(operand)?;
233 match op.as_str() {
234 "-" => self.chunk.emit(Op::Negate, self.line),
235 "!" => self.chunk.emit(Op::Not, self.line),
236 _ => {}
237 }
238 }
239 Node::Ternary {
240 condition,
241 true_expr,
242 false_expr,
243 } => {
244 self.compile_node(condition)?;
245 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
246 self.chunk.emit(Op::Pop, self.line);
247 self.compile_node(true_expr)?;
248 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
249 self.chunk.patch_jump(else_jump);
250 self.chunk.emit(Op::Pop, self.line);
251 self.compile_node(false_expr)?;
252 self.chunk.patch_jump(end_jump);
253 }
254 Node::FunctionCall { name, args, .. } => {
255 self.compile_function_call(name, args)?;
256 }
257 Node::MethodCall {
258 object,
259 method,
260 args,
261 } => {
262 self.compile_method_call(object, method, args)?;
263 }
264 Node::OptionalMethodCall {
265 object,
266 method,
267 args,
268 } => {
269 self.compile_node(object)?;
270 for arg in args {
271 self.compile_node(arg)?;
272 }
273 let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
274 self.chunk
275 .emit_method_call_opt(name_idx, args.len() as u8, self.line);
276 }
277 Node::PropertyAccess { object, property } => {
278 self.compile_property_access(object, property)?;
279 }
280 Node::OptionalPropertyAccess { object, property } => {
281 self.compile_node(object)?;
282 let idx = self.chunk.add_constant(Constant::String(property.clone()));
283 self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
284 }
285 Node::SubscriptAccess { object, index } => {
286 self.compile_node(object)?;
287 self.compile_node(index)?;
288 self.chunk.emit(Op::Subscript, self.line);
289 }
290 Node::OptionalSubscriptAccess { object, index } => {
291 self.compile_node(object)?;
292 self.compile_node(index)?;
293 self.chunk.emit(Op::SubscriptOpt, self.line);
294 }
295 Node::SliceAccess { object, start, end } => {
296 self.compile_node(object)?;
297 if let Some(s) = start {
298 self.compile_node(s)?;
299 } else {
300 self.chunk.emit(Op::Nil, self.line);
301 }
302 if let Some(e) = end {
303 self.compile_node(e)?;
304 } else {
305 self.chunk.emit(Op::Nil, self.line);
306 }
307 self.chunk.emit(Op::Slice, self.line);
308 }
309 Node::IfElse {
310 condition,
311 then_body,
312 else_body,
313 } => {
314 self.compile_if_else(condition, then_body, else_body)?;
315 }
316 Node::WhileLoop { condition, body } => {
317 self.compile_while_loop(condition, body)?;
318 }
319 Node::ForIn {
320 pattern,
321 iterable,
322 body,
323 } => {
324 self.compile_for_in(pattern, iterable, body)?;
325 }
326 Node::ReturnStmt { value } => {
327 self.compile_return_stmt(value)?;
328 }
329 Node::BreakStmt => {
330 self.compile_break_stmt()?;
331 }
332 Node::ContinueStmt => {
333 self.compile_continue_stmt()?;
334 }
335 Node::ListLiteral(elements) => {
336 self.compile_list_literal(elements)?;
337 }
338 Node::DictLiteral(entries) => {
339 self.compile_dict_literal(entries)?;
340 }
341 Node::InterpolatedString(segments) => {
342 self.compile_interpolated_string(segments)?;
343 }
344 Node::FnDecl {
345 name,
346 type_params,
347 params,
348 body,
349 is_stream,
350 ..
351 } => {
352 self.compile_fn_decl(name, type_params, params, body, *is_stream)?;
353 }
354 Node::ToolDecl {
355 name,
356 description,
357 params,
358 return_type,
359 body,
360 ..
361 } => {
362 self.compile_tool_decl(name, description, params, return_type, body)?;
363 }
364 Node::SkillDecl { name, fields, .. } => {
365 self.compile_skill_decl(name, fields)?;
366 }
367 Node::EvalPackDecl {
368 binding_name,
369 pack_id,
370 fields,
371 body,
372 summarize,
373 ..
374 } => {
375 self.compile_eval_pack_decl(binding_name, pack_id, fields, body, summarize, true)?;
376 }
377 Node::Closure { params, body, .. } => {
378 self.compile_closure(params, body)?;
379 }
380 Node::ThrowStmt { value } => {
381 self.compile_throw_stmt(value)?;
382 }
383 Node::MatchExpr { value, arms } => {
384 self.compile_match_expr(value, arms)?;
385 }
386 Node::RangeExpr {
387 start,
388 end,
389 inclusive,
390 } => {
391 let name_idx = self
392 .chunk
393 .add_constant(Constant::String("__range__".to_string()));
394 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
395 self.compile_node(start)?;
396 self.compile_node(end)?;
397 if *inclusive {
398 self.chunk.emit(Op::True, self.line);
399 } else {
400 self.chunk.emit(Op::False, self.line);
401 }
402 self.chunk.emit_u8(Op::Call, 3, self.line);
403 }
404 Node::GuardStmt {
405 condition,
406 else_body,
407 } => {
408 self.compile_guard_stmt(condition, else_body)?;
409 }
410 Node::RequireStmt { condition, message } => {
411 self.compile_node(condition)?;
412 let ok_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
413 self.chunk.emit(Op::Pop, self.line);
414 if let Some(message) = message {
415 self.compile_node(message)?;
416 } else {
417 let idx = self
418 .chunk
419 .add_constant(Constant::String("require condition failed".to_string()));
420 self.chunk.emit_u16(Op::Constant, idx, self.line);
421 }
422 self.chunk.emit(Op::Throw, self.line);
423 self.chunk.patch_jump(ok_jump);
424 self.chunk.emit(Op::Pop, self.line);
425 }
426 Node::Block(stmts) => {
427 self.compile_scoped_block(stmts)?;
428 }
429 Node::DeadlineBlock { duration, body } => {
430 self.compile_node(duration)?;
431 self.chunk.emit(Op::DeadlineSetup, self.line);
432 self.compile_scoped_block(body)?;
433 self.chunk.emit(Op::DeadlineEnd, self.line);
434 }
435 Node::MutexBlock { body } => {
436 self.begin_scope();
437 let key_idx = self
438 .chunk
439 .add_constant(Constant::String("__default__".to_string()));
440 self.chunk.emit_u16(Op::SyncMutexEnter, key_idx, self.line);
441 for sn in body {
442 self.compile_node(sn)?;
443 if Self::produces_value(&sn.node) {
444 self.chunk.emit(Op::Pop, self.line);
445 }
446 }
447 self.chunk.emit(Op::Nil, self.line);
448 self.end_scope();
449 }
450 Node::DeferStmt { body } => {
451 self.finally_bodies
453 .push(FinallyEntry::Finally(body.clone()));
454 self.chunk.emit(Op::Nil, self.line);
455 }
456 Node::YieldExpr { value } => {
457 if let Some(val) = value {
458 self.compile_node(val)?;
459 } else {
460 self.chunk.emit(Op::Nil, self.line);
461 }
462 self.chunk.emit(Op::Yield, self.line);
463 }
464 Node::EmitExpr { value } => {
465 self.compile_node(value)?;
466 self.chunk.emit(Op::Yield, self.line);
467 }
468 Node::EnumConstruct {
469 enum_name,
470 variant,
471 args,
472 } => {
473 self.compile_enum_construct(enum_name, variant, args)?;
474 }
475 Node::StructConstruct {
476 struct_name,
477 fields,
478 } => {
479 self.compile_struct_construct(struct_name, fields)?;
480 }
481 Node::ImportDecl { path, .. } => {
482 let idx = self.chunk.add_constant(Constant::String(path.clone()));
483 self.chunk.emit_u16(Op::Import, idx, self.line);
484 }
485 Node::SelectiveImport { names, path, .. } => {
486 let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
487 let names_str = names.join(",");
488 let names_idx = self.chunk.add_constant(Constant::String(names_str));
489 self.chunk
490 .emit_u16(Op::SelectiveImport, path_idx, self.line);
491 let hi = (names_idx >> 8) as u8;
492 let lo = names_idx as u8;
493 self.chunk.code.push(hi);
494 self.chunk.code.push(lo);
495 self.chunk.lines.push(self.line);
496 self.chunk.columns.push(self.column);
497 self.chunk.lines.push(self.line);
498 self.chunk.columns.push(self.column);
499 }
500 Node::TryOperator { operand } => {
501 self.compile_node(operand)?;
502 self.chunk.emit(Op::TryUnwrap, self.line);
503 }
504 Node::TryStar { operand } => {
520 self.compile_try_star(operand)?;
521 }
522 Node::ImplBlock { type_name, methods } => {
523 self.compile_impl_block(type_name, methods)?;
524 }
525 Node::StructDecl { name, fields, .. } => {
526 self.compile_struct_decl(name, fields)?;
527 }
528 Node::Pipeline { .. }
530 | Node::OverrideDecl { .. }
531 | Node::TypeDecl { .. }
532 | Node::EnumDecl { .. }
533 | Node::InterfaceDecl { .. } => {
534 self.chunk.emit(Op::Nil, self.line);
535 }
536 Node::TryCatch {
537 body,
538 error_var,
539 error_type,
540 catch_body,
541 finally_body,
542 } => {
543 self.compile_try_catch(body, error_var, error_type, catch_body, finally_body)?;
544 }
545 Node::TryExpr { body } => {
546 self.compile_try_expr(body)?;
547 }
548 Node::Retry { count, body } => {
549 self.compile_retry(count, body)?;
550 }
551 Node::CostRoute { options, body } => {
552 self.compile_cost_route(options, body)?;
553 }
554 Node::Parallel {
555 mode,
556 expr,
557 variable,
558 body,
559 options,
560 } => {
561 self.compile_parallel(mode, expr, variable, body, options)?;
562 }
563 Node::SpawnExpr { body } => {
564 self.compile_spawn_expr(body)?;
565 }
566 Node::HitlExpr { kind, args } => {
567 self.compile_hitl_expr(*kind, args)?;
568 }
569 Node::SelectExpr {
570 cases,
571 timeout,
572 default_body,
573 } => {
574 self.compile_select_expr(cases, timeout, default_body)?;
575 }
576 Node::Spread(_) => {
577 return Err(CompileError {
578 message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
579 line: self.line,
580 });
581 }
582 Node::AttributedDecl { attributes, inner } => {
583 self.compile_attributed_decl(attributes, inner)?;
584 }
585 Node::OrPattern(_) => {
586 return Err(CompileError {
587 message: "or-pattern (|) can only appear as a match arm pattern".into(),
588 line: self.line,
589 });
590 }
591 }
592 Ok(())
593 }
594}