use full hash length as name, improve docs

This commit is contained in:
Moritz Hölting 2024-09-27 16:13:13 +02:00
parent c1a8bc8577
commit 804f314df7
15 changed files with 124 additions and 32 deletions

View File

@ -5,7 +5,7 @@ edition = "2021"
authors = ["Moritz Hölting <moritz@hoelting.dev>"] authors = ["Moritz Hölting <moritz@hoelting.dev>"]
categories = ["compilers"] categories = ["compilers"]
description = "ShulkerScript language implementation with compiler" description = "Shulkerscript language implementation with compiler"
repository = "https://github.com/moritz-hoelting/shulkerscript-lang" repository = "https://github.com/moritz-hoelting/shulkerscript-lang"
readme = "README.md" readme = "README.md"
license = "MIT" license = "MIT"
@ -13,11 +13,12 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["fs_access", "lua", "shulkerbox"] default = ["fs_access", "lua", "shulkerbox", "zip"]
fs_access = ["shulkerbox?/fs_access"] fs_access = ["shulkerbox?/fs_access"]
lua = ["dep:mlua"] lua = ["dep:mlua"]
serde = ["dep:serde", "shulkerbox?/serde"] serde = ["dep:serde", "shulkerbox?/serde"]
shulkerbox = ["dep:shulkerbox"] shulkerbox = ["dep:shulkerbox"]
zip = ["shulkerbox?/zip"]
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
path-absolutize = { version = "3.1.1", features = ["use_unix_paths_on_wasm"] } path-absolutize = { version = "3.1.1", features = ["use_unix_paths_on_wasm"] }
@ -25,7 +26,7 @@ path-absolutize = { version = "3.1.1", features = ["use_unix_paths_on_wasm"] }
[dependencies] [dependencies]
chksum-md5 = "0.0.0" chksum-md5 = "0.0.0"
colored = "2.1.0" colored = "2.1.0"
derive_more = { version = "0.99.17", default-features = false, features = ["deref", "from", "deref_mut"] } derive_more = { version = "1.0.0", default-features = false, features = ["deref", "deref_mut", "from"] }
enum-as-inner = "0.6.0" enum-as-inner = "0.6.0"
getset = "0.1.2" getset = "0.1.2"
itertools = "0.13.0" itertools = "0.13.0"
@ -33,9 +34,9 @@ mlua = { version = "0.9.7", features = ["lua54", "vendored"], optional = true }
path-absolutize = "3.1.1" path-absolutize = "3.1.1"
pathdiff = "0.2.1" pathdiff = "0.2.1"
serde = { version = "1.0.197", features = ["derive", "rc"], optional = true } serde = { version = "1.0.197", features = ["derive", "rc"], optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", default-features = false, optional = true, rev = "60458e6b2d719278c4fe090cb2759e123a3f86d2" } shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", default-features = false, optional = true, rev = "a16f1ce105959b37bd5776476352b92905ff12d1" }
strsim = "0.11.1" strsim = "0.11.1"
strum = { version = "0.26.2", features = ["derive"] } strum = { version = "0.26.2", features = ["derive"] }
strum_macros = "0.26.2" strum_macros = "0.26.4"
thiserror = "1.0.58" thiserror = "1.0.58"
tracing = "0.1.40" tracing = "0.1.40"

View File

@ -1,6 +1,6 @@
# ShulkerScript Language # Shulkerscript Language
ShulkerScript is a simple, easy-to-use scripting language for Minecraft datapacks. It is designed to be easy to learn and use, while still being powerful enough to create complex scripts, while not being hindered by Minecraft command limitations. Shulkerscript is a simple, easy-to-use scripting language for Minecraft datapacks. It is designed to be easy to learn and use, while still being powerful enough to create complex scripts, while not being hindered by Minecraft command limitations.
## Usage ## Usage

View File

@ -1,4 +1,4 @@
# Grammar of the ShulkerScript language # Grammar of the Shulkerscript language
## Table of contents ## Table of contents

View File

