Skip to content

Commit 156a10b

Browse files
Chore: Remove core::fmt: migrate string building to s./target/debug/edge -c 'print(2**100)
1 parent 157103e commit 156a10b

19 files changed

Lines changed: 249 additions & 86 deletions

File tree

compiler/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ serde_json = "1"
4040
hashbrown = { version = "0.17", default-features = false, features = ["serde"] }
4141

4242
[features]
43-
wasm = ["lol_alloc"]
43+
wasm = ["dep:lol_alloc"]
4444
wasm-tests = []
4545

4646
[profile.release]

compiler/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ pub mod modules {
1818
pub mod lexer;
1919
pub mod parser;
2020
pub mod vm;
21+
pub mod fstr;
2122
}

compiler/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ fn parse_args() -> (String, usize, bool, bool) {
3838

3939
fn run(path: &str, sandbox: bool) -> Result<(), String> {
4040
let src = if path.ends_with(".py") {
41-
fs::read_to_string(path).map_err(|e| format!("io: cannot access '{}': {}", path, e))?
41+
fs::read_to_string(path).map_err(|e| format!("io: cannot access '{}': {}", path, e))? // std binary; fmt allowed outside no_std boundary.
4242
} else {
4343
path.to_string()
4444
};

compiler/src/modules/fstr.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
}

compiler/src/modules/parser/control.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// parser/control.rs
22

3+
4+
use crate::s;
5+
36
use super::Parser;
47
use super::types::OpCode;
58

69
use crate::modules::lexer::{Token, TokenType};
710

8-
use alloc::{string::{String, ToString}, vec, vec::Vec, format};
11+
use alloc::{string::{String, ToString}, vec, vec::Vec};
912

1013
impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
1114

@@ -59,7 +62,7 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
5962
self.expr();
6063

6164
let ver = self.increment_version("__match__");
62-
let subj = self.chunk.push_name(&format!("__match__{}", ver));
65+
let subj = self.chunk.push_name(&s!("__match__", int ver));
6366
self.chunk.emit(OpCode::StoreName, subj);
6467

6568
self.eat(TokenType::Colon);

compiler/src/modules/parser/expr.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// parser/expr.rs
22

3+
use crate::s;
4+
35
use super::Parser;
46
use super::types::{OpCode, Value, MAX_EXPR_DEPTH, Instruction};
57

68
use super::types::parse_string;
79
use crate::modules::lexer::{Token, TokenType};
810

9-
use alloc::{string::ToString, vec::Vec, vec, format, string::String};
11+
use alloc::{string::ToString, vec::Vec, vec, string::String};
1012

1113
impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
1214

@@ -300,8 +302,14 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
300302
}
301303
let mut out = String::new();
302304
for (i, &l) in limbs.iter().rev().enumerate() {
303-
if i == 0 { out.push_str(&format!("{}", l)); }
304-
else { out.push_str(&format!("{:09}", l)); }
305+
if i == 0 {
306+
let mut b = itoa::Buffer::new();
307+
out.push_str(b.format(l));
308+
} else {
309+
let s = {let mut b = itoa::Buffer::new(); b.format(l).to_string()};
310+
for _ in 0..9usize.saturating_sub(s.len()) { out.push('0'); }
311+
out.push_str(&s);
312+
}
305313
}
306314
if out.is_empty() { out.push('0'); }
307315
out
@@ -396,7 +404,7 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
396404
s.chunk.emit(OpCode::ReturnValue, 0);
397405
});
398406

