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:
Vincent Ambo 2023-01-15 14:52:37 +03:00 committed by tazjin
parent 1786b4c835
commit d365b09226
11 changed files with 189 additions and 0 deletions

View file

@ -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
View file

@ -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"

View file

@ -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";

View file

@ -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"

View file

@ -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)

View 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(())
}

View file

@ -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.