@ -82,11 +82,11 @@ pub struct Error {
} }
impl Error { impl Error {
/// Creates a new [`FileProviderError`] from a known kind of error as well as an /// Creates a new [`Error`] from a known kind of error as well as an
/// arbitrary error payload. /// arbitrary error payload.
/// ///
/// The `error` argument is an arbitrary /// The `error` argument is an arbitrary
/// payload which will be contained in this [`FileProviderError`]. /// payload which will be contained in this [`Error`].
/// ///
/// Note that this function allocates memory on the heap. /// Note that this function allocates memory on the heap.
/// If no extra payload is required, use the `From` conversion from /// If no extra payload is required, use the `From` conversion from
@ -101,9 +101,9 @@ impl Error {
} }
} }
/// Creates a new [`FileProviderError`] from an arbitrary error payload. /// Creates a new [`Error`] from an arbitrary error payload.
/// ///
/// It is a shortcut for [`FileProviderError::new`] /// It is a shortcut for [`Error::new`]
/// with [`std::io::ErrorKind::Other`]. /// with [`std::io::ErrorKind::Other`].
pub fn other<E>(error: E) -> Self pub fn other<E>(error: E) -> Self
where where
@ -114,14 +114,14 @@ impl Error {
/// Returns a reference to the inner error wrapped by this error (if any). /// Returns a reference to the inner error wrapped by this error (if any).
/// ///
/// If this [`FileProviderError`] was constructed via [`Self::new`] then this function will /// If this [`Error`] was constructed via [`Self::new`] then this function will
/// return [`Some`], otherwise it will return [`None`]. /// return [`Some`], otherwise it will return [`None`].
#[must_use] #[must_use]
pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> { pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
return self.error.as_deref(); return self.error.as_deref();
} }
/// Consumes the [`FileProviderError`], returning its inner error (if any). /// Consumes the [`Error`], returning its inner error (if any).
/// ///
/// If this [`Error`] was constructed via [`Self::new`] then this function will /// If this [`Error`] was constructed via [`Self::new`] then this function will
/// return [`Some`], otherwise it will return [`None`]. /// return [`Some`], otherwise it will return [`None`].

View File

@ -1,4 +1,4 @@
//! The base module contains the core functionality of the `ShulkerScript` language. //! The base module contains the core functionality of the `Shulkerscript` language.
pub mod source_file; pub mod source_file;

View File

