1pub mod ast;
11pub mod eval;
12pub mod geom;
13pub mod ir;
14pub mod lexer;
15pub mod parser;
16pub mod svg;
17pub mod token;
18
19pub use eval::{EvalError, eval};
20pub use ir::Drawing;
21pub use lexer::{LexError, lex};
22pub use parser::{ParseError, parse};
23pub use svg::to_svg;
24pub use token::Token;
25
26pub const CIRCUITS: &str = include_str!("std/circuits.pic");
29
30pub fn compile(src: &str) -> Result<Drawing, String> {
32 let picture = parse(src).map_err(|e| e.to_string())?;
33 eval(&picture).map_err(|e| e.to_string())
34}
35
36pub fn render_svg(src: &str) -> Result<String, String> {
38 Ok(to_svg(&compile(src)?))
39}
40
41pub fn animations_json(d: &Drawing) -> String {
44 let mut s = String::from("[");
45 for (i, a) in d.anims.iter().enumerate() {
46 if i > 0 {
47 s.push(',');
48 }
49 s.push_str(&format!(
50 "{{\"id\":\"s{}\",\"effect\":\"{}\",\"start\":{},\"duration\":{}}}",
51 a.shape,
52 json_str(&a.effect),
53 a.start,
54 a.duration
55 ));
56 }
57 s.push(']');
58 s
59}
60
61pub fn compile_json(src: &str) -> String {
65 match compile(src) {
66 Ok(d) => format!(
67 "{{\"svg\":\"{}\",\"animations\":{}}}",
68 json_str(&to_svg(&d)),
69 animations_json(&d)
70 ),
71 Err(e) => format!("{{\"error\":\"{}\"}}", json_str(&e)),
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn json_bundles_svg_and_animations() {
81 let j = compile_json("box\nanimate last box with \"fade\"");
82 assert!(j.starts_with("{\"svg\":\"<svg"));
83 assert!(j.contains("<g id=\\\"s0\\\">")); assert!(j.contains("\"animations\":[{\"id\":\"s0\",\"effect\":\"fade\""));
85 }
86
87 #[test]
88 fn json_reports_errors() {
89 let j = compile_json("sh \"oops\"");
90 assert!(j.contains("\"error\""));
91 }
92
93 #[test]
94 fn circuits_library_compiles_and_draws() {
95 let src = format!(
96 "{}\nA:(0,0); B:(1.6,0); C:(3,0)\nresistor(A,B)\ncapacitor(A,B)\ninductor(A,B)\n\
97 diode(A,B)\nbattery(A,B)\nground(A)\ndot(A)\nwire(A,B)\n\
98 and_gate(C)\nor_gate(C)\nnand_gate(C)\nnor_gate(C)\nxor_gate(C)\n\
99 xnor_gate(C)\nbuffer(C)\nnot_gate(C)\n\
100 opamp(C)\nnpn(C)\npnp(C)\nac_source(A,B)\nswitch(A,B)\n\
101 potentiometer(A,B)\ntransformer(C)\n\
102 nmos(C)\npmos(C)\ncurrent_source(A,B)\nvsource_ctrl(A,B)\n\
103 isource_ctrl(A,B)\nled(A,B)\nphotodiode(A,B)\nzener(A,B)\n\
104 clabel(A,\"x\")\nterminal(A)\nvdd(A)\nantenna(A)\ncurrent(A,B)\n\
105 voltage(A,B)\nlamp(A,B)\nfuse(A,B)\n\
106 voltmeter(A,B)\nammeter(A,B)\nohmmeter(A,B)\nmeter(A,B,\"X\")\n\
107 crystal(A,B)\nspeaker(A,B)\nhop(A,B)\nspdt(A,B,C)\n\
108 iec_resistor(A,B)\nvoltage_source(A,B)\npushbutton(A,B)\nrelay(A,B)\n\
109 thermistor(A,B)\nmotor(A,B)\ngenerator(A,B)\nbell(A,B)\n\
110 ieee_and(C)\nieee_or(C)\nieee_xor(C)\nieee_buf(C)\niecgate(C,\"x\")\n\
111 varistor(A,B)\ntline(A,B)\nbus(A,B)\n\
112 polcap(A,B)\nvarcap(A,B)\nvarind(A,B)\nschottky(A,B)\nldr(A,B)\n\
113 spark_gap(A,B)\nchassis_ground(A)\nsignal_ground(A)\n\
114 njfet(C)\npjfet(C)\nphototransistor(C)\nic_block(C,\"x\")\nmux(C)\n\
115 solar_cell(A,B)\nmicrophone(A,B)\nthermocouple(A,B)",
116 CIRCUITS
117 );
118 let d = compile(&src).expect("circuit library should compile");
119 assert!(!d.shapes.is_empty());
120 }
121}
122
123fn json_str(s: &str) -> String {
125 let mut o = String::with_capacity(s.len() + 8);
126 for c in s.chars() {
127 match c {
128 '"' => o.push_str("\\\""),
129 '\\' => o.push_str("\\\\"),
130 '\n' => o.push_str("\\n"),
131 '\r' => o.push_str("\\r"),
132 '\t' => o.push_str("\\t"),
133 c if (c as u32) < 0x20 => o.push_str(&format!("\\u{:04x}", c as u32)),
134 c => o.push(c),
135 }
136 }
137 o
138}