fix: strip app block before compilation to avoid parse errors
The El compiler cannot parse app { config { KEY: Type = value } } syntax —
it's a declaration, not an expression. parse_app_block() already extracts
config/secrets/flags correctly; the remaining source just needs the block
removed before Compiler::compile() runs.
strip_app_block() replaces the block with blank lines (preserving line numbers)
and correctly skips occurrences inside // comments.
Fixes daemon startup: 'compile error: parse error: invalid expression starting with : at 632:23'
This commit is contained in:
+65
-5
@@ -340,6 +340,62 @@ fn redact_secrets(s: &str) -> String {
|
||||
})
|
||||
}
|
||||
|
||||
/// Strip the first `app "..." { ... }` block from El source so the compiler
|
||||
/// doesn't see the declaration syntax (which it cannot parse as an expression).
|
||||
/// The block is replaced with an equal number of blank lines to preserve line numbers.
|
||||
/// Only matches `app "` at the start of a line (optional leading whitespace), to
|
||||
/// avoid matching the keyword inside comments.
|
||||
fn strip_app_block(source: &str) -> String {
|
||||
// Find `app "` that appears at the start of a line (not inside a // comment).
|
||||
let app_start = {
|
||||
let mut found: Option<usize> = None;
|
||||
for (i, _) in source.match_indices("app \"") {
|
||||
// Walk backwards to start of line — if we hit '//' first, this is a comment.
|
||||
let line_start = source[..i].rfind('\n').map(|p| p + 1).unwrap_or(0);
|
||||
let prefix = &source[line_start..i];
|
||||
if prefix.contains("//") {
|
||||
continue; // inside a comment
|
||||
}
|
||||
// Only accept if prefix is whitespace-only (app block declaration)
|
||||
if prefix.chars().all(|c| c.is_whitespace()) {
|
||||
found = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
match found {
|
||||
Some(pos) => pos,
|
||||
None => return source.to_string(),
|
||||
}
|
||||
};
|
||||
|
||||
// Walk from app_start to find the opening brace (skip name token)
|
||||
let after_app = &source[app_start..];
|
||||
let brace_pos = if let Some(p) = after_app.find('{') { p } else { return source.to_string() };
|
||||
|
||||
let mut depth = 0usize;
|
||||
let mut end = app_start + brace_pos;
|
||||
let chars: Vec<char> = source[app_start + brace_pos..].chars().collect();
|
||||
for (i, &ch) in chars.iter().enumerate() {
|
||||
match ch {
|
||||
'{' => depth += 1,
|
||||
'}' => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
end = app_start + brace_pos + i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the block span with blank lines (preserve line count for error messages).
|
||||
let removed = &source[app_start..end];
|
||||
let newlines = removed.chars().filter(|&c| c == '\n').count();
|
||||
let replacement = "\n".repeat(newlines);
|
||||
format!("{}{}{}", &source[..app_start], replacement, &source[end..])
|
||||
}
|
||||
|
||||
/// Try to parse and apply app block from source. If parsing succeeds, apply context.
|
||||
/// Prints error and exits on secret resolution failure.
|
||||
fn maybe_apply_app_block(source: &str) {
|
||||
@@ -805,12 +861,16 @@ async fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Parse and apply `app` block before running.
|
||||
maybe_apply_app_block(&entry_source);
|
||||
|
||||
// Strip `app { ... }` before compilation — the compiler cannot parse
|
||||
// the declaration syntax; config/secrets are already extracted above.
|
||||
let compile_source = strip_app_block(&entry_source);
|
||||
|
||||
// Compile via Rust compiler.
|
||||
let opts = CompilerOptions {
|
||||
target: Target::Debug,
|
||||
..Default::default()
|
||||
};
|
||||
let compiled = Compiler::compile(&entry_source, opts)
|
||||
let compiled = Compiler::compile(&compile_source, opts)
|
||||
.map_err(|e| format!("compile error: {e}"))?;
|
||||
let instructions = el_compiler::Bytecode::deserialize_all(&compiled.artifact)
|
||||
.unwrap_or_default();
|
||||
@@ -8986,10 +9046,10 @@ thread_local! {
|
||||
"http://localhost:11434/api/chat".to_string(),
|
||||
String::new(),
|
||||
));
|
||||
// Neuron (the platform itself)
|
||||
// Neuron inference — lives at neuron.neurontechnologies.ai, Anthropic Messages format
|
||||
m.insert("neuron".to_string(), (
|
||||
std::env::var("NEURON_ENDPOINT")
|
||||
.unwrap_or_else(|_| "https://api.neurontechnologies.ai/v1/chat/completions".to_string()),
|
||||
.unwrap_or_else(|_| "https://neuron.neurontechnologies.ai/v1/messages".to_string()),
|
||||
std::env::var("NEURON_API_KEY").unwrap_or_default(),
|
||||
));
|
||||
m
|
||||
@@ -9064,8 +9124,8 @@ fn call_llm_blocking(model: &str, system: Option<&str>, prompt: &str) -> String
|
||||
Err(e) => return format!("llm_call error: {}", e),
|
||||
};
|
||||
|
||||
if endpoint.contains("anthropic.com") {
|
||||
// Anthropic Messages API
|
||||
if endpoint.contains("anthropic.com") || endpoint.contains("neuron.neurontechnologies.ai") {
|
||||
// Anthropic Messages API — also used by neuron.neurontechnologies.ai
|
||||
let body = if let Some(sys) = system {
|
||||
serde_json::json!({
|
||||
"model": get_anthropic_model(model),
|
||||
|
||||
Reference in New Issue
Block a user