#garnish #language #variant #scripting-language #run-time #list #embed

garnish_lang_simple_data

使用简单变体列表为 garnish 运行时实现数据

16 个版本

0.0.17-alpha2024年8月12日
0.0.16-alpha2024年8月11日
0.0.8-alpha2024年3月10日
0.0.3-alpha2024年2月26日

#198 in 数据结构

Download history 11/week @ 2024-05-21 1/week @ 2024-05-28 5/week @ 2024-06-04 5/week @ 2024-06-11 1/week @ 2024-06-25 16/week @ 2024-07-02 2/week @ 2024-07-16 2/week @ 2024-07-23 235/week @ 2024-07-30 725/week @ 2024-08-06

964 每月下载量
用于 5 个crate(4个直接使用)

自定义许可协议

195KB
4.5K SLoC

Garnish Core   构建状态

核心库,用于嵌入 garnish 语言。这些是您将需要添加 Garnish 脚本到应用程序中的库。

如果您想了解 Garnish 语言,请访问 演示网站

此仓库包含以下库crate。即使没有对库进行更改,每个库的版本号也保持同步。

特质

Traits Crate Traits Docs

包含用于其他核心库的基础特质、结构体、枚举等。

简单数据

Simple Data Crate Simple Data Docs

使用标准Rust类型和数据结构实现的 GarnishData

运行时

Runtime Crate Traits Docs

实现 GarnishRuntime,它执行给定数据对象上的指令。

编译器

Compiler Crate Compiler Docs

包含对输入字符串进行词法分析和解析以及将指令集构建到数据对象的函数。

Garnish Lang

Lang Crate Lang Docs

用于上述四个库的便利单依赖项。

用法

以下示例使用 Garnish Lang crate。如果您计划单独导入这四个库,只需相应地调整 use 语句即可。

基本编译和执行

仅使用核心库,这是一个三步过程,并且需要为第三步创建一个 GarnishData 对象。

use garnish_lang::compiler::lex::{lex, LexerToken};
use garnish_lang::compiler::parse::{parse, ParseResult};
use garnish_lang::compiler::build::build_with_data;
use garnish_lang::simple::SimpleGarnishData;

const INPUT: &str = "5 + 5";

fn main() -> Result<(), String> {
    let tokens: Vec<LexerToken> = lex(input).or_else(|e| Err(e.get_message().clone()))?;

    let parse_result: ParseResult = parse(&tokens).or_else(|e| Err(e.get_message().clone()))?;

    let mut data = SimpleGarnishData::new();

    build_with_data(parse_result.get_root(), parse_result.get_nodes().clone(), &mut data)
        .or_else(|e| Err(e.get_message().clone()))?;
    
    let mut runtime = SimpleGarnishRuntime::new(data);
    
    // SimpleGarnishRuntime only provides method to execute instructions 1 at a time, 
    // so we loop until finished
    loop {
        // this None argument is where a GarnishContext would be passed
        match runtime.execute_current_instruction(None) {
            Err(e) => {
                return Err(e.get_message().clone());
            }
            Ok(data) => match data.get_state() {
                SimpleRuntimeState::Running => (),
                SimpleRuntimeState::End => break,
            },
        }
    }
    
    // Result of an execution is a data objects current value
    runtime.get_data().get_current_value().and_then(|v| {
        // get_raw_data is not a trait member of GarnishData, 
        // but a convenience function of SimpleGarnishData
        println!("Result: {:?}", runtime.get_data().get_raw_data(v))
    });
    
    Ok(())
}

使用上下文

在执行期间提供 GarnishContext 对象是扩展脚本功能的一种方法。这可以提供环境变量、访问数据库的方法或自定义操作。

以下示例向脚本提供两项内容。PI 的常量值以及执行三角函数正弦的方法。

use std::collections::HashMap;
use garnish_lang::{GarnishContext, GarnishData, RuntimeError};
use garnish_lang::simple::{
    DataError, 
    SimpleData, 
    SimpleGarnishData, 
    SimpleNumber, 
    symbol_value
};

