#garnish #language #embed #documenting #compiler #traits #re-exporting

garnish_lang

一个用于重新导出和记录所有Garnish核心库的单库

15次发布

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

#345 in 数据结构

Download history 3/week @ 2024-07-01 36/week @ 2024-07-29 787/week @ 2024-08-05

823 每月下载量
hypertext_garnish 中使用

自定义许可证

740KB
17K SLoC

Garnish Core — 构建状态

嵌入Garnish语言所需的核心库。这些是你需要将Garnish脚本添加到应用程序中所需的内容。

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

此存储库包含以下库crates。即使没有对库进行更改,每个库的版本号也会与其他库保持同步。

特质

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–275KB