baobao_codegen/builder/
expr.rs

1//! Language-agnostic expression builders.
2//!
3//! This module provides generic abstractions for code generation that can be
4//! rendered to any target language via the [`Renderer`] trait.
5//!
6//! # Core Abstractions
7//!
8//! - [`Value`] - Semantic values (bool, int, duration, enum variants, etc.)
9//! - [`BuilderSpec`] - Declarative specification for builder/fluent API patterns
10//! - [`Block`] - Scoped expressions with let bindings
11//! - [`Renderer`] - Trait for language-specific rendering
12//!
13//! # Example
14//!
15//! ```ignore
16//! use baobao_codegen::builder::{BuilderSpec, Value, Block, Binding};
17//!
18//! // Declarative builder specification
19//! let spec = BuilderSpec::new("SqliteConnectOptions")
20//!     .call_opt("create_if_missing", Some(Value::Bool(true)))
21//!     .call_opt("journal_mode", Some(Value::EnumVariant {
22//!         path: "SqliteJournalMode".into(),
23//!         variant: "Wal".into(),
24//!     }))
25//!     .call_opt("busy_timeout", Some(Value::Duration { millis: 5000 }));
26//!
27//! // Renders differently per language:
28//! // Rust:       SqliteConnectOptions::new().create_if_missing(true).journal_mode(SqliteJournalMode::Wal)
29//! // TypeScript: new SqliteConnectOptions().createIfMissing(true).journalMode(JournalMode.Wal)
30//! // Go:         sqlite.NewConnectOptions().CreateIfMissing(true).JournalMode(sqlite.JournalModeWal)
31//! ```
32
33use std::fmt;
34
35/// A semantic value that can be rendered to any language.
36///
37/// Values represent the *meaning* of data, not syntax. Each language's
38/// renderer decides how to format them appropriately.
39#[derive(Debug, Clone, PartialEq)]
40pub enum Value {
41    /// Boolean value.
42    Bool(bool),
43    /// Integer value.
44    Int(i64),
45    /// Unsigned integer value.
46    UInt(u64),
47    /// Floating point value.
48    Float(f64),
49    /// String literal (will be quoted).
50    String(String),
51    /// Raw identifier or expression (not quoted).
52    Ident(String),
53    /// Duration in milliseconds (rendered language-appropriately).
54    Duration {
55        /// Duration in milliseconds.
56        millis: u64,
57    },
58    /// Enum variant reference.
59    EnumVariant {
60        /// Full path to the enum type (e.g., "sqlx::sqlite::SqliteJournalMode").
61        path: String,
62        /// Variant name (e.g., "Wal").
63        variant: String,
64    },
65    /// Environment variable read.
66    ///
67    /// Rendered as `&std::env::var("NAME")?` in Rust, `process.env.NAME` in TS/JS.
68    EnvVar {
69        /// Environment variable name.
70        name: String,
71        /// Whether to pass by reference (Rust: `&`, others: no-op).
72        by_ref: bool,
73    },
74    /// Try expression (error propagation).
75    ///
76    /// Rendered as `expr?` in Rust, wrapped in try/catch in JS.
77    Try(Box<Value>),
78    /// Nested builder specification.
79    Builder(Box<BuilderSpec>),
80    /// Nested block expression.
81    Block(Box<Block>),
82}
83
84impl Value {
85    /// Create a boolean value.
86    pub fn bool(v: bool) -> Self {
87        Self::Bool(v)
88    }
89
90    /// Create an integer value.
91    pub fn int(v: i64) -> Self {
92        Self::Int(v)
93    }
94
95    /// Create an unsigned integer value.
96    pub fn uint(v: u64) -> Self {
97        Self::UInt(v)
98    }
99
100    /// Create a float value.
101    pub fn float(v: f64) -> Self {
102        Self::Float(v)
103    }
104
105    /// Create a string literal value.
106    pub fn string(v: impl Into<String>) -> Self {
107        Self::String(v.into())
108    }
109
110    /// Create an identifier/expression value.
111    pub fn ident(v: impl Into<String>) -> Self {
112        Self::Ident(v.into())
113    }
114
115    /// Create a duration value from seconds.
116    pub fn duration_secs(secs: u64) -> Self {
117        Self::Duration {
118            millis: secs * 1000,
119        }
120    }
121
122    /// Create a duration value from milliseconds.
123    pub fn duration_millis(millis: u64) -> Self {
124        Self::Duration { millis }
125    }
126
127    /// Create an enum variant value.
128    pub fn enum_variant(path: impl Into<String>, variant: impl Into<String>) -> Self {
129        Self::EnumVariant {
130            path: path.into(),
131            variant: variant.into(),
132        }
133    }
134
135    /// Create a nested builder value.
136    pub fn builder(spec: BuilderSpec) -> Self {
137        Self::Builder(Box::new(spec))
138    }
139
140    /// Create a nested block value.
141    pub fn block(block: Block) -> Self {
142        Self::Block(Box::new(block))
143    }
144
145    /// Create an environment variable read (by reference).
146    ///
147    /// In Rust: `&std::env::var("NAME")?`
148    /// In TypeScript: `process.env.NAME`
149    pub fn env_var(name: impl Into<String>) -> Self {
150        Self::EnvVar {
151            name: name.into(),
152            by_ref: true,
153        }
154    }
155
156    /// Create an environment variable read (by value).
157    ///
158    /// In Rust: `std::env::var("NAME")?`
159    /// In TypeScript: `process.env.NAME`
160    pub fn env_var_owned(name: impl Into<String>) -> Self {
161        Self::EnvVar {
162            name: name.into(),
163            by_ref: false,
164        }
165    }
166
167    /// Wrap a value in a try expression (error propagation).
168    ///
169    /// In Rust: `value?`
170    pub fn try_(value: Value) -> Self {
171        Self::Try(Box::new(value))
172    }
173
174    /// Render using the given renderer with default options.
175    pub fn render(&self, renderer: &dyn Renderer) -> String {
176        renderer.render_value(self, &RenderOptions::default())
177    }
178
179    /// Render inline using the given renderer.
180    pub fn render_inline(&self, renderer: &dyn Renderer) -> String {
181        renderer.render_value(self, &RenderOptions::inline())
182    }
183
184    /// Render with custom options.
185    pub fn render_with(&self, renderer: &dyn Renderer, opts: &RenderOptions) -> String {
186        renderer.render_value(self, opts)
187    }
188}
189
190/// How to construct the builder's base expression.
191#[derive(Debug, Clone, PartialEq)]
192pub enum Constructor {
193    /// Static method constructor: `Type::new()` or `Type.new()`.
194    StaticNew {
195        /// Full type path (e.g., "sqlx::pool::PoolOptions").
196        type_path: String,
197    },
198    /// Static method call with arguments: `Type::method(args)`.
199    StaticMethod {
200        /// Full type path (e.g., "sqlx::sqlite::SqliteConnectOptions").
201        type_path: String,
202        /// Method name (e.g., "from_str").
203        method: String,
204        /// Arguments to the method.
205        args: Vec<Value>,
206    },
207    /// Class-style constructor: `new Type()`.
208    ClassNew {
209        /// Type name (e.g., "PoolOptions").
210        type_name: String,
211    },
212    /// Factory function: `createType()` or `NewType()`.
213    Factory {
214        /// Factory function name.
215        name: String,
216    },
217}
218
219impl Constructor {
220    /// Create a static `::new()` constructor.
221    pub fn static_new(type_path: impl Into<String>) -> Self {
222        Self::StaticNew {
223            type_path: type_path.into(),
224        }
225    }
226
227    /// Create a static method call with arguments: `Type::method(args)`.
228    ///
229    /// # Example
230    /// ```ignore
231    /// // Produces: SqliteConnectOptions::from_str(env_var)
232    /// Constructor::static_method(
233    ///     "sqlx::sqlite::SqliteConnectOptions",
234    ///     "from_str",
235    ///     vec![Value::env_var("DATABASE_URL")],
236    /// )
237    /// ```
238    pub fn static_method(
239        type_path: impl Into<String>,
240        method: impl Into<String>,
241        args: Vec<Value>,
242    ) -> Self {
243        Self::StaticMethod {
244            type_path: type_path.into(),
245            method: method.into(),
246            args,
247        }
248    }
249
250    /// Create a class-style `new` constructor.
251    pub fn class_new(type_name: impl Into<String>) -> Self {
252        Self::ClassNew {
253            type_name: type_name.into(),
254        }
255    }
256
257    /// Create a factory function constructor.
258    pub fn factory(name: impl Into<String>) -> Self {
259        Self::Factory { name: name.into() }
260    }
261}
262
263/// A method call in a builder chain.
264#[derive(Debug, Clone, PartialEq)]
265pub struct MethodCall {
266    /// Method name (in canonical snake_case form).
267    pub name: String,
268    /// Arguments to the method.
269    pub args: Vec<Value>,
270}
271
272impl MethodCall {
273    /// Create a new method call.
274    pub fn new(name: impl Into<String>) -> Self {
275        Self {
276            name: name.into(),
277            args: Vec::new(),
278        }
279    }
280
281    /// Add an argument to the method call.
282    pub fn arg(mut self, value: Value) -> Self {
283        self.args.push(value);
284        self
285    }
286
287    /// Add multiple arguments.
288    pub fn args(mut self, values: impl IntoIterator<Item = Value>) -> Self {
289        self.args.extend(values);
290        self
291    }
292}
293
294/// Terminal operation for a builder chain.
295#[derive(Debug, Clone, PartialEq, Default)]
296pub struct Terminal {
297    /// Whether to add `.await` (or language equivalent).
298    pub is_async: bool,
299    /// Whether to add `?` or error propagation.
300    pub is_try: bool,
301    /// Optional final method call (e.g., `.build()`).
302    pub method: Option<String>,
303}
304
305impl Terminal {
306    /// Create a new terminal with default settings.
307    pub fn new() -> Self {
308        Self::default()
309    }
310
311    /// Mark as async (adds `.await` in Rust, `await` in JS, etc.).
312    pub fn async_(mut self) -> Self {
313        self.is_async = true;
314        self
315    }
316
317    /// Mark as try (adds `?` in Rust, try/catch wrapper in JS, etc.).
318    pub fn try_(mut self) -> Self {
319        self.is_try = true;
320        self
321    }
322
323    /// Add a terminal method call.
324    pub fn method(mut self, name: impl Into<String>) -> Self {
325        self.method = Some(name.into());
326        self
327    }
328}
329
330/// A declarative specification for a builder pattern.
331///
332/// This represents the *intent* of building an object via method chaining,
333/// independent of any specific language syntax.
334#[derive(Debug, Clone, PartialEq)]
335pub struct BuilderSpec {
336    /// How to construct the base expression.
337    pub constructor: Constructor,
338    /// Method calls in order.
339    pub calls: Vec<MethodCall>,
340    /// Terminal operation (await, try, build method).
341    pub terminal: Terminal,
342}
343
344impl BuilderSpec {
345    /// Create a new builder spec with a static `::new()` constructor.
346    pub fn new(type_path: impl Into<String>) -> Self {
347        Self {
348            constructor: Constructor::static_new(type_path),
349            calls: Vec::new(),
350            terminal: Terminal::default(),
351        }
352    }
353
354    /// Create a builder spec with a custom constructor.
355    pub fn with_constructor(constructor: Constructor) -> Self {
356        Self {
357            constructor,
358            calls: Vec::new(),
359            terminal: Terminal::default(),
360        }
361    }
362
363    /// Add a method call with no arguments.
364    pub fn call(mut self, name: impl Into<String>) -> Self {
365        self.calls.push(MethodCall::new(name));
366        self
367    }
368
369    /// Add a method call with a single argument.
370    pub fn call_arg(mut self, name: impl Into<String>, value: Value) -> Self {
371        self.calls.push(MethodCall::new(name).arg(value));
372        self
373    }
374
375    /// Add a method call with multiple arguments.
376    pub fn call_args(
377        mut self,
378        name: impl Into<String>,
379        values: impl IntoIterator<Item = Value>,
380    ) -> Self {
381        self.calls.push(MethodCall::new(name).args(values));
382        self
383    }
384
385    /// Conditionally add a method call.
386    pub fn call_if(self, condition: bool, name: impl Into<String>) -> Self {
387        if condition { self.call(name) } else { self }
388    }
389
390    /// Conditionally add a method call with argument.
391    pub fn call_arg_if(self, condition: bool, name: impl Into<String>, value: Value) -> Self {
392        if condition {
393            self.call_arg(name, value)
394        } else {
395            self
396        }
397    }
398
399    /// Add a method call if the value is Some.
400    pub fn call_opt(self, name: impl Into<String>, value: Option<Value>) -> Self {
401        match value {
402            Some(v) => self.call_arg(name, v),
403            None => self,
404        }
405    }
406
407    /// Mark the chain as async.
408    pub fn async_(mut self) -> Self {
409        self.terminal.is_async = true;
410        self
411    }
412
413    /// Mark the chain as try (error propagation).
414    pub fn try_(mut self) -> Self {
415        self.terminal.is_try = true;
416        self
417    }
418
419    /// Add a terminal method call.
420    pub fn terminal_method(mut self, name: impl Into<String>) -> Self {
421        self.terminal.method = Some(name.into());
422        self
423    }
424
425    /// Check if the spec has any method calls.
426    pub fn has_calls(&self) -> bool {
427        !self.calls.is_empty()
428    }
429
430    /// Apply configuration from an iterator of optional values.
431    ///
432    /// This enables declarative config-to-chain mapping:
433    /// ```ignore
434    /// spec.apply_config([
435    ///     ("max_connections", config.max.map(Value::uint)),
436    ///     ("timeout", config.timeout.map(Value::duration_secs)),
437    /// ])
438    /// ```
439    pub fn apply_config<I, S>(mut self, config: I) -> Self
440    where
441        I: IntoIterator<Item = (S, Option<Value>)>,
442        S: Into<String>,
443    {
444        for (name, value) in config {
445            if let Some(v) = value {
446                self.calls.push(MethodCall::new(name).arg(v));
447            }
448        }
449        self
450    }
451
452    /// Render using the given renderer with default options.
453    pub fn render(&self, renderer: &dyn Renderer) -> String {
454        renderer.render_builder(self, &RenderOptions::default())
455    }
456
457    /// Render inline using the given renderer.
458    pub fn render_inline(&self, renderer: &dyn Renderer) -> String {
459        renderer.render_builder(self, &RenderOptions::inline())
460    }
461
462    /// Render with custom options.
463    pub fn render_with(&self, renderer: &dyn Renderer, opts: &RenderOptions) -> String {
464        renderer.render_builder(self, opts)
465    }
466}
467
468/// A binding in a block (let/const/var).
469#[derive(Debug, Clone, PartialEq)]
470pub struct Binding {
471    /// Variable name.
472    pub name: String,
473    /// Value to bind.
474    pub value: Value,
475    /// Whether the binding is mutable.
476    pub mutable: bool,
477}
478
479impl Binding {
480    /// Create a new immutable binding.
481    pub fn new(name: impl Into<String>, value: Value) -> Self {
482        Self {
483            name: name.into(),
484            value,
485            mutable: false,
486        }
487    }
488
489    /// Create a new mutable binding.
490    pub fn new_mut(name: impl Into<String>, value: Value) -> Self {
491        Self {
492            name: name.into(),
493            value,
494            mutable: true,
495        }
496    }
497}
498
499/// A scoped block expression with bindings.
500///
501/// Represents `{ let x = ...; let y = ...; result }` patterns that exist
502/// across languages (Rust blocks, JS IIFEs, Go closures).
503#[derive(Debug, Clone, PartialEq)]
504pub struct Block {
505    /// Variable bindings.
506    pub bindings: Vec<Binding>,
507    /// The final expression/result.
508    pub body: Value,
509}
510
511impl Block {
512    /// Create a new block with the given body.
513    pub fn new(body: Value) -> Self {
514        Self {
515            bindings: Vec::new(),
516            body,
517        }
518    }
519
520    /// Add a let binding.
521    pub fn binding(mut self, name: impl Into<String>, value: Value) -> Self {
522        self.bindings.push(Binding::new(name, value));
523        self
524    }
525
526    /// Add a mutable binding.
527    pub fn binding_mut(mut self, name: impl Into<String>, value: Value) -> Self {
528        self.bindings.push(Binding::new_mut(name, value));
529        self
530    }
531
532    /// Render using the given renderer with default options.
533    pub fn render(&self, renderer: &dyn Renderer) -> String {
534        renderer.render_block(self, &RenderOptions::default())
535    }
536
537    /// Render inline using the given renderer.
538    pub fn render_inline(&self, renderer: &dyn Renderer) -> String {
539        renderer.render_block(self, &RenderOptions::inline())
540    }
541
542    /// Render with custom options.
543    pub fn render_with(&self, renderer: &dyn Renderer, opts: &RenderOptions) -> String {
544        renderer.render_block(self, opts)
545    }
546}
547
548/// Formatting options for rendering.
549#[derive(Debug, Clone)]
550pub struct RenderOptions {
551    /// Base indentation level.
552    pub indent: usize,
553    /// Spaces per indent level.
554    pub indent_size: usize,
555    /// Whether to render inline (single line) when possible.
556    pub inline: bool,
557}
558
559impl Default for RenderOptions {
560    fn default() -> Self {
561        Self {
562            indent: 0,
563            indent_size: 4,
564            inline: false,
565        }
566    }
567}
568
569impl RenderOptions {
570    /// Create options for inline rendering.
571    pub fn inline() -> Self {
572        Self {
573            inline: true,
574            ..Default::default()
575        }
576    }
577
578    /// Set the base indentation level.
579    pub fn with_indent(mut self, indent: usize) -> Self {
580        self.indent = indent;
581        self
582    }
583
584    /// Set spaces per indent level.
585    pub fn with_indent_size(mut self, size: usize) -> Self {
586        self.indent_size = size;
587        self
588    }
589
590    /// Get the current indentation string.
591    pub fn indent_str(&self) -> String {
592        " ".repeat(self.indent * self.indent_size)
593    }
594
595    /// Get indentation for a nested level.
596    pub fn nested(&self) -> Self {
597        Self {
598            indent: self.indent + 1,
599            ..*self
600        }
601    }
602}
603
604/// Trait for language-specific rendering of expressions.
605///
606/// Implement this trait to support a new target language.
607pub trait Renderer {
608    /// Render a value to a string.
609    fn render_value(&self, value: &Value, opts: &RenderOptions) -> String;
610
611    /// Render a builder specification to a string.
612    fn render_builder(&self, spec: &BuilderSpec, opts: &RenderOptions) -> String;
613
614    /// Render a block expression to a string.
615    fn render_block(&self, block: &Block, opts: &RenderOptions) -> String;
616
617    /// Transform a method name to the target language convention.
618    ///
619    /// Default implementation returns the name as-is (snake_case).
620    fn transform_method_name(&self, name: &str) -> String {
621        name.to_string()
622    }
623
624    /// Render a constructor to a string.
625    fn render_constructor(&self, ctor: &Constructor) -> String;
626
627    /// Render terminal operations (await, try, build method).
628    fn render_terminal(&self, terminal: &Terminal) -> String;
629}
630
631impl fmt::Display for Value {
632    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
633        // Debug representation for display
634        match self {
635            Value::Bool(v) => write!(f, "{}", v),
636            Value::Int(v) => write!(f, "{}", v),
637            Value::UInt(v) => write!(f, "{}", v),
638            Value::Float(v) => write!(f, "{}", v),
639            Value::String(v) => write!(f, "\"{}\"", v),
640            Value::Ident(v) => write!(f, "{}", v),
641            Value::Duration { millis } => write!(f, "{}ms", millis),
642            Value::EnumVariant { path, variant } => write!(f, "{}::{}", path, variant),
643            Value::EnvVar { name, .. } => write!(f, "env({})", name),
644            Value::Try(inner) => write!(f, "try({})", inner),
645            Value::Builder(_) => write!(f, "<builder>"),
646            Value::Block(_) => write!(f, "<block>"),
647        }
648    }
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654
655    #[test]
656    fn test_value_constructors() {
657        assert_eq!(Value::bool(true), Value::Bool(true));
658        assert_eq!(Value::int(42), Value::Int(42));
659        assert_eq!(Value::uint(100), Value::UInt(100));
660        assert_eq!(Value::string("hello"), Value::String("hello".into()));
661        assert_eq!(Value::ident("foo"), Value::Ident("foo".into()));
662        assert_eq!(Value::duration_secs(5), Value::Duration { millis: 5000 });
663        assert_eq!(Value::duration_millis(100), Value::Duration { millis: 100 });
664    }
665
666    #[test]
667    fn test_builder_spec_basic() {
668        let spec = BuilderSpec::new("PoolOptions")
669            .call_arg("max_connections", Value::uint(10))
670            .call_arg("min_connections", Value::uint(5));
671
672        assert_eq!(spec.calls.len(), 2);
673        assert_eq!(spec.calls[0].name, "max_connections");
674        assert_eq!(spec.calls[1].name, "min_connections");
675    }
676
677    #[test]
678    fn test_builder_spec_conditional() {
679        let spec = BuilderSpec::new("Options")
680            .call_opt("present", Some(Value::bool(true)))
681            .call_opt("missing", None)
682            .call_if(true, "enabled")
683            .call_if(false, "disabled");
684
685        assert_eq!(spec.calls.len(), 2);
686        assert_eq!(spec.calls[0].name, "present");
687        assert_eq!(spec.calls[1].name, "enabled");
688    }
689
690    #[test]
691    fn test_builder_spec_apply_config() {
692        let max: Option<u64> = Some(10);
693        let min: Option<u64> = None;
694        let timeout: Option<u64> = Some(30);
695
696        let spec = BuilderSpec::new("PoolOptions").apply_config([
697            ("max_connections", max.map(Value::uint)),
698            ("min_connections", min.map(Value::uint)),
699            ("timeout", timeout.map(Value::duration_secs)),
700        ]);
701
702        assert_eq!(spec.calls.len(), 2);
703        assert_eq!(spec.calls[0].name, "max_connections");
704        assert_eq!(spec.calls[1].name, "timeout");
705    }
706
707    #[test]
708    fn test_block_with_bindings() {
709        let block = Block::new(Value::ident("pool"))
710            .binding("options", Value::builder(BuilderSpec::new("SqliteOptions")))
711            .binding_mut("counter", Value::int(0));
712
713        assert_eq!(block.bindings.len(), 2);
714        assert!(!block.bindings[0].mutable);
715        assert!(block.bindings[1].mutable);
716    }
717
718    #[test]
719    fn test_constructor_variants() {
720        let static_new = Constructor::static_new("sqlx::pool::PoolOptions");
721        let static_method = Constructor::static_method(
722            "sqlx::sqlite::SqliteConnectOptions",
723            "from_str",
724            vec![Value::env_var("DATABASE_URL")],
725        );
726        let class_new = Constructor::class_new("PoolOptions");
727        let factory = Constructor::factory("NewPoolOptions");
728
729        assert!(matches!(static_new, Constructor::StaticNew { .. }));
730        assert!(matches!(static_method, Constructor::StaticMethod { .. }));
731        assert!(matches!(class_new, Constructor::ClassNew { .. }));
732        assert!(matches!(factory, Constructor::Factory { .. }));
733    }
734
735    #[test]
736    fn test_terminal_operations() {
737        let terminal = Terminal::new().async_().try_().method("build");
738
739        assert!(terminal.is_async);
740        assert!(terminal.is_try);
741        assert_eq!(terminal.method, Some("build".into()));
742    }
743
744    #[test]
745    fn test_render_options() {
746        let opts = RenderOptions::default().with_indent(2).with_indent_size(2);
747        assert_eq!(opts.indent_str(), "    ");
748
749        let nested = opts.nested();
750        assert_eq!(nested.indent_str(), "      ");
751    }
752}