//! Conversions from `wasmparser` to `wasm-encoder` to [`Reencode`] parsed wasm. //! //! The [`RoundtripReencoder`] allows encoding identical wasm to the parsed //! input.
usecrate::CoreTypeEncoder; use std::convert::Infallible;
#[cfg(feature = "component-model")] mod component;
/// Parses the input `section` given from the `wasmparser` crate and /// adds the custom section to the `module`. fn parse_custom_section(
&mutself,
module: &mutcrate::Module,
section: wasmparser::CustomSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_custom_section(self, module, section)
}
/// Converts the input `section` given from the `wasmparser` crate into an /// encoded custom section. fn custom_section<'a>(
&mutself,
section: wasmparser::CustomSectionReader<'a>,
) -> crate::CustomSection<'a> {
utils::custom_section(self, section)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the code to the `code` section. fn parse_code_section(
&mutself,
code: &mutcrate::CodeSection,
section: wasmparser::CodeSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_code_section(self, code, section)
}
/// Parses a single [`wasmparser::FunctionBody`] and adds it to the `code` section. fn parse_function_body(
&mutself,
code: &mutcrate::CodeSection,
func: wasmparser::FunctionBody<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_function_body(self, code, func)
}
/// Create a new [`crate::Function`] by parsing the locals declarations from the /// provided [`wasmparser::FunctionBody`]. fn new_function_with_parsed_locals(
&mutself,
func: &wasmparser::FunctionBody<'_>,
) -> Result<crate::Function, Error<Self::Error>> {
utils::new_function_with_parsed_locals(self, func)
}
/// Parses a single instruction from `reader` and adds it to `function`. fn parse_instruction<'a>(
&mutself,
reader: &mut wasmparser::OperatorsReader<'a>,
) -> Result<crate::Instruction<'a>, Error<Self::Error>> {
utils::parse_instruction(self, reader)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the data to the `data` section. fn parse_data_section(
&mutself,
data: &mutcrate::DataSection,
section: wasmparser::DataSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_data_section(self, data, section)
}
/// Parses a single [`wasmparser::Data`] and adds it to the `data` section. fn parse_data(
&mutself,
data: &mutcrate::DataSection,
datum: wasmparser::Data<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_data(self, data, datum)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the elements to the `element` section. fn parse_element_section(
&mutself,
elements: &mutcrate::ElementSection,
section: wasmparser::ElementSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_element_section(self, elements, section)
}
/// Parses the single [`wasmparser::Element`] provided and adds it to the /// `element` section. fn parse_element(
&mutself,
elements: &mutcrate::ElementSection,
element: wasmparser::Element<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_element(self, elements, element)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the exports to the `exports` section. fn parse_export_section(
&mutself,
exports: &mutcrate::ExportSection,
section: wasmparser::ExportSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_export_section(self, exports, section)
}
/// Parses the single [`wasmparser::Export`] provided and adds it to the /// `exports` section. fn parse_export(&mutself, exports: &mutcrate::ExportSection, export: wasmparser::Export<'_>) {
utils::parse_export(self, exports, export)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the functions to the `functions` section. fn parse_function_section(
&mutself,
functions: &mutcrate::FunctionSection,
section: wasmparser::FunctionSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_function_section(self, functions, section)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the globals to the `globals` section. fn parse_global_section(
&mutself,
globals: &mutcrate::GlobalSection,
section: wasmparser::GlobalSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_global_section(self, globals, section)
}
/// Parses the single [`wasmparser::Global`] provided and adds it to the /// `globals` section. fn parse_global(
&mutself,
globals: &mutcrate::GlobalSection,
global: wasmparser::Global<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_global(self, globals, global)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the imports to the `import` section. fn parse_import_section(
&mutself,
imports: &mutcrate::ImportSection,
section: wasmparser::ImportSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_import_section(self, imports, section)
}
/// Parses the single [`wasmparser::Import`] provided and adds it to the /// `import` section. fn parse_import(
&mutself,
imports: &mutcrate::ImportSection,
import: wasmparser::Import<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_import(self, imports, import)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the memories to the `memories` section. fn parse_memory_section(
&mutself,
memories: &mutcrate::MemorySection,
section: wasmparser::MemorySectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_memory_section(self, memories, section)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the tables to the `tables` section. fn parse_table_section(
&mutself,
tables: &mutcrate::TableSection,
section: wasmparser::TableSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_table_section(self, tables, section)
}
/// Parses a single [`wasmparser::Table`] and adds it to the `tables` section. fn parse_table(
&mutself,
tables: &mutcrate::TableSection,
table: wasmparser::Table<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_table(self, tables, table)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the tags to the `tags` section. fn parse_tag_section(
&mutself,
tags: &mutcrate::TagSection,
section: wasmparser::TagSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_tag_section(self, tags, section)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the types to the `types` section. fn parse_type_section(
&mutself,
types: &mutcrate::TypeSection,
section: wasmparser::TypeSectionReader<'_>,
) -> Result<(), Error<Self::Error>> {
utils::parse_type_section(self, types, section)
}
/// Parses a single [`wasmparser::RecGroup`] and adds it to the `types` section. fn parse_recursive_type_group(
&mutself,
encoder: CoreTypeEncoder,
rec_group: wasmparser::RecGroup,
) -> Result<(), Error<Self::Error>> {
utils::parse_recursive_type_group(self, encoder, rec_group)
}
/// A hook method that is called inside [`Reencode::parse_core_module`] /// before and after every non-custom core wasm section. /// /// This method can be used to insert new custom sections in between those /// sections, or to detect when a non-custom section is missing and insert /// it in the [proper order]. /// /// The `after` parameter is `None` iff the hook is called before the first /// non-custom section, and `Some(s)` afterwards, where `s` is the /// [`SectionId`] of the previous non-custom section. /// /// The `before` parameter is `None` iff the hook is called after the last /// non-custom section, and `Some(s)` beforehand, where `s` is the /// [`SectionId`] of the following non-custom section. /// /// [proper order]: https://webassembly.github.io/spec/core/binary/modules.html#binary-module /// [`SectionId`]: crate::SectionId fn intersperse_section_hook(
&mutself,
module: &mutcrate::Module,
after: Option<crate::SectionId>,
before: Option<crate::SectionId>,
) -> Result<(), Error<Self::Error>> {
utils::intersperse_section_hook(self, module, after, before)
}
/// An error when re-encoding from `wasmparser` to `wasm-encoder`. #[derive(Debug)] pubenum Error<E = Infallible> { /// There was a type reference that was canonicalized and no longer /// references an index into a module's types space, so we cannot encode it /// into a Wasm binary again.
CanonicalizedHeapTypeReference, /// The const expression is invalid: not actually constant or something like /// that.
InvalidConstExpr, /// The code section size listed was not valid for the wasm binary provided.
InvalidCodeSectionSize, /// There was a section that does not belong into a core wasm module.
UnexpectedNonCoreModuleSection, /// There was a section that does not belong into a compoennt module.
UnexpectedNonComponentSection, /// A core type definition was found in a component that's not supported.
UnsupportedCoreTypeInComponent, /// There was an error when parsing.
ParseError(wasmparser::BinaryReaderError), /// There was a user-defined error when re-encoding.
UserError(E),
}
impl<E: std::fmt::Display> std::fmt::Display for Error<E> { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { matchself { Self::ParseError(_e) => {
write!(fmt, "There was an error when parsing")
} Self::UserError(e) => write!(fmt, "{e}"), Self::InvalidConstExpr => write!(fmt, "The const expression was invalid"), Self::UnexpectedNonCoreModuleSection => write!(
fmt, "There was a section that does not belong into a core wasm module"
), Self::UnexpectedNonComponentSection => write!(
fmt, "There was a section that does not belong into a component"
), Self::CanonicalizedHeapTypeReference => write!(
fmt, "There was a canonicalized heap type reference without type index information"
), Self::UnsupportedCoreTypeInComponent => {
fmt.write_str("unsupported core type in a component")
} Self::InvalidCodeSectionSize => fmt.write_str("invalid code section size"),
}
}
}
/// Reencodes `wasmparser` into `wasm-encoder` so that the encoded wasm is /// identical to the input and can be parsed and encoded again. #[derive(Debug)] pubstruct RoundtripReencoder;
impl Reencode for RoundtripReencoder { type Error = Infallible;
}
// Convert from `range` to a byte range within `data` while // accounting for various offsets. Then create a // `CodeSectionReader` (which notably the payload does not // give us here) and recurse with that. This means that // users overridding `parse_code_section` always get that // function called. let orig_offset = parser.offset() as usize; let get_original_section = |range: Range<usize>| {
data.get(range.start - orig_offset..range.end - orig_offset)
.ok_or(Error::InvalidCodeSectionSize)
}; letmut last_section = None;
// Convert from `range` to a byte range within `data` while // accounting for various offsets. Then create a // `CodeSectionReader` (which notably the payload does not // give us here) and recurse with that. This means that // users overridding `parse_code_section` always get that // function called. let section = get_original_section(range.clone())?; let reader = wasmparser::BinaryReader::new(section, range.start); let section = wasmparser::CodeSectionReader::new(reader)?;
reencoder.parse_code_section(&mut codes, section)?;
module.section(&codes);
}
// Parsing of code section entries (function bodies) happens // above during the handling of the code section. That means // that we just skip all these payloads.
wasmparser::Payload::CodeSectionEntry(_) => {}
other => match other.as_section() {
Some((id, range)) => { let section = get_original_section(range)?;
reencoder.parse_unknown_section(module, id, section)?;
}
None => unreachable!(),
},
}
}
Ok(())
}
/// A hook method that is called inside [`Reencode::parse_core_module`] /// before and after every non-custom core wasm section. /// /// This method can be used to insert new custom sections in between those /// sections, or to detect when a non-custom section is missing and insert /// it in the [proper order]. /// /// The `after` parameter is `None` iff the hook is called before the first /// non-custom section, and `Some(s)` afterwards, where `s` is the /// [`SectionId`] of the previous non-custom section. /// /// The `before` parameter is `None` iff the hook is called after the last /// non-custom section, and `Some(s)` beforehand, where `s` is the /// [`SectionId`] of the following non-custom section. /// /// [proper order]: https://webassembly.github.io/spec/core/binary/modules.html#binary-module /// [`SectionId`]: crate::SectionId pubfn intersperse_section_hook<T: ?Sized + Reencode>(
_reencoder: &mut T,
_module: &mutcrate::Module,
_after: Option<crate::SectionId>,
_before: Option<crate::SectionId>,
) -> Result<(), Error<T::Error>> {
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the types to the `types` section. pubfn parse_type_section<T: ?Sized + Reencode>(
reencoder: &mut T,
types: &mutcrate::TypeSection,
section: wasmparser::TypeSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for rec_group in section {
reencoder.parse_recursive_type_group(types.ty(), rec_group?)?;
}
Ok(())
}
/// Parses a single [`wasmparser::RecGroup`] and adds it to the `types` section. pubfn parse_recursive_type_group<T: ?Sized + Reencode>(
reencoder: &mut T,
encoder: CoreTypeEncoder,
rec_group: wasmparser::RecGroup,
) -> Result<(), Error<T::Error>> { if rec_group.is_explicit_rec_group() { let subtypes = rec_group
.into_types()
.map(|t| reencoder.sub_type(t))
.collect::<Result<Vec<_>, _>>()?;
encoder.rec(subtypes);
} else { let ty = rec_group.into_types().next().unwrap();
encoder.subtype(&reencoder.sub_type(ty)?);
}
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the tables to the `tables` section. pubfn parse_table_section<T: ?Sized + Reencode>(
reencoder: &mut T,
tables: &mutcrate::TableSection,
section: wasmparser::TableSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for table in section {
reencoder.parse_table(tables, table?)?;
}
Ok(())
}
/// Parses a single [`wasmparser::Table`] and adds it to the `tables` section. pubfn parse_table<T: ?Sized + Reencode>(
reencoder: &mut T,
tables: &mutcrate::TableSection,
table: wasmparser::Table<'_>,
) -> Result<(), Error<T::Error>> { let ty = reencoder.table_type(table.ty)?; match table.init {
wasmparser::TableInit::RefNull => {
tables.table(ty);
}
wasmparser::TableInit::Expr(e) => {
tables.table_with_init(ty, &reencoder.const_expr(e)?);
}
}
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the tags to the `tags` section. pubfn parse_tag_section<T: ?Sized + Reencode>(
reencoder: &mut T,
tags: &mutcrate::TagSection,
section: wasmparser::TagSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for tag in section { let tag = tag?;
tags.tag(reencoder.tag_type(tag));
}
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the exports to the `exports` section. pubfn parse_export_section<T: ?Sized + Reencode>(
reencoder: &mut T,
exports: &mutcrate::ExportSection,
section: wasmparser::ExportSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for export in section {
reencoder.parse_export(exports, export?);
}
Ok(())
}
/// Parses the single [`wasmparser::Export`] provided and adds it to the /// `exports` section. pubfn parse_export<T: ?Sized + Reencode>(
reencoder: &mut T,
exports: &mutcrate::ExportSection,
export: wasmparser::Export<'_>,
) {
exports.export(
export.name,
reencoder.export_kind(export.kind),
reencoder.external_index(export.kind, export.index),
);
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the globals to the `globals` section. pubfn parse_global_section<T: ?Sized + Reencode>(
reencoder: &mut T,
globals: &mutcrate::GlobalSection,
section: wasmparser::GlobalSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for global in section {
reencoder.parse_global(globals, global?)?;
}
Ok(())
}
/// Parses the single [`wasmparser::Global`] provided and adds it to the /// `globals` section. pubfn parse_global<T: ?Sized + Reencode>(
reencoder: &mut T,
globals: &mutcrate::GlobalSection,
global: wasmparser::Global<'_>,
) -> Result<(), Error<T::Error>> {
globals.global(
reencoder.global_type(global.ty)?,
&reencoder.const_expr(global.init_expr)?,
);
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the imports to the `import` section. pubfn parse_import_section<T: ?Sized + Reencode>(
reencoder: &mut T,
imports: &mutcrate::ImportSection,
section: wasmparser::ImportSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for import in section {
reencoder.parse_import(imports, import?)?;
}
Ok(())
}
/// Parses the single [`wasmparser::Import`] provided and adds it to the /// `import` section. pubfn parse_import<T: ?Sized + Reencode>(
reencoder: &mut T,
imports: &mutcrate::ImportSection,
import: wasmparser::Import<'_>,
) -> Result<(), Error<T::Error>> {
imports.import(
import.module,
import.name,
reencoder.entity_type(import.ty)?,
);
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the memories to the `memories` section. pubfn parse_memory_section<T: ?Sized + Reencode>(
reencoder: &mut T,
memories: &mutcrate::MemorySection,
section: wasmparser::MemorySectionReader<'_>,
) -> Result<(), Error<T::Error>> { for memory in section { let memory = memory?;
memories.memory(reencoder.memory_type(memory));
}
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the functions to the `functions` section. pubfn parse_function_section<T: ?Sized + Reencode>(
reencoder: &mut T,
functions: &mutcrate::FunctionSection,
section: wasmparser::FunctionSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for func in section {
functions.function(reencoder.type_index(func?));
}
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the data to the `data` section. pubfn parse_data_section<T: ?Sized + Reencode>(
reencoder: &mut T,
data: &mutcrate::DataSection,
section: wasmparser::DataSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for datum in section {
reencoder.parse_data(data, datum?)?;
}
Ok(())
}
/// Parses a single [`wasmparser::Data`] and adds it to the `data` section. pubfn parse_data<T: ?Sized + Reencode>(
reencoder: &mut T,
data: &mutcrate::DataSection,
datum: wasmparser::Data<'_>,
) -> Result<(), Error<T::Error>> { match datum.kind {
wasmparser::DataKind::Active {
memory_index,
offset_expr,
} => data.active(
reencoder.memory_index(memory_index),
&reencoder.const_expr(offset_expr)?,
datum.data.iter().copied(),
),
wasmparser::DataKind::Passive => data.passive(datum.data.iter().copied()),
};
Ok(())
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the elements to the `element` section. pubfn parse_element_section<T: ?Sized + Reencode>(
reencoder: &mut T,
elements: &mutcrate::ElementSection,
section: wasmparser::ElementSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for element in section {
reencoder.parse_element(elements, element?)?;
}
Ok(())
}
/// Parses the single [`wasmparser::Element`] provided and adds it to the /// `element` section. pubfn parse_element<T: ?Sized + Reencode>(
reencoder: &mut T,
elements: &mutcrate::ElementSection,
element: wasmparser::Element<'_>,
) -> Result<(), Error<T::Error>> { let elems = reencoder.element_items(element.items)?; match element.kind {
wasmparser::ElementKind::Active {
table_index,
offset_expr,
} => elements.active( // Inform the reencoder that a table index is being used even if // it's not actually present here. That helps wasm-mutate for // example which wants to track uses to know when it's ok to // remove a table. // // If the table index started at `None` and is still zero then // preserve this encoding and keep it at `None`. Otherwise if // the result is nonzero or it was previously nonzero then keep // that encoding too. match (table_index, reencoder.table_index(table_index.unwrap_or(0))) {
(None, 0) => None,
(_, n) => Some(n),
},
&reencoder.const_expr(offset_expr)?,
elems,
),
wasmparser::ElementKind::Passive => elements.passive(elems),
wasmparser::ElementKind::Declared => elements.declared(elems),
};
Ok(())
}
pubfn element_items<'a, T: ?Sized + Reencode>(
reencoder: &mut T,
items: wasmparser::ElementItems<'a>,
) -> Result<crate::Elements<'a>, Error<T::Error>> {
Ok(match items {
wasmparser::ElementItems::Functions(f) => { letmut funcs = Vec::new(); for func in f {
funcs.push(reencoder.function_index(func?));
} crate::Elements::Functions(funcs.into())
}
wasmparser::ElementItems::Expressions(ty, e) => { letmut exprs = Vec::new(); for expr in e {
exprs.push(reencoder.const_expr(expr?)?);
} crate::Elements::Expressions(reencoder.ref_type(ty)?, exprs.into())
}
})
}
// This case takes the arguments of a wasmparser instruction and creates // a wasm-encoder instruction. There are a few special cases for where // the structure of a wasmparser instruction differs from that of // wasm-encoder.
(build $op:ident) => (Instruction::$op);
(build BrTable $arg:ident) => (Instruction::BrTable($arg.0, $arg.1));
(build I32Const $arg:ident) => (Instruction::I32Const($arg));
(build I64Const $arg:ident) => (Instruction::I64Const($arg));
(build F32Const $arg:ident) => (Instruction::F32Const(f32::from_bits($arg.bits())));
(build F64Const $arg:ident) => (Instruction::F64Const(f64::from_bits($arg.bits())));
(build V128Const $arg:ident) => (Instruction::V128Const($arg.i128()));
(build TryTable $table:ident) => (Instruction::TryTable(reencoder.block_type($table.ty)?, {
$table.catches.into_iter().map(|c| reencoder.catch(c)).collect::<Vec<_>>().into()
}));
(build $op:ident $arg:ident) => (Instruction::$op($arg));
(build $op:ident $($arg:ident)*) => (Instruction::$op { $($arg),* });
}
wasmparser::for_each_operator!(translate)
}
/// Parses the input `section` given from the `wasmparser` crate and adds /// all the code to the `code` section. pubfn parse_code_section<T: ?Sized + Reencode>(
reencoder: &mut T,
code: &mutcrate::CodeSection,
section: wasmparser::CodeSectionReader<'_>,
) -> Result<(), Error<T::Error>> { for func in section {
reencoder.parse_function_body(code, func?)?;
}
Ok(())
}
/// Parses a single [`wasmparser::FunctionBody`] and adds it to the `code` section. pubfn parse_function_body<T: ?Sized + Reencode>(
reencoder: &mut T,
code: &mutcrate::CodeSection,
func: wasmparser::FunctionBody<'_>,
) -> Result<(), Error<T::Error>> { letmut f = reencoder.new_function_with_parsed_locals(&func)?; letmut reader = func.get_operators_reader()?; while !reader.eof() {
f.instruction(&reencoder.parse_instruction(&mut reader)?);
}
code.function(&f);
Ok(())
}
/// Create a new [`crate::Function`] by parsing the locals declarations from the /// provided [`wasmparser::FunctionBody`]. pubfn new_function_with_parsed_locals<T: ?Sized + Reencode>(
reencoder: &mut T,
func: &wasmparser::FunctionBody<'_>,
) -> Result<crate::Function, Error<T::Error>> { letmut locals = Vec::new(); for pair in func.get_locals_reader()? { let (cnt, ty) = pair?;
locals.push((cnt, reencoder.val_type(ty)?));
}
Ok(crate::Function::new(locals))
}
/// Parses a single instruction from `reader` and adds it to `function`. pubfn parse_instruction<'a, T: ?Sized + Reencode>(
reencoder: &mut T,
reader: &mut wasmparser::OperatorsReader<'a>,
) -> Result<crate::Instruction<'a>, Error<T::Error>> { let instruction = reencoder.instruction(reader.read()?)?;
Ok(instruction)
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.