■ 미니 언어를 실행하는 웹 어셈블리를 만드는 방법을 보여준다.
▶ Cargo.toml
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[package] name = "test_library" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] [dependencies] peg = "0.7" wasm-bindgen = "0.2" |
▶ src/node.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#[derive(Debug, Clone)] pub enum Node { Nop, Number(i64), // 숫자 값 Expression(char, Box<Node>, Box<Node>), // 계산식 If(Box<Node>, Box<Vec<Node>>, Box<Vec<Node>>), // if문 For(String, i64, i64, Box<Vec<Node>>), // for문 Print(Box<Node>), // print문(계산 출력) PrintString(String), // print문(문자열 출력) SetVariable(String, Box<Node>), // 변수 대입 GetVariable(String) // 변수 참조 } impl Node { pub fn expression(operator : char, left_value : Node, right_value : Node) -> Node { return Node::Expression(operator, Box::new(left_value), Box::new(right_value)); } pub fn if_(condition_node : Node, true_node_vector : Vec<Node>, false_node_vector : Vec<Node>) -> Node { return Node::If(Box::new(condition_node), Box::new(true_node_vector), Box::new(false_node_vector)); } } |
▶ src/parser.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
use peg; use crate::node; peg::parser! ( pub grammar custom_language() for str { pub rule parse() -> Vec<node::Node> = value : sentences() rule sentences() -> Vec<node::Node> = sentence() ** end_of_line() rule sentence() -> node::Node = print() / if() / for() / let() / _ { node::Node::Nop } rule print() -> node::Node = "print" _ "\"" value : $([^ '"']*) "\"" { node::Node::PrintString(value.to_string()) } / "print" _ value : calc() { node::Node::Print(Box::new(value)) } rule if() -> node::Node = "if" _ value : if_cond() { value } rule if_cond() -> node::Node = if_elif() / if_else() / if_true_only() rule if_elif() -> node::Node = condition_node : calc() true_node_vector : block() lf() "elif" _ false_node_vector : if_cond() { node::Node::if_(condition_node, true_node_vector, vec![false_node_vector]) } rule if_else() -> node::Node = condition_node : calc() true_node_vector : block() lf() "else" _ false_node_vector : block() { node::Node::if_(condition_node, true_node_vector, false_node_vector) } rule if_true_only() -> node::Node = condition_node : calc() true_node_vector : block() { node::Node::if_(condition_node, true_node_vector, vec![]) } rule block() -> Vec<node::Node> = "{" _ value : sentences() _ "}" _ { value } rule for() -> node::Node = "for" _ variable_name : word() _ "=" _ start : number() _ "to" _ end : number() _ body : block() { node::Node::For(variable_name, start, end, Box::new(body)) } rule let() -> node::Node = variable_name : word() _ "=" _ value : calc() { node::Node::SetVariable(variable_name, Box::new(value))} rule calc() -> node::Node = comp() rule comp() -> node::Node = left : expr() "==" _ right : comp() { node::Node::expression('=', left, right) } / left : expr() "!=" _ right : comp() { node::Node::expression('!', left, right) } / left : expr() ">" _ right : comp() { node::Node::expression('>', left, right) } / left : expr() ">=" _ right : comp() { node::Node::expression('g', left, right) } / left : expr() "<" _ right : comp() { node::Node::expression('<', left, right) } / left : expr() "<=" _ right : comp() { node::Node::expression('l', left, right) } / expr() rule expr() -> node::Node = left : term() "+" _ right : calc() { node::Node::expression('+', left, right) } / left : term() "-" _ right : calc() { node::Node::expression('-', left, right) } / term() rule term() -> node::Node = left : val() "*" _ right : term() { node::Node::expression('*', left, right) } / left : val() "/" _ right : term() { node::Node::expression('/', left, right) } / left : val() "%" _ right : term() { node::Node::expression('%', left, right) } / val() rule val() -> node::Node = "(" _ value : calc() _ ")" _ { value } / value : number() _ { node::Node::Number(value) } / value : word() _ { node::Node::GetVariable(value) } rule number() -> i64 = value : $(['0'..='9']+) { value.parse().unwrap() } rule word() -> String = value : $(['a'..='z'|'A'..='Z'|'_']+ ['0'..='9']*) { String::from(value) } rule end_of_line() = [';' | '\n']+ _ // 문장 나누기 rule lf() = _ ['\n']* _ // 줄바꿈 rule _ = [' ' | '\t']* // 공백문자 } ); |
▶ src/runner.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
use std::collections; use crate::node; use crate::parser; struct Context { variable_hashmap : collections::HashMap<String, i64>, output : String } fn run_node(context : &mut Context, node : node::Node) -> i64 { match node { node::Node::Number(value) => value, node::Node::Expression(operator, left_value, right_value) => { return calculate(operator, run_node(context, *left_value), run_node(context, *right_value)); }, node::Node::GetVariable(variable_name) => { return match context.variable_hashmap.get(&variable_name) { Some(value) => *value, None => 0 }; }, node::Node::SetVariable(variable_name, node) => { let value : i64 = run_node(context, *node); context.variable_hashmap.insert(variable_name, value); return value; }, node::Node::If(condition_node_box, true_node_vector_box, false_node_vector_box) => { let result : i64 = run_node(context, *condition_node_box); if result > 0 { return run_nodes(context, &*true_node_vector_box); } else { return run_nodes(context, &*false_node_vector_box); } }, node::Node::For(control_variable_name, start_value, end_value, body_node_vector_box) => { let mut result : i64 = 0; let node_vector : Vec<node::Node> = *body_node_vector_box; for i in start_value..=end_value { context.variable_hashmap.insert(control_variable_name.clone(), i); result = run_nodes(context, &node_vector); } return result; }, node::Node::PrintString(value) => { context.output += &format!("{}\n", value); return 0; }, node::Node::Print(node_box) => { let value : i64 = run_node(context, *node_box); context.output += &format!("{}\n", value); return value; }, _ => 0 } } fn calculate(operator : char, left_value : i64, right_value : i64) -> i64 { return match operator { '+' => left_value + right_value, '-' => left_value - right_value, '*' => left_value * right_value, '/' => left_value / right_value, '%' => left_value % right_value, '=' => if left_value == right_value {1} else {0}, '!' => if left_value != right_value {1} else {0}, '>' => if left_value > right_value {1} else {0}, 'g' => if left_value >= right_value {1} else {0}, '<' => if left_value < right_value {1} else {0}, 'l' => if left_value <= right_value {1} else {0}, _ => 0 }; } fn run_nodes(context : &mut Context, node_vector : &Vec<node::Node>) -> i64 { let mut result : i64 = 0; node_vector.iter().for_each(|node : &node::Node| { result = run_node(context, node.clone()); }); return result; } pub fn run(source : &str) -> String { let node_vector : Vec<node::Node> = match parser::custom_language::parse(source) { Ok(res) => res, Err(e) => return e.to_string() }; let mut context : Context = Context { variable_hashmap : collections::HashMap::new(), output : String::new() }; let result = run_nodes(&mut context, &node_vector); if context.output == "" { return format!("{}", result); } else { return context.output.clone(); } } |
▶ src/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
extern crate wasm_bindgen; mod runner; mod parser; mod node; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn test_run(source : &str) -> String { return runner::run(source); } |
▶ index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<!DOCTYPE html> <html> <meta charset="utf-8"> <body> <script type="module"> import init, {test_run} from "./pkg/test_library.js"; init().then ( () => { window.test_run = test_run; } ); </script> <script type="text/javascript"> function run() { if(!window.test_run) { alert("WebAssembly 로드가 완료되지 않았습니다."); return; } const code = document.getElementById("code").value; const r = window.test_run(code); const result = document.getElementById("result"); result.innerHTML = to_html(r); } function to_html(s) { s = s.replace(/&/g , "&" ); s = s.replace(/</g , "<" ); s = s.replace(/>/g , ">" ); s = s.replace(/\n/g, "<br>\n"); return s; } </script> <h1>미니 언어</h1> <div> <textarea id="code" rows=8 cols=60></textarea><br> <button onclick="run()">실행</button> <hr> <div id="result"></div> </div> </body> </html> |
※ 참고 사항
1. wasm-pack이 설치되어 있어야 한다.
2. cargo run 또는 cargo build 명령이 아니라 wasp-pack build 명령으로 컴파일을 해야 한다.
3. 웹 서버에 wasm MIME 타입을 등록해야 한다.
4. 2번이 성공적으로 실행되면 pkg 폴더가 생성되는데 pkg 폴더를 웹 서버의 적절한 디렉토리에 복사한다.