@ -86,7 +86,7 @@ impl SourceFile {
/// Load the source file from the given file path. /// Load the source file from the given file path.
/// ///
/// # Errors /// # Errors
/// - [`Error::IoError`]: Error occurred when reading the file contents. /// - [`Error::FileProviderError`]: Error occurred when reading the file contents.
pub fn load( pub fn load(
path: &Path, path: &Path,
identifier: String, identifier: String,

View File

@ -1,6 +1,6 @@
//! The `ShulkerScript` language. //! The `Shulkerscript` language.
//! //!
//! `ShulkerScript` is a simple, imperative scripting language for creating Minecraft data packs. //! `Shulkerscript` is a simple, imperative scripting language for creating Minecraft data packs.
#![deny( #![deny(
missing_debug_implementations, missing_debug_implementations,
@ -29,7 +29,9 @@ use shulkerbox::{datapack::Datapack, virtual_fs::VFolder};
use crate::lexical::token_stream::TokenStream; use crate::lexical::token_stream::TokenStream;
/// The version of the `ShulkerScript` language. /// The version of the `Shulkerscript` language.
///
/// Matches the version of this [`crate`].
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// Converts the given source code to tokens and returns a token stream. /// Converts the given source code to tokens and returns a token stream.

View File

@ -1,4 +1,4 @@
//! This module contains the syntax tree and parser for the `ShulkerScript` language. //! This module contains the syntax tree and parser for the `Shulkerscript` language.
pub mod error; pub mod error;
pub mod parser; pub mod parser;

View File

@ -23,11 +23,13 @@ use crate::{
}, },
}; };
/// Condition that is viewed as a single entity during precedence parsing.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
/// PrimaryCondition: /// PrimaryCondition:
/// ConditionalPrefix /// UnaryCondition
/// | ParenthesizedCondition /// | ParenthesizedCondition
/// | StringLiteral /// | StringLiteral
/// ``` /// ```
@ -35,7 +37,7 @@ use crate::{
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum PrimaryCondition { pub enum PrimaryCondition {
Prefix(ConditionalPrefix), Unary(UnaryCondition),
Parenthesized(ParenthesizedCondition), Parenthesized(ParenthesizedCondition),
StringLiteral(StringLiteral), StringLiteral(StringLiteral),
} }
@ -43,13 +45,15 @@ pub enum PrimaryCondition {
impl SourceElement for PrimaryCondition { impl SourceElement for PrimaryCondition {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Self::Prefix(prefix) => prefix.span(), Self::Unary(unary) => unary.span(),
Self::Parenthesized(parenthesized) => parenthesized.span(), Self::Parenthesized(parenthesized) => parenthesized.span(),
Self::StringLiteral(literal) => literal.span(), Self::StringLiteral(literal) => literal.span(),
} }
} }
} }
/// Condition that is composed of two conditions and a binary operator.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -88,6 +92,8 @@ impl BinaryCondition {
} }
} }
/// Operator that is used to combine two conditions.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -128,6 +134,8 @@ impl SourceElement for ConditionalBinaryOperator {
} }
} }
/// Condition that is enclosed in parentheses.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -165,6 +173,8 @@ impl SourceElement for ParenthesizedCondition {
} }
} }
/// Operator that is used to prefix a condition.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -185,16 +195,18 @@ impl SourceElement for ConditionalPrefixOperator {
} }
} }
/// Condition that is prefixed by an operator.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ```ebnf /// ```ebnf
/// ConditionalPrefix: /// UnaryCondition:
/// ConditionalPrefixOperator PrimaryCondition /// ConditionalPrefixOperator PrimaryCondition
/// ; /// ;
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ConditionalPrefix { pub struct UnaryCondition {
/// The operator of the prefix. /// The operator of the prefix.
#[get = "pub"] #[get = "pub"]
operator: ConditionalPrefixOperator, operator: ConditionalPrefixOperator,
@ -203,12 +215,12 @@ pub struct ConditionalPrefix {
operand: Box<PrimaryCondition>, operand: Box<PrimaryCondition>,
} }
impl SourceElement for ConditionalPrefix { impl SourceElement for UnaryCondition {
fn span(&self) -> Span { fn span(&self) -> Span {
self.operator.span().join(&self.operand.span()).unwrap() self.operator.span().join(&self.operand.span()).unwrap()
} }
} }
impl ConditionalPrefix { impl UnaryCondition {
/// Dissolves the conditional prefix into its components /// Dissolves the conditional prefix into its components
#[must_use] #[must_use]
pub fn dissolve(self) -> (ConditionalPrefixOperator, PrimaryCondition) { pub fn dissolve(self) -> (ConditionalPrefixOperator, PrimaryCondition) {
@ -216,6 +228,8 @@ impl ConditionalPrefix {
} }
} }
/// Represents a condition in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -331,7 +345,7 @@ impl<'a> Parser<'a> {
let operand = Box::new(self.parse_primary_condition(handler)?); let operand = Box::new(self.parse_primary_condition(handler)?);
Ok(PrimaryCondition::Prefix(ConditionalPrefix { Ok(PrimaryCondition::Unary(UnaryCondition {
operator, operator,
operand, operand,
})) }))

View File

@ -22,6 +22,8 @@ use crate::{
use super::{statement::Block, ConnectedList, DelimitedList}; use super::{statement::Block, ConnectedList, DelimitedList};
/// Represents a declaration in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -47,6 +49,8 @@ impl SourceElement for Declaration {
} }
} }
} }
/// Represents an Annotation with optional value.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -99,6 +103,8 @@ impl SourceElement for Annotation {
} }
} }
/// Represents a function declaration in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -176,6 +182,8 @@ impl SourceElement for Function {
} }
} }
/// Represents an import declaration in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -198,6 +206,7 @@ pub struct Import {
semicolon: Punctuation, semicolon: Punctuation,
} }
/// Items to import.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ImportItems { pub enum ImportItems {
@ -228,6 +237,8 @@ impl SourceElement for Import {
} }
} }
/// Represents a tag declaration in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf

View File

@ -22,6 +22,8 @@ use crate::{
use super::ConnectedList; use super::ConnectedList;
/// Represents an expression in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ```ebnf /// ```ebnf
@ -43,6 +45,8 @@ impl SourceElement for Expression {
} }
} }
/// Represents a primary expression in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -68,6 +72,8 @@ impl SourceElement for Primary {
} }
} }
/// Represents a function call in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -101,6 +107,8 @@ impl SourceElement for FunctionCall {
} }
} }
/// Represents a lua code block in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ```ebnf /// ```ebnf

View File

@ -25,6 +25,8 @@ use self::execute_block::ExecuteBlock;
use super::expression::Expression; use super::expression::Expression;
/// Represents a statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -65,6 +67,8 @@ impl SourceElement for Statement {
} }
} }
/// Represents a block in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -103,6 +107,8 @@ impl SourceElement for Block {
} }
} }
/// Represents a run statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -141,6 +147,8 @@ impl Run {
} }
} }
/// Represents a grouping statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -176,6 +184,8 @@ impl SourceElement for Grouping {
} }
} }
/// Represents a statement that ends with a semicolon in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ``` ebnf /// ``` ebnf
/// Semicolon: /// Semicolon:

View File

