1pub mod ast;
11pub mod eval;
12pub mod geom;
13pub mod ir;
14pub mod lexer;
15mod math;
16pub mod parser;
17pub mod svg;
18pub mod token;
19
20pub use eval::{EvalError, eval};
21pub use ir::Drawing;
22pub use lexer::{LexError, lex};
23pub use math::{MathSpan, set_math_renderer};
24pub use parser::{ParseError, parse, parse_in_dir};
25pub use svg::to_svg;
26pub use token::Token;
27
28pub const CIRCUITS: &str = include_str!("std/circuits.pic");
31
32pub fn compile(src: &str) -> Result<Drawing, String> {
34 compile_in_dir(src, None)
35}
36
37pub fn compile_in_dir(src: &str, base: Option<&std::path::Path>) -> Result<Drawing, String> {
39 let picture = parser::parse_in_dir(src, base).map_err(|e| e.to_string())?;
40 eval(&picture).map_err(|e| e.to_string())
41}
42
43pub fn render_svg(src: &str) -> Result<String, String> {
45 Ok(to_svg(&compile(src)?))
46}
47
48pub fn render_svg_in_dir(src: &str, base: Option<&std::path::Path>) -> Result<String, String> {
50 Ok(to_svg(&compile_in_dir(src, base)?))
51}
52
53pub fn animations_json(d: &Drawing) -> String {
56 let mut s = String::from("[");
57 for (i, a) in d.anims.iter().enumerate() {
58 if i > 0 {
59 s.push(',');
60 }
61 s.push_str(&format!(
62 "{{\"id\":\"s{}\",\"effect\":\"{}\",\"start\":{},\"duration\":{}}}",
63 a.shape,
64 json_str(&a.effect),
65 a.start,
66 a.duration
67 ));
68 }
69 s.push(']');
70 s
71}
72
73pub fn diagnostics_json(d: &Drawing) -> String {
75 let mut s = String::from("[");
76 for (i, line) in d.diagnostics.iter().enumerate() {
77 if i > 0 {
78 s.push(',');
79 }
80 s.push('"');
81 s.push_str(&json_str(line));
82 s.push('"');
83 }
84 s.push(']');
85 s
86}
87
88pub fn compile_json(src: &str) -> String {
92 match compile(src) {
93 Ok(d) => format!(
94 "{{\"svg\":\"{}\",\"animations\":{},\"diagnostics\":{}}}",
95 json_str(&to_svg(&d)),
96 animations_json(&d),
97 diagnostics_json(&d)
98 ),
99 Err(e) => format!("{{\"error\":\"{}\"}}", json_str(&e)),
100 }
101}
102
103fn json_str(s: &str) -> String {
105 let mut o = String::with_capacity(s.len() + 8);
106 for c in s.chars() {
107 match c {
108 '"' => o.push_str("\\\""),
109 '\\' => o.push_str("\\\\"),
110 '\n' => o.push_str("\\n"),
111 '\r' => o.push_str("\\r"),
112 '\t' => o.push_str("\\t"),
113 c if (c as u32) < 0x20 => o.push_str(&format!("\\u{:04x}", c as u32)),
114 c => o.push(c),
115 }
116 }
117 o
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn json_bundles_svg_and_animations() {
126 let j = compile_json("box\nanimate last box with \"fade\"");
127 assert!(j.starts_with("{\"svg\":\"<svg"));
128 assert!(j.contains("<g id=\\\"s0\\\">")); assert!(j.contains("\"animations\":[{\"id\":\"s0\",\"effect\":\"fade\""));
130 assert!(j.contains("\"diagnostics\":[]"));
131 }
132
133 #[test]
134 fn json_reports_errors() {
135 let j = compile_json("copy \"oops\"");
136 assert!(j.contains("\"error\""));
137 }
138
139 #[test]
140 fn json_reports_print_diagnostics() {
141 let j = compile_json("print \"hi\"\nprint 2+3");
142 assert!(j.contains("\"diagnostics\":[\"hi\",\"5\"]"));
143 }
144
145 #[test]
146 fn circuits_library_compiles_and_draws() {
147 let src = format!(
148 "{}\nA:(0,0); B:(1.6,0); C:(3,0)\nresistor(A,B)\ncapacitor(A,B)\ninductor(A,B)\n\
149 diode(A,B)\nbattery(A,B)\nground(A)\ndot(A)\nwire(A,B)\n\
150 and_gate(C)\nor_gate(C)\nnand_gate(C)\nnor_gate(C)\nxor_gate(C)\n\
151 xnor_gate(C)\nbuffer(C)\nnot_gate(C)\n\
152 opamp(C)\nnpn(C)\npnp(C)\nac_source(A,B)\nswitch(A,B)\n\
153 potentiometer(A,B)\ntransformer(C)\n\
154 nmos(C)\npmos(C)\ncurrent_source(A,B)\nvsource_ctrl(A,B)\n\
155 isource_ctrl(A,B)\nled(A,B)\nphotodiode(A,B)\nzener(A,B)\n\
156 clabel(A,\"x\")\nterminal(A)\nvdd(A)\nantenna(A)\ncurrent(A,B)\n\
157 voltage(A,B)\nlamp(A,B)\nfuse(A,B)\n\
158 voltmeter(A,B)\nammeter(A,B)\nohmmeter(A,B)\nmeter(A,B,\"X\")\n\
159 crystal(A,B)\nspeaker(A,B)\nhop(A,B)\nspdt(A,B,C)\n\
160 iec_resistor(A,B)\nvoltage_source(A,B)\npushbutton(A,B)\nrelay(A,B)\n\
161 thermistor(A,B)\nmotor(A,B)\ngenerator(A,B)\nbell(A,B)\n\
162 ieee_and(C)\nieee_or(C)\nieee_xor(C)\nieee_buf(C)\niecgate(C,\"x\")\n\
163 varistor(A,B)\ntline(A,B)\nbus(A,B)\n\
164 polcap(A,B)\nvarcap(A,B)\nvarind(A,B)\nschottky(A,B)\nldr(A,B)\n\
165 spark_gap(A,B)\nchassis_ground(A)\nsignal_ground(A)\n\
166 njfet(C)\npjfet(C)\nphototransistor(C)\nic_block(C,\"x\")\nmux(C)\n\
167 solar_cell(A,B)\nmicrophone(A,B)\nthermocouple(A,B)",
168 CIRCUITS
169 );
170 let d = compile(&src).expect("circuit library should compile");
171 assert!(!d.shapes.is_empty());
172 }
173}