17 个版本

new 0.0.17-alpha2024 年 8 月 12 日
0.0.16-alpha2024 年 8 月 11 日
0.0.8-alpha2024 年 3 月 10 日
0.0.3-alpha2024 年 2 月 26 日
0.0.1-alpha2020 年 4 月 22 日

#64 in 编程语言

Download history 1/week @ 2024-05-15 5/week @ 2024-05-22 6/week @ 2024-06-05 8/week @ 2024-07-03 39/week @ 2024-07-24 303/week @ 2024-07-31 757/week @ 2024-08-07

1,099 每月下载量
6 个 crate(5 个直接) 中使用

自定义许可

465KB
11K 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,
        },
    }
}
// ...

进一步阅读

Browser Garnish 项目是 演示站点 使用的 WebAssembly 库。通过演示并查看源代码,可以说明这一切是如何相互关联的。

API 文档 - 提供详细描述和更多示例。(目前仍在开发中)

依赖项

~46–255KB