Update compiler and add goto_transform
This commit is contained in:
parent
59a74fa3ec
commit
0d4797738b
3 changed files with 648 additions and 37 deletions
504
src/basic/compiler/goto_transform.rs
Normal file
504
src/basic/compiler/goto_transform.rs
Normal file
|
|
@ -0,0 +1,504 @@
|
||||||
|
//! GOTO/Label transformation for BASIC
|
||||||
|
//!
|
||||||
|
//! Transforms GOTO-based control flow into a state machine that Rhai can execute.
|
||||||
|
//!
|
||||||
|
//! # Warning
|
||||||
|
//!
|
||||||
|
//! While GOTO is supported for backward compatibility, it is **strongly recommended**
|
||||||
|
//! to use event-driven patterns with the `ON` keyword instead:
|
||||||
|
//!
|
||||||
|
//! ```basic
|
||||||
|
//! ' ❌ OLD WAY - GOTO loop (not recommended)
|
||||||
|
//! mainLoop:
|
||||||
|
//! data = FIND "sensors", "processed = false"
|
||||||
|
//! WAIT 5
|
||||||
|
//! GOTO mainLoop
|
||||||
|
//!
|
||||||
|
//! ' ✅ NEW WAY - Event-driven with ON (recommended)
|
||||||
|
//! ON INSERT OF "sensors"
|
||||||
|
//! data = GET LAST "sensors"
|
||||||
|
//! ' Process data reactively
|
||||||
|
//! END ON
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Benefits of ON over GOTO:
|
||||||
|
//! - More efficient (no polling)
|
||||||
|
//! - Cleaner code structure
|
||||||
|
//! - Better integration with LLM tools
|
||||||
|
//! - Automatic resource management
|
||||||
|
|
||||||
|
use log::{trace, warn};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
/// Represents a labeled block of code
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LabeledBlock {
|
||||||
|
name: String,
|
||||||
|
lines: Vec<String>,
|
||||||
|
next_label: Option<String>, // Fall-through label
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if source contains GOTO statements or labels
|
||||||
|
pub fn has_goto_constructs(source: &str) -> bool {
|
||||||
|
for line in source.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
let upper = trimmed.to_uppercase();
|
||||||
|
|
||||||
|
// Label detection: "labelname:" at start of line (not a string, not a comment)
|
||||||
|
if is_label_line(trimmed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOTO detection (but not ON ERROR GOTO)
|
||||||
|
if upper.contains("GOTO ") && !upper.contains("ON ERROR GOTO") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a line is a label definition
|
||||||
|
fn is_label_line(line: &str) -> bool {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
// Must end with : and not contain spaces (except it could be indented)
|
||||||
|
if !trimmed.ends_with(':') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if it's a comment
|
||||||
|
if trimmed.starts_with('\'') || trimmed.starts_with("REM ") || trimmed.starts_with("//") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the label name (everything before the colon)
|
||||||
|
let label_part = trimmed.trim_end_matches(':');
|
||||||
|
|
||||||
|
// Must be a valid identifier (alphanumeric + underscore, not starting with number)
|
||||||
|
if label_part.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check it's not a CASE statement or other construct
|
||||||
|
let upper = label_part.to_uppercase();
|
||||||
|
if upper == "CASE" || upper == "DEFAULT" || upper == "ELSE" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be a simple identifier
|
||||||
|
let first_char = label_part.chars().next().unwrap();
|
||||||
|
if !first_char.is_alphabetic() && first_char != '_' {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
label_part.chars().all(|c| c.is_alphanumeric() || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract label name from a label line
|
||||||
|
fn extract_label(line: &str) -> Option<String> {
|
||||||
|
if is_label_line(line) {
|
||||||
|
Some(line.trim().trim_end_matches(':').to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform BASIC code with GOTO/labels into Rhai-compatible state machine
|
||||||
|
///
|
||||||
|
/// This function emits warnings when GOTO is detected, recommending the use of
|
||||||
|
/// ON keyword patterns instead.
|
||||||
|
pub fn transform_goto(source: &str) -> String {
|
||||||
|
let lines: Vec<&str> = source.lines().collect();
|
||||||
|
|
||||||
|
// First pass: find all labels and GOTO statements
|
||||||
|
let mut labels: HashSet<String> = HashSet::new();
|
||||||
|
let mut goto_targets: HashSet<String> = HashSet::new();
|
||||||
|
let mut has_goto = false;
|
||||||
|
|
||||||
|
for line in &lines {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
let upper = trimmed.to_uppercase();
|
||||||
|
|
||||||
|
// Label detection
|
||||||
|
if let Some(label) = extract_label(trimmed) {
|
||||||
|
labels.insert(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOTO detection (but not ON ERROR GOTO)
|
||||||
|
if upper.contains("GOTO ") && !upper.contains("ON ERROR GOTO") {
|
||||||
|
has_goto = true;
|
||||||
|
|
||||||
|
// Extract target label
|
||||||
|
if let Some(target) = extract_goto_target(trimmed) {
|
||||||
|
goto_targets.insert(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No GOTO? Return unchanged
|
||||||
|
if !has_goto {
|
||||||
|
return source.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit warning about GOTO usage
|
||||||
|
warn!(
|
||||||
|
"⚠️ GOTO detected in BASIC script. Consider using event-driven patterns with ON keyword instead."
|
||||||
|
);
|
||||||
|
warn!(" Example: ON INSERT OF \"table\" ... END ON");
|
||||||
|
warn!(" See documentation: https://docs.generalbots.com/06-gbdialog/keyword-on.html");
|
||||||
|
|
||||||
|
// Check for undefined labels
|
||||||
|
for target in &goto_targets {
|
||||||
|
if !labels.contains(target) {
|
||||||
|
warn!("⚠️ GOTO references undefined label: {}", target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Transforming GOTO: {} labels found, {} GOTO statements",
|
||||||
|
labels.len(),
|
||||||
|
goto_targets.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Second pass: split code into labeled blocks
|
||||||
|
let blocks = split_into_blocks(&lines, &labels);
|
||||||
|
|
||||||
|
// Third pass: generate state machine
|
||||||
|
generate_state_machine(&blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split source lines into labeled blocks
|
||||||
|
fn split_into_blocks(lines: &[&str], labels: &HashSet<String>) -> Vec<LabeledBlock> {
|
||||||
|
let mut blocks: Vec<LabeledBlock> = Vec::new();
|
||||||
|
let mut current_label = "__start".to_string();
|
||||||
|
let mut current_lines: Vec<String> = Vec::new();
|
||||||
|
let mut label_order: Vec<String> = vec!["__start".to_string()];
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
// Skip empty lines and comments in block splitting (but keep them in output)
|
||||||
|
if trimmed.is_empty()
|
||||||
|
|| trimmed.starts_with('\'')
|
||||||
|
|| trimmed.starts_with("//")
|
||||||
|
|| trimmed.starts_with("REM ")
|
||||||
|
{
|
||||||
|
// Keep comments in the current block
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
current_lines.push(format!(
|
||||||
|
"// {}",
|
||||||
|
trimmed.trim_start_matches(&['\'', '/'][..])
|
||||||
|
));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New label starts a new block
|
||||||
|
if let Some(label) = extract_label(trimmed) {
|
||||||
|
// Save previous block if it has content
|
||||||
|
if !current_lines.is_empty() || current_label != "__start" || blocks.is_empty() {
|
||||||
|
let next_label = if labels.contains(&label) {
|
||||||
|
Some(label.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
blocks.push(LabeledBlock {
|
||||||
|
name: current_label.clone(),
|
||||||
|
lines: current_lines.clone(),
|
||||||
|
next_label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
current_label = label.clone();
|
||||||
|
label_order.push(label);
|
||||||
|
current_lines.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_lines.push(trimmed.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save final block
|
||||||
|
if !current_lines.is_empty() || blocks.is_empty() {
|
||||||
|
blocks.push(LabeledBlock {
|
||||||
|
name: current_label,
|
||||||
|
lines: current_lines,
|
||||||
|
next_label: None, // End of program
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix up next_label references based on order
|
||||||
|
let label_order_vec: Vec<_> = label_order.iter().collect();
|
||||||
|
for (i, block) in blocks.iter_mut().enumerate() {
|
||||||
|
if block.next_label.is_none() && i + 1 < label_order_vec.len() {
|
||||||
|
// Check if there's a block after this one
|
||||||
|
let current_idx = label_order_vec.iter().position(|l| **l == block.name);
|
||||||
|
if let Some(idx) = current_idx {
|
||||||
|
if idx + 1 < label_order_vec.len() {
|
||||||
|
block.next_label = Some(label_order_vec[idx + 1].to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the target label from a GOTO statement
|
||||||
|
fn extract_goto_target(line: &str) -> Option<String> {
|
||||||
|
let upper = line.to_uppercase();
|
||||||
|
|
||||||
|
// Simple GOTO
|
||||||
|
if let Some(pos) = upper.find("GOTO ") {
|
||||||
|
let rest = &line[pos + 5..];
|
||||||
|
let target = rest.trim().split_whitespace().next()?;
|
||||||
|
return Some(target.trim_matches(|c| c == '"' || c == '\'').to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the state machine code
|
||||||
|
fn generate_state_machine(blocks: &[LabeledBlock]) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
// Add warning comment at the top
|
||||||
|
output.push_str(
|
||||||
|
"// ⚠️ WARNING: This code uses GOTO which is transformed into a state machine.\n",
|
||||||
|
);
|
||||||
|
output.push_str("// Consider using event-driven patterns with ON keyword instead:\n");
|
||||||
|
output.push_str("// ON INSERT OF \"table\" ... END ON\n");
|
||||||
|
output.push_str("// See: https://docs.generalbots.com/06-gbdialog/keyword-on.html\n\n");
|
||||||
|
|
||||||
|
// Determine start label
|
||||||
|
let start_label = if blocks.is_empty() {
|
||||||
|
"__start"
|
||||||
|
} else {
|
||||||
|
&blocks[0].name
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push_str(&format!("let __goto_label = \"{}\";\n", start_label));
|
||||||
|
output.push_str("let __goto_iterations = 0;\n");
|
||||||
|
output.push_str("let __goto_max_iterations = 1000000;\n\n");
|
||||||
|
output.push_str("while __goto_label != \"__exit\" {\n");
|
||||||
|
output.push_str(" __goto_iterations += 1;\n");
|
||||||
|
output.push_str(" if __goto_iterations > __goto_max_iterations {\n");
|
||||||
|
output.push_str(
|
||||||
|
" throw \"GOTO loop exceeded maximum iterations. Possible infinite loop.\";\n",
|
||||||
|
);
|
||||||
|
output.push_str(" }\n\n");
|
||||||
|
|
||||||
|
for block in blocks {
|
||||||
|
output.push_str(&format!(" if __goto_label == \"{}\" {{\n", block.name));
|
||||||
|
|
||||||
|
for line in &block.lines {
|
||||||
|
let transformed = transform_line(line);
|
||||||
|
// Indent the transformed line
|
||||||
|
for transformed_line in transformed.lines() {
|
||||||
|
if !transformed_line.trim().is_empty() {
|
||||||
|
output.push_str(&format!(" {}\n", transformed_line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall-through to next label or exit
|
||||||
|
match &block.next_label {
|
||||||
|
Some(next) => {
|
||||||
|
output.push_str(&format!(" __goto_label = \"{}\"; continue;\n", next));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
output.push_str(" __goto_label = \"__exit\";\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_str(" }\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_str("}\n");
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform a single line, handling GOTO and IF...GOTO
|
||||||
|
fn transform_line(line: &str) -> String {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
let upper = trimmed.to_uppercase();
|
||||||
|
|
||||||
|
// Skip ON ERROR GOTO - that's handled separately
|
||||||
|
if upper.contains("ON ERROR GOTO") {
|
||||||
|
return trimmed.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple GOTO at start of line
|
||||||
|
if upper.starts_with("GOTO ") {
|
||||||
|
let target = trimmed[5..].trim();
|
||||||
|
return format!("__goto_label = \"{}\"; continue;", target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IF ... THEN GOTO ... (single line if)
|
||||||
|
if upper.starts_with("IF ") && upper.contains(" THEN GOTO ") {
|
||||||
|
if let Some(then_pos) = upper.find(" THEN GOTO ") {
|
||||||
|
let condition = &trimmed[3..then_pos].trim();
|
||||||
|
let target = trimmed[then_pos + 11..].trim();
|
||||||
|
return format!(
|
||||||
|
"if {} {{ __goto_label = \"{}\"; continue; }}",
|
||||||
|
condition, target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IF ... THEN ... (might contain GOTO after THEN)
|
||||||
|
if upper.starts_with("IF ") && upper.contains(" THEN ") {
|
||||||
|
if let Some(then_pos) = upper.find(" THEN ") {
|
||||||
|
let after_then = &trimmed[then_pos + 6..];
|
||||||
|
let after_then_upper = after_then.trim().to_uppercase();
|
||||||
|
|
||||||
|
if after_then_upper.starts_with("GOTO ") {
|
||||||
|
let condition = &trimmed[3..then_pos].trim();
|
||||||
|
let target = after_then.trim()[5..].trim();
|
||||||
|
return format!(
|
||||||
|
"if {} {{ __goto_label = \"{}\"; continue; }}",
|
||||||
|
condition, target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IF ... GOTO ... (without THEN - some BASIC dialects support this)
|
||||||
|
if upper.starts_with("IF ") && upper.contains(" GOTO ") && !upper.contains(" THEN ") {
|
||||||
|
if let Some(goto_pos) = upper.find(" GOTO ") {
|
||||||
|
let condition = &trimmed[3..goto_pos].trim();
|
||||||
|
let target = trimmed[goto_pos + 6..].trim();
|
||||||
|
return format!(
|
||||||
|
"if {} {{ __goto_label = \"{}\"; continue; }}",
|
||||||
|
condition, target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a GOTO line, return as-is
|
||||||
|
trimmed.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_label_line() {
|
||||||
|
assert!(is_label_line("start:"));
|
||||||
|
assert!(is_label_line(" mainLoop:"));
|
||||||
|
assert!(is_label_line("my_label:"));
|
||||||
|
assert!(is_label_line("label123:"));
|
||||||
|
|
||||||
|
assert!(!is_label_line("TALK \"hello:\""));
|
||||||
|
assert!(!is_label_line("' comment:"));
|
||||||
|
assert!(!is_label_line("CASE:"));
|
||||||
|
assert!(!is_label_line("123label:"));
|
||||||
|
assert!(!is_label_line("has space:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_goto_target() {
|
||||||
|
assert_eq!(extract_goto_target("GOTO start"), Some("start".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
extract_goto_target(" GOTO myLabel"),
|
||||||
|
Some("myLabel".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
extract_goto_target("IF x > 5 THEN GOTO done"),
|
||||||
|
Some("done".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(extract_goto_target("TALK \"hello\""), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transform_line_simple_goto() {
|
||||||
|
assert_eq!(
|
||||||
|
transform_line("GOTO start"),
|
||||||
|
"__goto_label = \"start\"; continue;"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
transform_line(" GOTO myLoop "),
|
||||||
|
"__goto_label = \"myLoop\"; continue;"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transform_line_if_then_goto() {
|
||||||
|
let result = transform_line("IF x < 10 THEN GOTO start");
|
||||||
|
assert!(result.contains("if x < 10"));
|
||||||
|
assert!(result.contains("__goto_label = \"start\""));
|
||||||
|
assert!(result.contains("continue"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transform_line_if_goto_no_then() {
|
||||||
|
let result = transform_line("IF x < 10 GOTO start");
|
||||||
|
assert!(result.contains("if x < 10"));
|
||||||
|
assert!(result.contains("__goto_label = \"start\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transform_line_not_goto() {
|
||||||
|
assert_eq!(transform_line("TALK \"Hello\""), "TALK \"Hello\"");
|
||||||
|
assert_eq!(transform_line("x = x + 1"), "x = x + 1");
|
||||||
|
assert_eq!(transform_line("ON ERROR GOTO 0"), "ON ERROR GOTO 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_has_goto_constructs() {
|
||||||
|
assert!(has_goto_constructs("start:\nTALK \"hi\"\nGOTO start"));
|
||||||
|
assert!(has_goto_constructs("IF x > 0 THEN GOTO done"));
|
||||||
|
assert!(!has_goto_constructs("TALK \"hello\"\nWAIT 1"));
|
||||||
|
assert!(!has_goto_constructs("ON ERROR GOTO 0")); // This is special, not regular GOTO
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transform_goto_simple() {
|
||||||
|
let input = r#"start:
|
||||||
|
TALK "Hello"
|
||||||
|
x = x + 1
|
||||||
|
IF x < 3 THEN GOTO start
|
||||||
|
TALK "Done""#;
|
||||||
|
|
||||||
|
let output = transform_goto(input);
|
||||||
|
|
||||||
|
assert!(output.contains("__goto_label"));
|
||||||
|
assert!(output.contains("while"));
|
||||||
|
assert!(output.contains("\"start\""));
|
||||||
|
assert!(output.contains("WARNING"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transform_goto_no_goto() {
|
||||||
|
let input = "TALK \"Hello\"\nTALK \"World\"";
|
||||||
|
let output = transform_goto(input);
|
||||||
|
assert_eq!(output, input); // Unchanged
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transform_goto_multiple_labels() {
|
||||||
|
let input = r#"start:
|
||||||
|
TALK "Start"
|
||||||
|
GOTO middle
|
||||||
|
middle:
|
||||||
|
TALK "Middle"
|
||||||
|
GOTO done
|
||||||
|
done:
|
||||||
|
TALK "Done""#;
|
||||||
|
|
||||||
|
let output = transform_goto(input);
|
||||||
|
|
||||||
|
assert!(output.contains("\"start\""));
|
||||||
|
assert!(output.contains("\"middle\""));
|
||||||
|
assert!(output.contains("\"done\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_infinite_loop_protection() {
|
||||||
|
let output = transform_goto("loop:\nGOTO loop");
|
||||||
|
assert!(output.contains("__goto_max_iterations"));
|
||||||
|
assert!(output.contains("throw"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,9 @@ use crate::shared::state::AppState;
|
||||||
use diesel::ExpressionMethods;
|
use diesel::ExpressionMethods;
|
||||||
use diesel::QueryDsl;
|
use diesel::QueryDsl;
|
||||||
use diesel::RunQueryDsl;
|
use diesel::RunQueryDsl;
|
||||||
use log::warn;
|
use log::{trace, warn};
|
||||||
|
|
||||||
|
pub mod goto_transform;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
@ -313,6 +315,16 @@ impl BasicCompiler {
|
||||||
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||||
let bot_uuid = bot_id;
|
let bot_uuid = bot_id;
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
|
|
||||||
|
// Transform GOTO/labels into state machine if present
|
||||||
|
// WARNING: GOTO is supported but event-driven ON patterns are recommended
|
||||||
|
let source = if goto_transform::has_goto_constructs(source) {
|
||||||
|
trace!("GOTO constructs detected, transforming to state machine");
|
||||||
|
goto_transform::transform_goto(source)
|
||||||
|
} else {
|
||||||
|
source.to_string()
|
||||||
|
};
|
||||||
|
let source = source.as_str();
|
||||||
let mut has_schedule = false;
|
let mut has_schedule = false;
|
||||||
let mut _has_webhook = false;
|
let mut _has_webhook = false;
|
||||||
let script_name = Path::new(source_path)
|
let script_name = Path::new(source_path)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,50 @@
|
||||||
|
|
||||||
Pre-built bot packages for common business use cases. Templates are organized by category for easy discovery.
|
Pre-built bot packages for common business use cases. Templates are organized by category for easy discovery.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Template List (Flat Reference)
|
||||||
|
|
||||||
|
| # | Template | Category | Folder | Key Features |
|
||||||
|
|---|----------|----------|--------|--------------|
|
||||||
|
| 1 | Default | Core | `default.gbai` | Minimal starter bot |
|
||||||
|
| 2 | Template | Core | `template.gbai` | Reference implementation |
|
||||||
|
| 3 | AI Search | Search | `ai-search.gbai` | QR codes, document search |
|
||||||
|
| 4 | Announcements | Communications | `announcements.gbai` | Company news, multiple KB |
|
||||||
|
| 5 | Analytics Dashboard | Platform | `analytics-dashboard.gbai` | Metrics, Reports |
|
||||||
|
| 6 | Backup | Platform | `backup.gbai` | Server backup scripts |
|
||||||
|
| 7 | Bank | Finance | `bank.gbai` | Banking services |
|
||||||
|
| 8 | BI | Platform | `bi.gbai` | Dashboards, role separation |
|
||||||
|
| 9 | Bling | Integration | `bling.gbai` | Bling ERP integration |
|
||||||
|
| 10 | Broadcast | Communications | `broadcast.gbai` | Mass messaging |
|
||||||
|
| 11 | Crawler | Search | `crawler.gbai` | Web indexing |
|
||||||
|
| 12 | CRM | Sales | `sales/crm.gbai` | Customer management |
|
||||||
|
| 13 | Attendance CRM | Sales | `sales/attendance-crm.gbai` | Event attendance tracking |
|
||||||
|
| 14 | Marketing | Sales | `sales/marketing.gbai` | Campaign tools |
|
||||||
|
| 15 | Education | Education | `edu.gbai` | Course management |
|
||||||
|
| 16 | ERP | Operations | `erp.gbai` | Process automation |
|
||||||
|
| 17 | HIPAA Medical | Compliance | `compliance/hipaa-medical.gbai` | HIPAA, HITECH |
|
||||||
|
| 18 | Privacy | Compliance | `compliance/privacy.gbai` | LGPD, GDPR, CCPA |
|
||||||
|
| 19 | Law | Legal | `law.gbai` | Document templates |
|
||||||
|
| 20 | LLM Server | AI | `llm-server.gbai` | Model hosting |
|
||||||
|
| 21 | LLM Tools | AI | `llm-tools.gbai` | TOOL-based LLM examples |
|
||||||
|
| 22 | Store | E-commerce | `store.gbai` | Product catalog |
|
||||||
|
| 23 | Talk to Data | Platform | `talk-to-data.gbai` | Natural language SQL |
|
||||||
|
| 24 | WhatsApp | Messaging | `whatsapp.gbai` | WhatsApp Business |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Categories
|
## Categories
|
||||||
|
|
||||||
|
### `/sales`
|
||||||
|
Customer relationship and marketing templates.
|
||||||
|
|
||||||
|
| Template | Description | Features |
|
||||||
|
|----------|-------------|----------|
|
||||||
|
| `crm.gbai` | Full CRM system | Leads, Contacts, Accounts, Opportunities |
|
||||||
|
| `marketing.gbai` | Marketing automation | Campaigns, Lead capture, Email sequences |
|
||||||
|
| `attendance-crm.gbai` | Event attendance | Check-ins, Tracking |
|
||||||
|
|
||||||
### `/compliance`
|
### `/compliance`
|
||||||
Privacy and regulatory compliance templates.
|
Privacy and regulatory compliance templates.
|
||||||
|
|
||||||
|
|
@ -12,13 +54,29 @@ Privacy and regulatory compliance templates.
|
||||||
| `privacy.gbai` | Data subject rights portal | LGPD, GDPR, CCPA |
|
| `privacy.gbai` | Data subject rights portal | LGPD, GDPR, CCPA |
|
||||||
| `hipaa-medical.gbai` | Healthcare privacy management | HIPAA, HITECH |
|
| `hipaa-medical.gbai` | Healthcare privacy management | HIPAA, HITECH |
|
||||||
|
|
||||||
### `/sales`
|
### `/platform`
|
||||||
Customer relationship and marketing templates.
|
Platform administration and analytics templates.
|
||||||
|
|
||||||
| Template | Description | Features |
|
| Template | Description | Features |
|
||||||
|----------|-------------|----------|
|
|----------|-------------|----------|
|
||||||
| `crm.gbai` | Full CRM system | Leads, Contacts, Accounts, Opportunities, Activities |
|
| `analytics-dashboard.gbai` | Platform analytics bot | Metrics, Reports, AI insights |
|
||||||
| `marketing.gbai` | Marketing automation | Campaigns, Lead capture, Email sequences |
|
| `bi.gbai` | Business intelligence | Dashboards, role separation |
|
||||||
|
| `backup.gbai` | Backup automation | Server backup scripts |
|
||||||
|
| `talk-to-data.gbai` | Data queries | Natural language SQL |
|
||||||
|
|
||||||
|
### `/finance`
|
||||||
|
Financial services templates.
|
||||||
|
|
||||||
|
| Template | Description | Features |
|
||||||
|
|----------|-------------|----------|
|
||||||
|
| `bank.gbai` | Banking services | Account management |
|
||||||
|
|
||||||
|
### `/integration`
|
||||||
|
External API and service integrations.
|
||||||
|
|
||||||
|
| Template | Description | APIs |
|
||||||
|
|----------|-------------|------|
|
||||||
|
| `bling.gbai` | Bling ERP | Brazilian ERP integration |
|
||||||
|
|
||||||
### `/productivity`
|
### `/productivity`
|
||||||
Office and personal productivity templates.
|
Office and personal productivity templates.
|
||||||
|
|
@ -26,29 +84,14 @@ Office and personal productivity templates.
|
||||||
| Template | Description | Features |
|
| Template | Description | Features |
|
||||||
|----------|-------------|----------|
|
|----------|-------------|----------|
|
||||||
| `office.gbai` | Office automation | Document management, Scheduling |
|
| `office.gbai` | Office automation | Document management, Scheduling |
|
||||||
| `reminder.gbai` | Reminder and notification system | Scheduled alerts, Follow-ups |
|
| `reminder.gbai` | Reminder system | Scheduled alerts, Follow-ups |
|
||||||
|
|
||||||
### `/platform`
|
|
||||||
Platform administration and analytics templates.
|
|
||||||
|
|
||||||
| Template | Description | Features |
|
|
||||||
|----------|-------------|----------|
|
|
||||||
| `analytics.gbai` | Platform analytics bot | Metrics, Reports, AI insights |
|
|
||||||
|
|
||||||
### `/integration`
|
|
||||||
External API and service integrations.
|
|
||||||
|
|
||||||
| Template | Description | APIs |
|
|
||||||
|----------|-------------|------|
|
|
||||||
| `api-client.gbai` | REST API client examples | Various |
|
|
||||||
| `public-apis.gbai` | Public API integrations | Weather, News, etc. |
|
|
||||||
|
|
||||||
### `/hr`
|
### `/hr`
|
||||||
Human resources templates.
|
Human resources templates.
|
||||||
|
|
||||||
| Template | Description | Features |
|
| Template | Description | Features |
|
||||||
|----------|-------------|----------|
|
|----------|-------------|----------|
|
||||||
| `employee-mgmt.gbai` | Employee management | Directory, Onboarding |
|
| `employees.gbai` | Employee management | Directory, Onboarding |
|
||||||
|
|
||||||
### `/it`
|
### `/it`
|
||||||
IT service management templates.
|
IT service management templates.
|
||||||
|
|
@ -64,14 +107,6 @@ Healthcare-specific templates.
|
||||||
|----------|-------------|----------|
|
|----------|-------------|----------|
|
||||||
| `patient-comm.gbai` | Patient communication | Appointments, Reminders |
|
| `patient-comm.gbai` | Patient communication | Appointments, Reminders |
|
||||||
|
|
||||||
### `/finance`
|
|
||||||
Financial services templates.
|
|
||||||
|
|
||||||
| Template | Description | Features |
|
|
||||||
|----------|-------------|----------|
|
|
||||||
| `bank.gbai` | Banking services | Account management |
|
|
||||||
| `finance.gbai` | Financial operations | Invoicing, Payments |
|
|
||||||
|
|
||||||
### `/nonprofit`
|
### `/nonprofit`
|
||||||
Nonprofit organization templates.
|
Nonprofit organization templates.
|
||||||
|
|
||||||
|
|
@ -85,6 +120,7 @@ Core and utility templates.
|
||||||
| Template | Description |
|
| Template | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `default.gbai` | Starter template |
|
| `default.gbai` | Starter template |
|
||||||
|
| `template.gbai` | Template for creating templates |
|
||||||
| `ai-search.gbai` | AI-powered document search |
|
| `ai-search.gbai` | AI-powered document search |
|
||||||
| `announcements.gbai` | Company announcements |
|
| `announcements.gbai` | Company announcements |
|
||||||
| `backup.gbai` | Backup automation |
|
| `backup.gbai` | Backup automation |
|
||||||
|
|
@ -96,10 +132,10 @@ Core and utility templates.
|
||||||
| `llm-server.gbai` | LLM server management |
|
| `llm-server.gbai` | LLM server management |
|
||||||
| `llm-tools.gbai` | LLM tool definitions |
|
| `llm-tools.gbai` | LLM tool definitions |
|
||||||
| `store.gbai` | E-commerce |
|
| `store.gbai` | E-commerce |
|
||||||
| `talk-to-data.gbai` | Natural language data queries |
|
|
||||||
| `template.gbai` | Template for creating templates |
|
|
||||||
| `whatsapp.gbai` | WhatsApp-specific features |
|
| `whatsapp.gbai` | WhatsApp-specific features |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Template Structure
|
## Template Structure
|
||||||
|
|
||||||
Each `.gbai` template follows this structure:
|
Each `.gbai` template follows this structure:
|
||||||
|
|
@ -109,7 +145,7 @@ template-name.gbai/
|
||||||
├── README.md # Template documentation
|
├── README.md # Template documentation
|
||||||
├── template-name.gbdialog/ # BASIC dialog scripts
|
├── template-name.gbdialog/ # BASIC dialog scripts
|
||||||
│ ├── start.bas # Entry point
|
│ ├── start.bas # Entry point
|
||||||
│ └── *.bas # Additional dialogs
|
│ └── *.bas # Additional dialogs (auto-discovered as TOOLs)
|
||||||
├── template-name.gbot/ # Bot configuration
|
├── template-name.gbot/ # Bot configuration
|
||||||
│ └── config.csv # Settings
|
│ └── config.csv # Settings
|
||||||
├── template-name.gbkb/ # Knowledge base (optional)
|
├── template-name.gbkb/ # Knowledge base (optional)
|
||||||
|
|
@ -119,6 +155,55 @@ template-name.gbai/
|
||||||
└── index.html
|
└── index.html
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Event-Driven Patterns
|
||||||
|
|
||||||
|
Templates should use **ON** triggers instead of polling loops:
|
||||||
|
|
||||||
|
```basic
|
||||||
|
' ❌ OLD WAY - Polling (avoid)
|
||||||
|
mainLoop:
|
||||||
|
leads = FIND "leads", "processed = false"
|
||||||
|
WAIT 5
|
||||||
|
GOTO mainLoop
|
||||||
|
|
||||||
|
' ✅ NEW WAY - Event-Driven
|
||||||
|
ON INSERT OF "leads"
|
||||||
|
lead = GET LAST "leads"
|
||||||
|
score = SCORE LEAD lead
|
||||||
|
TALK TO "whatsapp:" + sales_phone, "New lead: " + lead.name
|
||||||
|
END ON
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TOOL-Based LLM Integration
|
||||||
|
|
||||||
|
Every `.bas` file with `PARAM` and `DESCRIPTION` becomes an LLM-invokable tool:
|
||||||
|
|
||||||
|
```basic
|
||||||
|
' score-lead.bas
|
||||||
|
PARAM email AS STRING LIKE "john@company.com" DESCRIPTION "Lead email"
|
||||||
|
PARAM name AS STRING LIKE "John Smith" DESCRIPTION "Lead name"
|
||||||
|
|
||||||
|
DESCRIPTION "Score a new lead. Use when user mentions a prospect."
|
||||||
|
|
||||||
|
lead = NEW OBJECT
|
||||||
|
lead.email = email
|
||||||
|
lead.name = name
|
||||||
|
|
||||||
|
score = AI SCORE LEAD lead
|
||||||
|
|
||||||
|
IF score.status = "hot" THEN
|
||||||
|
TALK TO "whatsapp:+5511999887766", "🔥 Hot lead: " + name
|
||||||
|
END IF
|
||||||
|
|
||||||
|
TALK "Lead scored: " + score.score + "/100"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From Console
|
### From Console
|
||||||
|
|
@ -141,6 +226,8 @@ Copy the template folder to your bot's packages directory:
|
||||||
cp -r templates/sales/crm.gbai /path/to/your/bot/packages/
|
cp -r templates/sales/crm.gbai /path/to/your/bot/packages/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Creating Custom Templates
|
## Creating Custom Templates
|
||||||
|
|
||||||
1. Copy `template.gbai` as a starting point
|
1. Copy `template.gbai` as a starting point
|
||||||
|
|
@ -148,16 +235,22 @@ cp -r templates/sales/crm.gbai /path/to/your/bot/packages/
|
||||||
3. Update internal folder names to match
|
3. Update internal folder names to match
|
||||||
4. Edit `config.csv` with your bot settings
|
4. Edit `config.csv` with your bot settings
|
||||||
5. Create dialog scripts in the `.gbdialog` folder
|
5. Create dialog scripts in the `.gbdialog` folder
|
||||||
6. Add documentation in `README.md`
|
6. Use **ON** triggers instead of polling loops
|
||||||
|
7. Add `PARAM` and `DESCRIPTION` to make scripts LLM-invokable
|
||||||
|
8. Add documentation in `README.md`
|
||||||
|
|
||||||
### Template Best Practices
|
### Template Best Practices
|
||||||
|
|
||||||
- Use `HEAR AS` for typed input validation
|
- Use `ON` for event-driven automation
|
||||||
- Use spaces in keywords (e.g., `SET BOT MEMORY`, not `SET_BOT_MEMORY`)
|
- Use `TALK TO` for multi-channel notifications
|
||||||
|
- Use `LLM` for intelligent decision-making
|
||||||
|
- Use `SCORE LEAD` / `AI SCORE LEAD` for lead qualification
|
||||||
|
- Create `.bas` files with `DESCRIPTION` for LLM tool discovery
|
||||||
- Log activities for audit trails
|
- Log activities for audit trails
|
||||||
- Include error handling
|
- Include error handling
|
||||||
- Document all configuration options
|
- Document all configuration options
|
||||||
- Provide example conversations
|
|
||||||
|
---
|
||||||
|
|
||||||
## Contributing Templates
|
## Contributing Templates
|
||||||
|
|
||||||
|
|
@ -169,6 +262,8 @@ cp -r templates/sales/crm.gbai /path/to/your/bot/packages/
|
||||||
- Updated category README
|
- Updated category README
|
||||||
- Entry in this document
|
- Entry in this document
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
All templates are licensed under AGPL-3.0 as part of General Bots.
|
All templates are licensed under AGPL-3.0 as part of General Bots.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue