diff options
-rw-r--r-- | corp/tvixbolt/Cargo.lock | 8 | ||||
-rw-r--r-- | tvix/Cargo.lock | 7 | ||||
-rw-r--r-- | tvix/Cargo.nix | 16 | ||||
-rw-r--r-- | tvix/eval/Cargo.toml | 1 | ||||
-rw-r--r-- | tvix/eval/src/builtins/mod.rs | 9 | ||||
-rw-r--r-- | tvix/eval/src/builtins/to_xml.rs | 126 | ||||
-rw-r--r-- | tvix/eval/src/errors.rs | 22 | ||||
-rw-r--r-- | tvix/eval/src/tests/nix_tests/eval-okay-toxml.exp (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml.exp) | 0 | ||||
-rw-r--r-- | tvix/eval/src/tests/nix_tests/eval-okay-toxml.nix (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml.nix) | 0 | ||||
-rw-r--r-- | tvix/eval/src/tests/nix_tests/eval-okay-toxml2.exp (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml2.exp) | 0 | ||||
-rw-r--r-- | tvix/eval/src/tests/nix_tests/eval-okay-toxml2.nix (renamed from tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml2.nix) | 0 |
11 files changed, 189 insertions, 0 deletions
diff --git a/corp/tvixbolt/Cargo.lock b/corp/tvixbolt/Cargo.lock index 8225e059fa17..2bfdc1b344cd 100644 --- a/corp/tvixbolt/Cargo.lock +++ b/corp/tvixbolt/Cargo.lock @@ -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]] @@ -823,6 +825,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index da7bc0c5803e..37e1a0f13998 100644 --- a/tvix/Cargo.lock +++ b/tvix/Cargo.lock @@ -2316,6 +2316,7 @@ dependencies = [ "test-generator", "test-strategy", "tvix-eval-builtin-macros", + "xml-rs", ] [[package]] @@ -2603,6 +2604,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix index 285e55fb7322..5fca533d1cb7 100644 --- a/tvix/Cargo.nix +++ b/tvix/Cargo.nix @@ -6895,6 +6895,10 @@ rec { packageId = "tvix-eval-builtin-macros"; rename = "builtin-macros"; } + { + name = "xml-rs"; + packageId = "xml-rs"; + } ]; devDependencies = [ { @@ -8424,6 +8428,18 @@ rec { ]; }; + "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"; version = "0.5.1"; diff --git a/tvix/eval/Cargo.toml b/tvix/eval/Cargo.toml index 75c45c96b08d..6974110290bf 100644 --- a/tvix/eval/Cargo.toml +++ b/tvix/eval/Cargo.toml @@ -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" diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs index b93bbd99f87a..7614353f5bbb 100644 --- a/tvix/eval/src/builtins/mod.rs +++ b/tvix/eval/src/builtins/mod.rs @@ -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) diff --git a/tvix/eval/src/builtins/to_xml.rs b/tvix/eval/src/builtins/to_xml.rs new file mode 100644 index 000000000000..f2b98a7e3168 --- /dev/null +++ b/tvix/eval/src/builtins/to_xml.rs @@ -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(()) +} diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs index 6a463d9f96df..67ef509ba9d7 100644 --- a/tvix/eval/src/errors.rs +++ b/tvix/eval/src/errors.rs @@ -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. diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml.exp b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.exp index 828220890ecd..828220890ecd 100644 --- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml.exp +++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.exp diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml.nix b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.nix index 068c97a6c1b3..068c97a6c1b3 100644 --- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml.nix +++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.nix diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml2.exp b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.exp index 634a841eb190..634a841eb190 100644 --- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml2.exp +++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.exp diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml2.nix b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.nix index ff1791b30eb5..ff1791b30eb5 100644 --- a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-toxml2.nix +++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.nix |