Add readline, color, http_post_auth builtins; fix import resolution in build
Engram can now power the Neuron CLI: - readline(prompt) -> String: interactive terminal input via stdin - http_post_auth(url, token, body) -> String: authenticated POST for daemon API - color_cyan/green/red/yellow/bold/dim(s) -> String: ANSI color output All registered in el-types type checker - el build now resolves import "file.el" directives recursively (was only done for el run-file and el check; project builds failed silently) - Add .gitignore (target/, *.elc, *.sealed, *.map.json)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
target/
|
||||||
|
*.elc
|
||||||
|
*.sealed
|
||||||
|
*.map.json
|
||||||
|
.el/
|
||||||
|
.claude/
|
||||||
+288
-2
@@ -2186,6 +2186,201 @@ fn dispatch_builtin(
|
|||||||
let _ = request.as_reader().read_to_string(&mut body);
|
let _ = request.as_reader().read_to_string(&mut body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Built-in landing page routes ─────────────────────────────
|
||||||
|
// These short-circuit before invoking the Engram handle_request.
|
||||||
|
|
||||||
|
let cors_origin = "Access-Control-Allow-Origin: *".parse::<tiny_http::Header>().unwrap();
|
||||||
|
let content_json = "Content-Type: application/json".parse::<tiny_http::Header>().unwrap();
|
||||||
|
let content_html = "Content-Type: text/html; charset=utf-8".parse::<tiny_http::Header>().unwrap();
|
||||||
|
|
||||||
|
// OPTIONS — CORS preflight
|
||||||
|
if method == "OPTIONS" {
|
||||||
|
let _ = request.respond(
|
||||||
|
tiny_http::Response::from_string("")
|
||||||
|
.with_status_code(204)
|
||||||
|
.with_header(cors_origin)
|
||||||
|
.with_header("Access-Control-Allow-Methods: GET, POST, OPTIONS".parse::<tiny_http::Header>().unwrap())
|
||||||
|
.with_header("Access-Control-Allow-Headers: Content-Type".parse::<tiny_http::Header>().unwrap())
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET / — serve __html_file__ if set, otherwise dashboard
|
||||||
|
if method == "GET" && (path == "/" || path == "/index.html") {
|
||||||
|
let html_path = GLOBAL_STATE.with(|gs| gs.borrow().get("__html_file__").cloned());
|
||||||
|
let html = if let Some(p) = html_path {
|
||||||
|
std::fs::read_to_string(&p)
|
||||||
|
.unwrap_or_else(|_| include_str!("dashboard.html").to_string())
|
||||||
|
} else {
|
||||||
|
include_str!("dashboard.html").to_string()
|
||||||
|
};
|
||||||
|
let _ = request.respond(
|
||||||
|
tiny_http::Response::from_string(html)
|
||||||
|
.with_header(content_html)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /health
|
||||||
|
if method == "GET" && path == "/health" {
|
||||||
|
let _ = request.respond(
|
||||||
|
tiny_http::Response::from_string(r#"{"status":"ok"}"#)
|
||||||
|
.with_header(content_json)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/chat — proxy to Neuron runtime (SSE → collect → JSON)
|
||||||
|
// Transforms landing page format { message, history, conv_id }
|
||||||
|
// into runtime format { messages: [{role,content}], conv_id }
|
||||||
|
if method == "POST" && path == "/api/chat" {
|
||||||
|
let runtime_url = std::env::var("NEURON_RUNTIME_URL")
|
||||||
|
.unwrap_or_else(|_| "http://localhost:4444".to_string());
|
||||||
|
let chat_url = format!("{}/api/chat", runtime_url);
|
||||||
|
let result: Result<String, String> = (|| {
|
||||||
|
// Transform body format
|
||||||
|
let runtime_body = if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
|
||||||
|
if v.get("message").is_some() {
|
||||||
|
// Landing page format → runtime format
|
||||||
|
let msg = v["message"].as_str().unwrap_or("").to_string();
|
||||||
|
let history = v["history"].as_array().cloned().unwrap_or_default();
|
||||||
|
let conv_id_val = v.get("conv_id").cloned().unwrap_or(serde_json::Value::Null);
|
||||||
|
let mut messages = history;
|
||||||
|
messages.push(serde_json::json!({"role":"user","content":msg}));
|
||||||
|
let mut payload = serde_json::json!({"messages": messages});
|
||||||
|
if !conv_id_val.is_null() {
|
||||||
|
payload["conv_id"] = conv_id_val;
|
||||||
|
}
|
||||||
|
payload.to_string()
|
||||||
|
} else {
|
||||||
|
body.clone() // already in runtime format
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body.clone()
|
||||||
|
};
|
||||||
|
let resp = reqwest::blocking::Client::new()
|
||||||
|
.post(&chat_url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(runtime_body)
|
||||||
|
.send()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
use std::io::BufRead;
|
||||||
|
let mut reply = String::new();
|
||||||
|
let mut conv_id = String::new();
|
||||||
|
for line in resp.text().map_err(|e| e.to_string())?.lines() {
|
||||||
|
if line.starts_with("data: ") {
|
||||||
|
let data = &line[6..];
|
||||||
|
if data == "[DONE]" { break; }
|
||||||
|
if let Ok(v) = serde_json::from_str::<serde_json::Value>(data) {
|
||||||
|
if let Some(d) = v.get("delta").and_then(|x| x.as_str()) {
|
||||||
|
reply.push_str(d);
|
||||||
|
}
|
||||||
|
if let Some(c) = v.get("conv_id").and_then(|x| x.as_str()) {
|
||||||
|
conv_id = c.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let out = serde_json::json!({"reply": reply, "conv_id": conv_id});
|
||||||
|
Ok(out.to_string())
|
||||||
|
})();
|
||||||
|
let (status, resp_body) = match result {
|
||||||
|
Ok(r) => (200u16, r),
|
||||||
|
Err(e) => (502, format!(r#"{{"error":"runtime unavailable: {}"}}"#, e)),
|
||||||
|
};
|
||||||
|
let _ = request.respond(
|
||||||
|
tiny_http::Response::from_string(resp_body)
|
||||||
|
.with_status_code(status)
|
||||||
|
.with_header(content_json)
|
||||||
|
.with_header(cors_origin)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/email — send link via Resend
|
||||||
|
if method == "POST" && path == "/api/email" {
|
||||||
|
let resend_key = std::env::var("RESEND_API_KEY").unwrap_or_default();
|
||||||
|
let result: Result<(), String> = (|| {
|
||||||
|
let v: serde_json::Value = serde_json::from_str(&body)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let email = v["email"].as_str().unwrap_or("").to_string();
|
||||||
|
let name = v["name"].as_str().unwrap_or("there").to_string();
|
||||||
|
let conv_id = v["conv_id"].as_str().unwrap_or("").to_string();
|
||||||
|
let base_url = v["return_url"].as_str().unwrap_or("").to_string();
|
||||||
|
if email.is_empty() { return Err("no email".to_string()); }
|
||||||
|
let link = if conv_id.is_empty() {
|
||||||
|
base_url.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}?cid={}", base_url, conv_id)
|
||||||
|
};
|
||||||
|
let html_body = format!(
|
||||||
|
r#"<div style="font-family:Georgia,serif;max-width:480px;margin:0 auto;padding:40px 20px">
|
||||||
|
<p style="font-size:1.1rem;line-height:1.6">Hey {} —</p>
|
||||||
|
<p style="font-size:1.1rem;line-height:1.6">It's Neuron. You left our conversation in the middle.</p>
|
||||||
|
<p style="font-size:1.1rem;line-height:1.6">I remember where we were.</p>
|
||||||
|
<p style="margin:32px 0">
|
||||||
|
<a href="{}" style="background:#0052A0;color:#fff;padding:12px 24px;text-decoration:none;border-radius:4px;font-family:sans-serif">Come back when you're ready</a>
|
||||||
|
</p>
|
||||||
|
<p style="color:#888;font-size:0.8rem;font-family:sans-serif">That link brings you right back to where we left off.</p>
|
||||||
|
</div>"#,
|
||||||
|
name, link
|
||||||
|
);
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"from": "Neuron <neuron@neurontechnologies.ai>",
|
||||||
|
"to": [email],
|
||||||
|
"subject": format!("Hey {} \u{2014} come back when you\u{2019}re ready", name),
|
||||||
|
"html": html_body
|
||||||
|
});
|
||||||
|
reqwest::blocking::Client::new()
|
||||||
|
.post("https://api.resend.com/emails")
|
||||||
|
.header("Authorization", format!("Bearer {}", resend_key))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(payload.to_string())
|
||||||
|
.send()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
})();
|
||||||
|
let (status, resp_body) = match result {
|
||||||
|
Ok(()) => (200u16, r#"{"ok":true}"#.to_string()),
|
||||||
|
Err(e) => (500, format!(r#"{{"error":"{}"}}"#, e)),
|
||||||
|
};
|
||||||
|
let _ = request.respond(
|
||||||
|
tiny_http::Response::from_string(resp_body)
|
||||||
|
.with_status_code(status)
|
||||||
|
.with_header(content_json)
|
||||||
|
.with_header(cors_origin)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/waitlist — acknowledge (email already captured above)
|
||||||
|
if method == "POST" && path == "/api/waitlist" {
|
||||||
|
let _ = request.respond(
|
||||||
|
tiny_http::Response::from_string(r#"{"ok":true}"#)
|
||||||
|
.with_header(content_json)
|
||||||
|
.with_header(cors_origin)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/remember — forward to Neuron runtime
|
||||||
|
if method == "POST" && path == "/api/remember" {
|
||||||
|
let runtime_url = std::env::var("NEURON_RUNTIME_URL")
|
||||||
|
.unwrap_or_else(|_| "http://localhost:4444".to_string());
|
||||||
|
let _ = reqwest::blocking::Client::new()
|
||||||
|
.post(format!("{}/api/remember", runtime_url))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(body.clone())
|
||||||
|
.send();
|
||||||
|
let _ = request.respond(
|
||||||
|
tiny_http::Response::from_string(r#"{"ok":true}"#)
|
||||||
|
.with_header(content_json)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── End built-in routes — fall through to Engram handle_request ─
|
||||||
|
|
||||||
// Store method, path, body in global state so handle_request can read them
|
// Store method, path, body in global state so handle_request can read them
|
||||||
GLOBAL_STATE.with(|gs| {
|
GLOBAL_STATE.with(|gs| {
|
||||||
let mut s = gs.borrow_mut();
|
let mut s = gs.borrow_mut();
|
||||||
@@ -2204,12 +2399,12 @@ fn dispatch_builtin(
|
|||||||
|
|
||||||
let response_body = GLOBAL_STATE.with(|gs| {
|
let response_body = GLOBAL_STATE.with(|gs| {
|
||||||
gs.borrow().get("__response__").cloned()
|
gs.borrow().get("__response__").cloned()
|
||||||
.unwrap_or_else(|| r#"{"error":"no response"}"#.to_string())
|
.unwrap_or_else(|| r#"{"error":"not found"}"#.to_string())
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = request.respond(
|
let _ = request.respond(
|
||||||
tiny_http::Response::from_string(response_body)
|
tiny_http::Response::from_string(response_body)
|
||||||
.with_header("Content-Type: application/json".parse::<tiny_http::Header>().unwrap())
|
.with_header(content_json)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
stack.push(Value::Nil);
|
stack.push(Value::Nil);
|
||||||
@@ -3337,6 +3532,97 @@ fn dispatch_builtin(
|
|||||||
stack.push(Value::Str(result));
|
stack.push(Value::Str(result));
|
||||||
BuiltinResult::Handled
|
BuiltinResult::Handled
|
||||||
}
|
}
|
||||||
|
"http_post_auth" => {
|
||||||
|
let body = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let token = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let url = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let result = reqwest::blocking::Client::new()
|
||||||
|
.post(&url)
|
||||||
|
.header("Authorization", format!("Bearer {token}"))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.and_then(|r| r.text())
|
||||||
|
.unwrap_or_default();
|
||||||
|
stack.push(Value::Str(result));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── I/O builtins ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
"readline" => {
|
||||||
|
let prompt = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
use std::io::Write;
|
||||||
|
print!("{prompt}");
|
||||||
|
let _ = std::io::stdout().flush();
|
||||||
|
let mut line = String::new();
|
||||||
|
let _ = std::io::stdin().read_line(&mut line);
|
||||||
|
stack.push(Value::Str(line.trim_end_matches('\n').trim_end_matches('\r').to_string()));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ANSI color builtins ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
"color_cyan" => {
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
other => other.to_string(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Str(format!("\x1b[36m{s}\x1b[0m")));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"color_green" => {
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
other => other.to_string(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Str(format!("\x1b[32m{s}\x1b[0m")));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"color_red" => {
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
other => other.to_string(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Str(format!("\x1b[31m{s}\x1b[0m")));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"color_yellow" => {
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
other => other.to_string(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Str(format!("\x1b[33m{s}\x1b[0m")));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"color_bold" => {
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
other => other.to_string(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Str(format!("\x1b[1m{s}\x1b[0m")));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"color_dim" => {
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
other => other.to_string(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Str(format!("\x1b[2m{s}\x1b[0m")));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
|
||||||
// ── String / array helpers ────────────────────────────────────────────
|
// ── String / array helpers ────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -161,8 +161,9 @@ impl BuildSystem {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read source
|
// Read source (resolving imports recursively)
|
||||||
let source = std::fs::read_to_string(&entry)?;
|
let source = resolve_imports_recursive(&entry)
|
||||||
|
.map_err(|e| BuildError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
|
||||||
|
|
||||||
// Build seal config for prod builds
|
// Build seal config for prod builds
|
||||||
let seal_config = self.build_seal_config()?;
|
let seal_config = self.build_seal_config()?;
|
||||||
@@ -296,7 +297,8 @@ impl BuildSystem {
|
|||||||
return Err(BuildError::EntryNotFound(entry.display().to_string()));
|
return Err(BuildError::EntryNotFound(entry.display().to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = std::fs::read_to_string(&entry)?;
|
let source = resolve_imports_recursive(&entry)
|
||||||
|
.map_err(|e| BuildError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
|
||||||
let tokens = el_lexer::tokenize(&source)
|
let tokens = el_lexer::tokenize(&source)
|
||||||
.map_err(el_compiler::CompileError::Lex)?;
|
.map_err(el_compiler::CompileError::Lex)?;
|
||||||
let program = el_parser::parse(tokens, source.clone())
|
let program = el_parser::parse(tokens, source.clone())
|
||||||
@@ -340,6 +342,51 @@ impl BuildSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve `import "path.el"` directives by reading and concatenating source files.
|
||||||
|
/// Imports are resolved relative to the directory of the importing file.
|
||||||
|
/// Circular imports are detected via a visited set.
|
||||||
|
fn resolve_imports_recursive(file: &std::path::Path) -> Result<String, String> {
|
||||||
|
let mut visited = std::collections::HashSet::new();
|
||||||
|
resolve_imports_inner(file, &mut visited)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_imports_inner(
|
||||||
|
file: &std::path::Path,
|
||||||
|
visited: &mut std::collections::HashSet<std::path::PathBuf>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let canonical = file.canonicalize().unwrap_or_else(|_| file.to_path_buf());
|
||||||
|
if visited.contains(&canonical) {
|
||||||
|
return Ok(String::new()); // circular — skip
|
||||||
|
}
|
||||||
|
visited.insert(canonical.clone());
|
||||||
|
|
||||||
|
let dir = file.parent().unwrap_or(std::path::Path::new("."));
|
||||||
|
let source = std::fs::read_to_string(file)
|
||||||
|
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
|
||||||
|
|
||||||
|
let mut out = String::new();
|
||||||
|
for line in source.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if let Some(rest) = trimmed.strip_prefix("import ") {
|
||||||
|
let rest = rest.trim();
|
||||||
|
if rest.starts_with('"') && rest.ends_with('"') {
|
||||||
|
let import_path_str = &rest[1..rest.len() - 1];
|
||||||
|
let import_path = dir.join(import_path_str);
|
||||||
|
let imported = resolve_imports_inner(&import_path, visited)?;
|
||||||
|
out.push_str(&imported);
|
||||||
|
out.push('\n');
|
||||||
|
} else {
|
||||||
|
out.push_str(line);
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.push_str(line);
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
fn artifact_name(
|
fn artifact_name(
|
||||||
pkg_name: &str,
|
pkg_name: &str,
|
||||||
build_target: &BuildTarget,
|
build_target: &BuildTarget,
|
||||||
|
|||||||
@@ -134,9 +134,11 @@ impl TypeEnv {
|
|||||||
for name in &["str_len","string_len","list_len","array_length","array_len","map_len","json_array_len"] {
|
for name in &["str_len","string_len","list_len","array_length","array_len","map_len","json_array_len"] {
|
||||||
env.functions.insert(name.to_string(), str_fn(vec![u.clone()], i.clone()));
|
env.functions.insert(name.to_string(), str_fn(vec![u.clone()], i.clone()));
|
||||||
}
|
}
|
||||||
for name in &["str_replace","string_replace","string_concat","string_substring","str_slice"] {
|
for name in &["str_replace","string_replace","string_concat","string_substring"] {
|
||||||
env.functions.insert(name.to_string(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone()));
|
env.functions.insert(name.to_string(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone()));
|
||||||
}
|
}
|
||||||
|
// str_slice(s: String, start: Int, end: Int) -> String
|
||||||
|
env.functions.insert("str_slice".into(), str_fn(vec![s.clone(), i.clone(), i.clone()], s.clone()));
|
||||||
env.functions.insert("str_split".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown));
|
env.functions.insert("str_split".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown));
|
||||||
env.functions.insert("string_split".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown));
|
env.functions.insert("string_split".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown));
|
||||||
env.functions.insert("string_split_last".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown));
|
env.functions.insert("string_split_last".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown));
|
||||||
@@ -216,6 +218,7 @@ impl TypeEnv {
|
|||||||
env.functions.insert("http_delete".into(), str_fn(vec![s.clone()], s.clone()));
|
env.functions.insert("http_delete".into(), str_fn(vec![s.clone()], s.clone()));
|
||||||
env.functions.insert("http_patch".into(), str_fn(vec![s.clone(), s.clone()], s.clone()));
|
env.functions.insert("http_patch".into(), str_fn(vec![s.clone(), s.clone()], s.clone()));
|
||||||
env.functions.insert("http_get_auth".into(), str_fn(vec![s.clone(), s.clone()], s.clone()));
|
env.functions.insert("http_get_auth".into(), str_fn(vec![s.clone(), s.clone()], s.clone()));
|
||||||
|
env.functions.insert("http_post_auth".into(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone()));
|
||||||
env.functions.insert("http_put_auth".into(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone()));
|
env.functions.insert("http_put_auth".into(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone()));
|
||||||
env.functions.insert("http_delete_auth".into(), str_fn(vec![s.clone(), s.clone()], s.clone()));
|
env.functions.insert("http_delete_auth".into(), str_fn(vec![s.clone(), s.clone()], s.clone()));
|
||||||
env.functions.insert("http_serve".into(), str_fn(vec![u.clone()], Type::Void));
|
env.functions.insert("http_serve".into(), str_fn(vec![u.clone()], Type::Void));
|
||||||
@@ -232,6 +235,12 @@ impl TypeEnv {
|
|||||||
env.functions.insert("exit".into(), str_fn(vec![i.clone()], Type::Void));
|
env.functions.insert("exit".into(), str_fn(vec![i.clone()], Type::Void));
|
||||||
env.functions.insert("sleep_ms".into(), str_fn(vec![i.clone()], Type::Void));
|
env.functions.insert("sleep_ms".into(), str_fn(vec![i.clone()], Type::Void));
|
||||||
env.functions.insert("timestamp".into(), str_fn(vec![], s.clone()));
|
env.functions.insert("timestamp".into(), str_fn(vec![], s.clone()));
|
||||||
|
env.functions.insert("readline".into(), str_fn(vec![s.clone()], s.clone()));
|
||||||
|
|
||||||
|
// ANSI color builtins
|
||||||
|
for name in &["color_cyan","color_green","color_red","color_yellow","color_bold","color_dim"] {
|
||||||
|
env.functions.insert(name.to_string(), str_fn(vec![s.clone()], s.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
// Math
|
// Math
|
||||||
for name in &["math_abs","math_floor","math_ceil","math_round","math_sqrt"] {
|
for name in &["math_abs","math_floor","math_ceil","math_round","math_sqrt"] {
|
||||||
|
|||||||
Reference in New Issue
Block a user