399-
let param_slots: alloc::collections::BTreeSet<String> = params.iter().map(|p| format!("{}_0", p.trim_start_matches('*'))).collect();
407+
let param_slots: alloc::collections::BTreeSet<String> = params.iter().map(|p| s!(str p.trim_start_matches('*'), "_0")).collect();
400408
for name in &body.names {
401409
if !param_slots.contains(name.as_str()) {
402410
self.chunk.push_name(name);

compiler/src/modules/parser/literals.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// parser/literals.rs
22

3+
use crate::s;
4+
35
use super::Parser;
46
use super::types::builtin;
57
use super::types::{OpCode, Value, SSAChunk, Instruction};
68

79
use crate::modules::lexer::{Token, TokenType, utf8_char_len};
810
use crate::modules::fx::FxHashMap as HashMap;
911

10-
use alloc::{string::{String, ToString}, vec::Vec, format};
12+
use alloc::{string::{String, ToString}, vec::Vec};
1113

1214
impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
1315

@@ -142,8 +144,9 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
142144
let old_ver = versions_before.get(var).copied().unwrap_or(0);
143145
let new_ver = self.current_version(var);
144146
if old_ver == new_ver { continue; }
145-
let old_name = format!("{}_{}", var, old_ver);
146-
let Some(&old_slot) = self.chunk.name_index.get(old_name.as_str()) else { continue };
147+
let mut ob = [0u8; 128];
148+
let old_name = Self::ssa_name(var, old_ver, &mut ob);
149+
let Some(&old_slot) = self.chunk.name_index.get(old_name) else { continue };
147150
let mut nb = [0u8; 128];
148151
let new_slot = self.chunk.push_name(Self::ssa_name(var, new_ver, &mut nb));
149152
var_map.insert(old_slot, new_slot);
@@ -399,11 +402,11 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
399402
}
400403
if self.eat_if(TokenType::Star) {
401404
let p = self.advance();
402-
params.push(format!("*{}", self.lexeme(&p)));
405+
params.push(s!("*", str self.lexeme(&p)));
403406
self.drain_annotation();
404407
} else if self.eat_if(TokenType::DoubleStar) {
405408
let p = self.advance();
406-
params.push(format!("**{}", self.lexeme(&p)));
409+
params.push(s!("**", str self.lexeme(&p)));
407410
self.drain_annotation();
408411
} else {
409412
let p = self.advance();

compiler/src/modules/parser/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
211211
if matches!(self.peek(), Some(k) if k == kind) {
212212
self.advance();
213213
} else {
214-
self.error(&format!("expected {:?}", kind));
214+
self.error(&format!("expected {:?}", kind)); // Debug formatting; s! has no Debug token
215215
}
216216
}
217217

compiler/src/modules/parser/stmt.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// parser/stmt.rs
22

3+
use crate::s;
4+
35
use super::Parser;
46
use super::types::OpCode;
57

68
use crate::modules::lexer::{Token, TokenType};
79

8-
use alloc::{string::{String, ToString}, vec, format};
10+
use alloc::{string::{String, ToString}, vec};
911

1012
impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
1113

@@ -189,7 +191,7 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
189191
Some(TokenType::Star) => {
190192
self.advance();
191193
let t = self.advance();
192-
let mut targets = vec![format!("*{}", self.lexeme(&t))];
194+
let mut targets = vec![s!("*", str self.lexeme(&t))];
193195
while self.eat_if(TokenType::Comma) {
194196
if !matches!(self.peek(), Some(TokenType::Name)) { break; }
195197
let t = self.advance();
@@ -375,7 +377,7 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
375377
if self.eat_if(TokenType::Star) {
376378
star_pos = Some(targets.len());
377379
let t = self.advance();
378-
targets.push(format!("*{}", self.lexeme(&t)));
380+
targets.push(s!("*", str self.lexeme(&t)));
379381
} else if matches!(self.peek(), Some(TokenType::Name)) {
380382
let t = self.advance();
381383
targets.push(self.lexeme(&t).to_string());

compiler/src/modules/parser/types.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// parser/types.rs
22

3-
use alloc::{string::{String, ToString}, vec, vec::Vec, format};
4-
3+
use crate::s;
54
use crate::modules::fx::FxHashMap as HashMap;
65

6+
use alloc::{string::{String, ToString}, vec, vec::Vec};
7+
78
pub(crate) const MAX_EXPR_DEPTH: usize = 200;
89
pub(crate) const MAX_INSTRUCTIONS: usize = 65_535;
910

@@ -128,7 +129,7 @@ impl SSAChunk {
128129
if let Some(pos) = name.rfind('_')
129130
&& let Ok(ver) = name[pos+1..].parse::<u32>()
130131
&& ver > 0 {
131-
let prev = format!("{}_{}", &name[..pos], ver - 1);
132+
let prev = s!(str &name[..pos], "_", int ver - 1);
132133
if let Some(&j) = self.name_index.get(&prev) {
133134
ps[i] = Some(j);
134135
}

0 commit comments

Comments
 (0)