1+ // src/modules/fstr.rs
2+ //
3+ // String building, errors, and logging without core::fmt.
4+ // Deps: itoa (ints), ryu (floats).
5+ //
6+ // TOKENS
7+ // "literal" &str literal
8+ // str expr &str expression
9+ // int expr integer (itoa)
10+ // float expr float (ryu)
11+ // char expr char
12+ // bool expr bool → "true" / "false"
13+ //
14+ // USAGE
15+ // s!("x=", int n, " flags=", bool f)
16+ // s!(cap: 64; "prefix", str p)
17+ // push!(buf, int n)
18+ // err!("bad token '", str tok, "' at line ", int line)
19+ // log_info!("processed ", int n, " items in ", float ms, "ms")
20+ // log_err!(e, "loading config '", str path, "'")
21+
22+ // ── push! ────────────────────────────────────────────────────────────────────
23+
24+ #[ macro_export]
25+ macro_rules! push {
26+ ( $s: ident, $v: literal) => { $s. push_str( $v) ; } ;
27+ ( $s: ident, str $v: expr) => { $s. push_str( $v) ; } ;
28+ ( $s: ident, int $v: expr) => { { let mut b = itoa:: Buffer :: new( ) ; $s. push_str( b. format( $v) ) ; } } ;
29+ ( $s: ident, float $v: expr) => { { let mut b = ryu:: Buffer :: new( ) ; $s. push_str( b. format( $v) ) ; } } ;
30+ ( $s: ident, char $v: expr) => { $s. push( $v) ; } ;
31+ ( $s: ident, bool $v: expr) => { $s. push_str( if $v { "true" } else { "false" } ) ; } ;
32+ }
33+
34+ // ── s! ───────────────────────────────────────────────────────────────────────
35+
36+ /// Build a `String` without `core::fmt`. See module header for token syntax.
37+ #[ macro_export]
38+ macro_rules! s {
39+ // internal builder arms
40+ ( @b $s: ident; ) => { } ;
41+ ( @b $s: ident; $l: literal $( , $( $r: tt) * ) ?) => { $s. push_str( $l) ; $( $crate:: s!( @b $s; $( $r) * ) ; ) ? } ;
42+ ( @b $s: ident; str $v: expr $( , $( $r: tt) * ) ?) => { $s. push_str( $v) ; $( $crate:: s!( @b $s; $( $r) * ) ; ) ? } ;
43+ ( @b $s: ident; int $v: expr $( , $( $r: tt) * ) ?) => { { let mut _b = itoa:: Buffer :: new( ) ; $s. push_str( _b. format( $v) ) ; $( $crate:: s!( @b $s; $( $r) * ) ; ) ? } } ;
44+ ( @b $s: ident; float $v: expr $( , $( $r: tt) * ) ?) => { { let mut _b = ryu:: Buffer :: new( ) ; $s. push_str( _b. format( $v) ) ; $( $crate:: s!( @b $s; $( $r) * ) ; ) ? } } ;
45+ ( @b $s: ident; char $v: expr $( , $( $r: tt) * ) ?) => { $s. push( $v) ; $( $crate:: s!( @b $s; $( $r) * ) ; ) ? } ;
46+ ( @b $s: ident; bool $v: expr $( , $( $r: tt) * ) ?) => { $s. push_str( if $v { "true" } else { "false" } ) ; $( $crate:: s!( @b $s; $( $r) * ) ; ) ? } ;
47+
48+ // public API
49+ ( cap: $c: expr; $( $t: tt) * ) => { { let mut _s = alloc:: string:: String :: with_capacity( $c) ; $crate:: s!( @b _s; $( $t) * ) ; _s } } ;
50+ ( $( $t: tt) * ) => { { let mut _s = alloc:: string:: String :: new( ) ; $crate:: s!( @b _s; $( $t) * ) ; _s } } ;
51+ }
52+
53+ // ── Error ────────────────────────────────────────────────────────────────────
54+
55+ pub enum E {
56+ Parse { ctx : & ' static str } ,
57+ Custom { msg : alloc:: string:: String } ,
58+ }
59+
60+ impl E {
61+ pub fn message ( & self ) -> alloc:: string:: String {
62+ match self {
63+ Self :: Parse { ctx } => s ! ( "parse error: " , str ctx) ,
64+ Self :: Custom { msg } => msg. clone ( ) ,
65+ }
66+ }
67+
68+ #[ inline] pub fn parse ( ctx : & ' static str ) -> Self { Self :: Parse { ctx } }
69+ }
70+
71+ impl From < E > for alloc:: string:: String { fn from ( e : E ) -> Self { e. message ( ) } }
72+
73+ /// Construct an `E::Custom` from token syntax: `err!("unexpected '", str tok, "' at line ", int line)`
74+ #[ macro_export]
75+ macro_rules! err {
76+ ( $( $t: tt) * ) => { $crate:: modules:: fstr:: E :: Custom { msg: $crate:: s!( $( $t) * ) } } ;
77+ }
78+
79+ // ── Log ──────────────────────────────────────────────────────────────────────
80+
81+ #[ derive( Clone , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
82+ pub enum Level { Debug = 0 , Info = 1 , Warn = 2 , Error = 3 }
83+
84+ impl Level {
85+ pub fn as_str ( self ) -> & ' static str {
86+ match self {
87+ Self :: Debug => "DEBUG" ,
88+ Self :: Info => "INFO " ,
89+ Self :: Warn => "WARN " ,
90+ Self :: Error => "ERROR" ,
91+ }
92+ }
93+ }
94+
95+ type Sink = fn ( Level , & str ) ;
96+ fn _noop ( _: Level , _: & str ) { }
97+
98+ static SINK : core:: sync:: atomic:: AtomicPtr < ( ) > = core:: sync:: atomic:: AtomicPtr :: new ( _noop as * mut ( ) ) ;
99+ static MIN_LEVEL : core:: sync:: atomic:: AtomicU8 = core:: sync:: atomic:: AtomicU8 :: new ( 0 ) ;
100+
101+ /// Register a log sink and minimum level. Messages below `min` are dropped.
102+ pub fn set_sink ( f : Sink , min : Level ) {
103+ SINK . store ( f as * mut ( ) , core:: sync:: atomic:: Ordering :: Relaxed ) ;
104+ MIN_LEVEL . store ( min as u8 , core:: sync:: atomic:: Ordering :: Relaxed ) ;
105+ }
106+
107+ #[ inline]
108+ pub fn emit ( level : Level , msg : & str ) {
109+ if level as u8 >= MIN_LEVEL . load ( core:: sync:: atomic:: Ordering :: Relaxed ) {
110+ let f: Sink = unsafe { core:: mem:: transmute ( SINK . load ( core:: sync:: atomic:: Ordering :: Relaxed ) ) } ;
111+ f ( level, msg) ;
112+ }
113+ }
114+
115+ #[ macro_export] macro_rules! log { ( $lvl: expr, $( $t: tt) * ) => { $crate:: modules:: fmt:: emit( $lvl, & $crate:: s!( $( $t) * ) ) ; } ; }
116+ #[ macro_export] macro_rules! log_debug { ( $( $t: tt) * ) => { $crate:: log!( $crate:: modules:: fmt:: Level :: Debug , $( $t) * ) } ; }
117+ #[ macro_export] macro_rules! log_info { ( $( $t: tt) * ) => { $crate:: log!( $crate:: modules:: fmt:: Level :: Info , $( $t) * ) } ; }
118+ #[ macro_export] macro_rules! log_warn { ( $( $t: tt) * ) => { $crate:: log!( $crate:: modules:: fmt:: Level :: Warn , $( $t) * ) } ; }
119+ #[ macro_export] macro_rules! log_error { ( $( $t: tt) * ) => { $crate:: log!( $crate:: modules:: fmt:: Level :: Error , $( $t) * ) } ; }
120+
121+ /// Log at error level, optionally with context: `log_err!(e)` or `log_err!(e, "loading '", str path, "'")`
122+ #[ macro_export]
123+ macro_rules! log_err {
124+ ( $e: expr) => { $crate:: modules:: fmt:: emit( $crate:: modules:: fmt:: Level :: Error , & $e. message( ) ) ; } ;
125+ ( $e: expr, $( $t: tt) * ) => { $crate:: modules:: fmt:: emit( $crate:: modules:: fmt:: Level :: Error , & $crate:: s!( $( $t) * , " — " , str & $e. message( ) ) ) ; } ;
126+ }
127+
128+ #[ cfg( not( target_arch = "wasm32" ) ) ]
129+ pub fn read_file ( path : & str ) -> Result < alloc:: string:: String , E > {
130+ std:: fs:: read_to_string ( path)
131+ . map_err ( |_| err ! ( "io: cannot access '" , str path, "'" ) )
132+ }
0 commit comments