feat(tvix/eval): implement builtins.toXML
Change-Id: I009efc53a8e98f0650ae660c4decd8216e8a06e7 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7835 Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
This commit is contained in:
parent
1786b4c835
commit
d365b09226
11 changed files with 189 additions and 0 deletions
8
corp/tvixbolt/Cargo.lock
generated
8
corp/tvixbolt/Cargo.lock
generated
|
@ -289,6 +289,7 @@ dependencies = [
|
|||
"imbl-sized-chunks",
|
||||
"rand_core",
|
||||
"rand_xoshiro",
|
||||
"serde",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -665,6 +666,7 @@ dependencies = [
|
|||
"smol_str",
|
||||
"tabwriter",
|
||||
"tvix-eval-builtin-macros",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -822,6 +824,12 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "yew"
|
||||
version = "0.19.3"
|
||||
|
|
7
tvix/Cargo.lock
generated
7
tvix/Cargo.lock
generated
|
@ -2316,6 +2316,7 @@ dependencies = [
|
|||
"test-generator",
|
||||
"test-strategy",
|
||||
"tvix-eval-builtin-macros",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2602,6 +2603,12 @@ version = "0.42.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
|
|
|
@ -6895,6 +6895,10 @@ rec {
|
|||
packageId = "tvix-eval-builtin-macros";
|
||||
rename = "builtin-macros";
|
||||
}
|
||||
{
|
||||
name = "xml-rs";
|
||||
packageId = "xml-rs";
|
||||
}
|
||||
];
|
||||
devDependencies = [
|
||||
{
|
||||
|
@ -8423,6 +8427,18 @@ rec {
|
|||
"Microsoft"
|
||||
];
|
||||
|
||||
};
|
||||
"xml-rs" = rec {
|
||||
crateName = "xml-rs";
|
||||
version = "0.8.4";
|
||||
edition = "2015";
|
||||
crateBin = [ ];
|
||||
sha256 = "18q048wk3jafgl59sa2m0qv4vk2sqkfcya4kznc5rxqkhsad7myj";
|
||||
libName = "xml";
|
||||
authors = [
|
||||
"Vladimir Matveev <vmatveev@citrine.cc>"
|
||||
];
|
||||
|
||||
};
|
||||
"yansi" = rec {
|
||||
crateName = "yansi";
|
||||
|
|
|
@ -25,6 +25,7 @@ serde_json = "1.0"
|
|||
smol_str = "0.1"
|
||||
tabwriter = "1.2"
|
||||
test-strategy = { version = "0.2.1", optional = true }
|
||||
xml-rs = "0.8.4"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.4"
|
||||
|
|
|
@ -20,6 +20,7 @@ use crate::{
|
|||
|
||||
use self::versions::{VersionPart, VersionPartsIter};
|
||||
|
||||
mod to_xml;
|
||||
mod versions;
|
||||
|
||||
#[cfg(feature = "impure")]
|
||||
|
@ -918,6 +919,14 @@ mod pure_builtins {
|
|||
.map(Value::String)
|
||||
}
|
||||
|
||||
#[builtin("toXML")]
|
||||
fn builtin_to_xml(vm: &mut VM, value: Value) -> Result<Value, ErrorKind> {
|
||||
value.deep_force(vm, &mut Default::default())?;
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
to_xml::value_to_xml(&mut buf, &value)?;
|
||||
Ok(String::from_utf8(buf)?.into())
|
||||
}
|
||||
|
||||
#[builtin("placeholder")]
|
||||
fn builtin_placeholder(vm: &mut VM, #[lazy] _: Value) -> Result<Value, ErrorKind> {
|
||||
// TODO(amjoseph)
|
||||
|
|
126
tvix/eval/src/builtins/to_xml.rs
Normal file
126
tvix/eval/src/builtins/to_xml.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
//! This module implements `builtins.toXML`, which is a serialisation
|
||||
//! of value information as well as internal tvix state that several
|
||||
//! things in nixpkgs rely on.
|
||||
|
||||
use std::{io::Write, rc::Rc};
|
||||
use xml::writer::events::XmlEvent;
|
||||
use xml::writer::EmitterConfig;
|
||||
use xml::writer::EventWriter;
|
||||
|
||||
use crate::{ErrorKind, Value};
|
||||
|
||||
/// Recursively serialise a value to XML. The value *must* have been
|
||||
/// deep-forced before being passed to this function.
|
||||
pub(super) fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<(), ErrorKind> {
|
||||
let config = EmitterConfig {
|
||||
perform_indent: true,
|
||||
pad_self_closing: true,
|
||||
|
||||
// Nix uses single-quotes *only* in the document declaration,
|
||||
// so we need to write it manually.
|
||||
write_document_declaration: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Write a literal document declaration, using C++-Nix-style
|
||||
// single quotes.
|
||||
write!(writer, "<?xml version='1.0' encoding='utf-8'?>\n")?;
|
||||
|
||||
let mut writer = EventWriter::new_with_config(writer, config);
|
||||
|
||||
writer.write(XmlEvent::start_element("expr"))?;
|
||||
value_variant_to_xml(&mut writer, value)?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
|
||||
// Unwrap the writer to add the final newline that C++ Nix adds.
|
||||
write!(writer.into_inner(), "\n")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_typed_value<W: Write, V: ToString>(
|
||||
w: &mut EventWriter<W>,
|
||||
name: &str,
|
||||
value: V,
|
||||
) -> Result<(), ErrorKind> {
|
||||
w.write(XmlEvent::start_element(name).attr("value", &value.to_string()))?;
|
||||
w.write(XmlEvent::end_element())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn value_variant_to_xml<W: Write>(w: &mut EventWriter<W>, value: &Value) -> Result<(), ErrorKind> {
|
||||
match value {
|
||||
Value::Thunk(t) => return value_variant_to_xml(w, &t.value()),
|
||||
|
||||
Value::Null => {
|
||||
w.write(XmlEvent::start_element("null"))?;
|
||||
w.write(XmlEvent::end_element())
|
||||
}
|
||||
|
||||
Value::Bool(b) => return write_typed_value(w, "bool", b),
|
||||
Value::Integer(i) => return write_typed_value(w, "int", i),
|
||||
Value::Float(f) => return write_typed_value(w, "float", f),
|
||||
Value::String(s) => return write_typed_value(w, "string", s.as_str()),
|
||||
Value::Path(p) => return write_typed_value(w, "path", p.to_string_lossy()),
|
||||
|
||||
Value::List(list) => {
|
||||
w.write(XmlEvent::start_element("list"))?;
|
||||
|
||||
for elem in list.into_iter() {
|
||||
value_variant_to_xml(w, &elem)?;
|
||||
}
|
||||
|
||||
w.write(XmlEvent::end_element())
|
||||
}
|
||||
|
||||
Value::Attrs(attrs) => {
|
||||
w.write(XmlEvent::start_element("attrs"))?;
|
||||
|
||||
for elem in attrs.iter() {
|
||||
w.write(XmlEvent::start_element("attr").attr("name", elem.0.as_str()))?;
|
||||
value_variant_to_xml(w, &elem.1)?;
|
||||
w.write(XmlEvent::end_element())?;
|
||||
}
|
||||
|
||||
w.write(XmlEvent::end_element())
|
||||
}
|
||||
|
||||
Value::Closure(c) => {
|
||||
w.write(XmlEvent::start_element("function"))?;
|
||||
|
||||
match &c.lambda.formals {
|
||||
Some(formals) => {
|
||||
if formals.ellipsis {
|
||||
w.write(XmlEvent::start_element("attrspat").attr("ellipsis", "1"))?;
|
||||
w.write(XmlEvent::end_element())?;
|
||||
}
|
||||
|
||||
for arg in formals.arguments.iter() {
|
||||
w.write(XmlEvent::start_element("attr").attr("name", arg.0.as_str()))?;
|
||||
w.write(XmlEvent::end_element())?;
|
||||
}
|
||||
}
|
||||
None => todo!("we don't persist the arg name ..."),
|
||||
}
|
||||
|
||||
w.write(XmlEvent::end_element())
|
||||
}
|
||||
|
||||
Value::Builtin(_) => {
|
||||
w.write(XmlEvent::start_element("unevaluated"))?;
|
||||
w.write(XmlEvent::end_element())
|
||||
}
|
||||
|
||||
Value::AttrNotFound
|
||||
| Value::Blueprint(_)
|
||||
| Value::DeferredUpvalue(_)
|
||||
| Value::UnresolvedPath(_) => {
|
||||
return Err(ErrorKind::TvixBug {
|
||||
msg: "internal value variant encountered in builtins.toXML",
|
||||
metadata: Some(Rc::new(value.clone())),
|
||||
})
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -5,12 +5,14 @@ use std::io;
|
|||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::str::Utf8Error;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt::Debug, fmt::Display, num::ParseIntError};
|
||||
|
||||
use codemap::{File, Span};
|
||||
use codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};
|
||||
use smol_str::SmolStr;
|
||||
use xml::writer::Error as XmlError;
|
||||
|
||||
use crate::{SourceCode, Value};
|
||||
|
||||
|
@ -138,6 +140,9 @@ pub enum ErrorKind {
|
|||
formals_span: Span,
|
||||
},
|
||||
|
||||
/// Errors while serialising to XML.
|
||||
Xml(Rc<XmlError>),
|
||||
|
||||
/// Variant for code paths that are known bugs in Tvix (usually
|
||||
/// issues with the compiler/VM interaction).
|
||||
TvixBug {
|
||||
|
@ -164,6 +169,7 @@ impl error::Error for Error {
|
|||
errors.first().map(|e| e as &dyn error::Error)
|
||||
}
|
||||
ErrorKind::IO { error, .. } => Some(error.as_ref()),
|
||||
ErrorKind::Xml(error) => Some(error.as_ref()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -181,6 +187,18 @@ impl From<Utf8Error> for ErrorKind {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for ErrorKind {
|
||||
fn from(_: FromUtf8Error) -> Self {
|
||||
Self::NotImplemented("FromUtf8Error not handled: https://b.tvl.fyi/issues/189")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<XmlError> for ErrorKind {
|
||||
fn from(err: XmlError) -> Self {
|
||||
Self::Xml(Rc::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation used if errors occur while forcing thunks (which
|
||||
/// can potentially be threaded through a few contexts, i.e. nested
|
||||
/// thunks).
|
||||
|
@ -392,6 +410,8 @@ to a missing value in the attribute set(s) included via `with`."#,
|
|||
)
|
||||
}
|
||||
|
||||
ErrorKind::Xml(error) => write!(f, "failed to serialise to XML: {error}"),
|
||||
|
||||
ErrorKind::TvixBug { msg, metadata } => {
|
||||
write!(f, "Tvix bug: {}", msg)?;
|
||||
|
||||
|
@ -689,6 +709,7 @@ impl Error {
|
|||
| ErrorKind::ImportCompilerError { .. }
|
||||
| ErrorKind::IO { .. }
|
||||
| ErrorKind::FromJsonError(_)
|
||||
| ErrorKind::Xml(_)
|
||||
| ErrorKind::TvixBug { .. }
|
||||
| ErrorKind::NotImplemented(_) => return None,
|
||||
};
|
||||
|
@ -731,6 +752,7 @@ impl Error {
|
|||
ErrorKind::UnexpectedArgument { .. } => "E031",
|
||||
ErrorKind::RelativePathResolution(_) => "E032",
|
||||
ErrorKind::DivisionByZero => "E033",
|
||||
ErrorKind::Xml(_) => "E034",
|
||||
|
||||
// Special error code that is not part of the normal
|
||||
// ordering.
|
||||
|
|
Loading…
Reference in a new issue