atmos/os_lib/aura/mod.rs
1//! Aura: `.aura` アプリ/REPL 用の独自 Lisp 系スクリプト言語の実行系。
2//!
3//! ターミナルアプリ([`crate::apps::terminal`])の REPL、および `/apps/アプリ名/` 配下の
4//! `.aura` アプリのサンドボックス実行基盤として使われる。ブラウザの `<script>` は
5//! JavaScript エンジン([`crate::os_lib::js`])が別途実行する(Aura の役割ではない)。
6//!
7//! 言語仕様・組み込み関数リファレンス・アプリパッケージ設計の詳細は `spec/AURA.md` を参照。
8//!
9//! - [`lexer`]: 字句解析
10//! - [`parser`]: S式パーサ
11//! - [`eval`]: 評価器([`eval::Env`] が環境を保持)
12//! - [`builtins`]: 組み込み関数群(`arith`/`math`/`os_fs`/`os_net`/`cloud`/`system` 等)
13//! - [`number`]: 数値型(整数/浮動小数点の統一表現)
14pub mod builtins;
15pub mod eval;
16pub mod lexer;
17pub mod number;
18pub mod parser;
19
20use alloc::format;
21use alloc::string::String;
22
23// グローバル環境を保持するための static は OS 組み込み等で必要になりますが、
24// ひとまずシェルから呼ばれるたびに評価する構造とします。
25// 後でシェルプロセス単位の永続的な環境を持たせることも可能です。
26
27pub fn eval_string(input: &str, env: &mut eval::Env) -> String {
28 let tokens = lexer::tokenize(input);
29 if tokens.is_empty() {
30 return String::new();
31 }
32
33 match parser::parse(&tokens) {
34 Ok(asts) => {
35 let mut result_str = String::new();
36
37 for ast in asts {
38 match eval::eval(env, &ast) {
39 Ok(val) => {
40 match eval::force_eval(env, val) {
41 Ok(v) => {
42 let final_v = match v {
43 eval::Value::BuiltinFunc(f) => match f(env, &[]) {
44 Ok(res) => res,
45 Err(e) => eval::Value::Str(alloc::format!("Error: {}", e)),
46 },
47 eval::Value::Lambda(arg_names, body_ast, closure_env) => {
48 if arg_names.is_empty() {
49 let mut local_env =
50 eval::Env::with_parent(*closure_env);
51 match eval::eval(&mut local_env, &body_ast) {
52 Ok(res) => eval::force_eval(&mut local_env, res)
53 .unwrap_or_else(|e| {
54 eval::Value::Str(alloc::format!(
55 "Error: {}",
56 e
57 ))
58 }),
59 Err(e) => {
60 eval::Value::Str(alloc::format!("Error: {}", e))
61 }
62 }
63 } else {
64 eval::Value::Lambda(arg_names, body_ast, closure_env)
65 }
66 }
67 other => other,
68 };
69
70 if let eval::Value::Nil = final_v {
71 // nil は出力しない(ターミナルの余分な改行を防ぐため)
72 } else {
73 if !result_str.is_empty() {
74 result_str.push('\n');
75 }
76 result_str.push_str(&final_v.to_string());
77 }
78 }
79 Err(e) => return format!("Eval Error: {}", e),
80 }
81 }
82 Err(e) => return format!("Eval Error: {}", e),
83 }
84 }
85 result_str
86 }
87 Err(e) => format!("Syntax Error: {}", e),
88 }
89}
90
91pub fn selftest() -> (usize, usize) {
92 let mut passed = 0;
93 let mut total = 0;
94
95 let mut env = eval::setup_global_env(core::ptr::null_mut());
96
97 let cases = [
98 ("(1 2 3)", "(1 2 3)"),
99 ("+(1, 2)", "3"),
100 ("set(x, (10 20 30))", "(10 20 30)"),
101 ("x(0)", "10"),
102 ("x(1, 3)", "(20 30)"),
103 ("length((1 2 3))", "3"),
104 ("cons(0, (1 2 3))", "(0 1 2 3)"),
105 ("3.14", "157/50"),
106 ("+(0.5, 0.25)", "3/4"),
107 ("+(1, 2) # line comment\n", "3"),
108 ("+(1, 2) ## line comment\n", "3"),
109 ("+(1, #| inline block |# 2)", "3"),
110 ("+(1, ##| inline block |## 2)", "3"),
111 ];
112
113 for &(input, expected) in &cases {
114 total += 1;
115 let res = eval_string(input, &mut env);
116 if res == expected {
117 passed += 1;
118 } else {
119 crate::println!(
120 "AURA_SELFTEST FAIL: `{}` => `{}` (expected `{}`)",
121 input,
122 res,
123 expected
124 );
125 }
126 }
127
128 (passed, total)
129}