const MATH_FUNCTION_SINE: usize = 1;

pub struct MathContext {
    symbol_to_data: HashMap<u64, SimpleData>
}

impl MathContext {
    pub fn new() -> Self {
        let mut symbol_to_data = HashMap::new();
        symbol_to_data.insert(
            symbol_value("Math::PI"), 
            SimpleData::Number(SimpleNumber::Float(std::f64::consts::PI))
        );
        symbol_to_data.insert(
            symbol_value("sin"), 
            SimpleData::External(MATH_FUNCTION_SINE)
        );

        BrowserContext {
            symbol_to_expression: HashMap::new(),
            symbol_to_data
        }
    }
}

impl GarnishContext<SimpleGarnishData> for MathContext {
    // This method is called when ever a script has an unresolved identifier during runtime
    fn resolve(&mut self, symbol: u64, data: &mut SimpleGarnishData) 
        -> Result<bool, RuntimeError<DataError>> {
        // lookup given symbol to see if we have a value for it
        // returning true tells runtime that the symbol was resolved 
        //   and not to do any more checks
        // returning false will let the runtime check additional resolve methods, 
        //   resulting in a Unit value if nothing resolves it
        match self.symbol_to_data.get(&symbol) {
            Some(v) => match v {
                SimpleData::External(n) => {
                    // using GarnishData trait methods, add_* for each GarnishDataType
                    data.add_external(*n).and_then(|addr| data.push_register(addr))?;
                    Ok(true)
                },
                SimpleData::Number(n) => {
                    data.add_number(*n).and_then(|addr| data.push_register(addr))?;
                    Ok(true)
                },
                _ => Ok(false)
            }
            None => Ok(false)
        }
    }
    
    // This method is called when ever an External type value 
    // is used with Garnish's 'apply' type operations
    fn apply(
        &mut self,
        external_value: usize,
        input_addr: usize,
        data: &mut SimpleGarnishData,
    ) -> Result<bool, RuntimeError<DataError>> {
        // check that the external value given is actually supported
        if external_value == MATH_FUNCTION_SINE {
            // using some non trait methods, whether to use trait methods or 
            // implementation specific methods will depend on your use case
            let new_data = data.get_raw_data(input_addr).and_then(|d| Some(match d {
                SimpleData::Number(num) => SimpleData::Number(SimpleNumber::Float(match num {
                    SimpleNumber::Integer(n) => f64::sin(n as f64),
                    SimpleNumber::Float(f) => f64::sin(f),
                })),
                // sin function only supports numbers, all other values result in Unit
                _ => SimpleData::Unit
            })).ok_or(
                DataError::from("Failed to retrieve data during external apply 'sin'"
                    .to_string())
            )?;

            // need to add new data 
            // then push its address to registers for next operation to use
            // failure to not push expected values and still returning true, 
            //   could cause script to fail due to empty registers
            let addr = data.get_data().len();
            data.get_data_mut().push(new_data);
            data.push_register(addr)?;

            Ok(true)
        } else {
            // return value signifies same as resolve method's return value
            Ok(false)
        }
    }
}

现在我们已经实现了 GarnishContext,我们可以将其传递给 execute_current_instruction 方法,而不是传递 None。

// ...
let mut runtime = SimpleGarnishRuntime::new(data);
let mut context = MathContext::new();

loop {
    // add new context object
    match runtime.execute_current_instruction(Some(&mut context)) {
        Err(e) => {
            return Err(e.get_message().clone());
        }
        Ok(data) => match data.get_state() {
            SimpleRuntimeState::Running => (),
            SimpleRuntimeState::End => break,
        },
    }
}
// ...

进一步阅读

浏览器 Garnish 项目是 演示网站 所使用的 WebAssembly 库。通过演示和查看源代码可以说明它们是如何相互关联的。

API 文档 - 包含完整描述和更多示例。(目前仍在开发中)

依赖项

~175KB