@ -23,6 +23,8 @@ use crate::{
use super::Block; use super::Block;
/// Represents an execute block statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// ExecuteBlock: /// ExecuteBlock:
@ -51,11 +53,25 @@ impl SourceElement for ExecuteBlock {
} }
} }
/// Represents the head of an execute block statement.
///
/// Syntax Synopsis: /// Syntax Synopsis:
///
/// ```ebnf /// ```ebnf
/// ExecuteBlockHead: /// ExecuteBlockHead:
/// Conditional /// Conditional
/// | Align
/// | Anchored
/// | As /// | As
/// | AsAt
/// | At
/// | Facing
/// | In
/// | On
/// | Positioned
/// | Rotated
/// | Store
/// | Summon
/// ; /// ;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner, From)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner, From)]
@ -96,6 +112,8 @@ impl SourceElement for ExecuteBlockHead {
} }
} }
/// Represents the tail of an execute block statement.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// ExecuteBlockTail: /// ExecuteBlockTail:
@ -122,6 +140,8 @@ impl SourceElement for ExecuteBlockTail {
} }
} }
/// Represents an conditional `if` statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -157,6 +177,8 @@ impl SourceElement for Conditional {
} }
} }
/// Represents an `else` block in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
@ -189,6 +211,8 @@ impl SourceElement for Else {
} }
} }
/// Represents an `as` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ```ebnf /// ```ebnf
@ -234,6 +258,8 @@ impl As {
} }
} }
/// Represents an `align` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Align: /// Align:
@ -278,6 +304,8 @@ impl Align {
} }
} }
/// Represents an `anchored` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Anchored: /// Anchored:
@ -321,6 +349,8 @@ impl Anchored {
} }
} }
/// Represents an `asat` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// AsAt: /// AsAt:
@ -364,6 +394,8 @@ impl AsAt {
} }
} }
/// Represents an `at` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// At: /// At:
@ -407,6 +439,8 @@ impl At {
} }
} }
/// Represents a `facing` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Facing: /// Facing:
@ -450,6 +484,8 @@ impl Facing {
} }
} }
/// Represents an `in` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// In: /// In:
@ -493,6 +529,8 @@ impl In {
} }
} }
/// Represents an `on` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// On: /// On:
@ -536,6 +574,8 @@ impl On {
} }
} }
/// Represents a `positioned` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Positioned: /// Positioned:
@ -579,6 +619,8 @@ impl Positioned {
} }
} }
/// Represents a `rotated` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Rotated: /// Rotated:
@ -622,6 +664,8 @@ impl Rotated {
} }
} }
/// Represents a `store` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Store: /// Store:
@ -665,6 +709,8 @@ impl Store {
} }
} }
/// Represents a `summon` execute statement in the syntax tree.
///
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Summon: /// Summon:

View File

@ -23,7 +23,7 @@ impl From<PrimaryCondition> for DpCondition {
Self::Atom(literal.str_content().to_string()) Self::Atom(literal.str_content().to_string())
} }
PrimaryCondition::Parenthesized(cond) => cond.dissolve().1.into(), PrimaryCondition::Parenthesized(cond) => cond.dissolve().1.into(),
PrimaryCondition::Prefix(prefix) => match prefix.operator() { PrimaryCondition::Unary(prefix) => match prefix.operator() {
ConditionalPrefixOperator::LogicalNot(_) => { ConditionalPrefixOperator::LogicalNot(_) => {
Self::Not(Box::new(prefix.dissolve().1.into())) Self::Not(Box::new(prefix.dissolve().1.into()))
} }

View File

@ -1,4 +1,4 @@
//! Transpiler for `ShulkerScript` //! Transpiler for `Shulkerscript`
use chksum_md5 as md5; use chksum_md5 as md5;
use std::{ use std::{
@ -29,7 +29,7 @@ use crate::{
use super::error::{TranspileError, TranspileResult, UnexpectedExpression}; use super::error::{TranspileError, TranspileResult, UnexpectedExpression};
/// A transpiler for `ShulkerScript`. /// A transpiler for `Shulkerscript`.
#[derive(Debug)] #[derive(Debug)]
pub struct Transpiler { pub struct Transpiler {
datapack: shulkerbox::datapack::Datapack, datapack: shulkerbox::datapack::Datapack,
@ -282,7 +282,7 @@ impl Transpiler {
|| { || {
let hash_data = let hash_data =
program_identifier.to_string() + "\0" + identifier_span.str(); program_identifier.to_string() + "\0" + identifier_span.str();
Some("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase()[..16]) Some("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
}, },
Clone::clone, Clone::clone,
) )