git.delta.rocks / jrsonnet / refs/commits / df9bc99da41f

difftreelog

feat std.parseYaml intrinsic

Yaroslav Bolyukin2021-11-27parent: #7c0b097.patch.diff
in: master

6 files changed

modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -7,11 +7,9 @@
 edition = "2018"
 
 [features]
-default = ["serialized-stdlib", "explaining-traces", "serde-json"]
+default = ["serialized-stdlib", "explaining-traces"]
 # Serializes standard library AST instead of parsing them every run
-serialized-stdlib = ["serde", "bincode", "jrsonnet-parser/deserialize"]
-# Allow to convert Val into serde_json::Value and backwards
-serde-json = ["serde", "serde_json"]
+serialized-stdlib = ["bincode", "jrsonnet-parser/deserialize"]
 # Rustc-like trace visualization
 explaining-traces = ["annotate-snippets"]
 # Allows library authors to throw custom errors
@@ -34,21 +32,17 @@
 thiserror = "1.0"
 gcmodule = { git = "https://github.com/CertainLach/gcmodule", branch = "jrsonnet" }
 
+serde = "1.0"
+serde_json = "1.0"
+serde_yaml = { git = "https://github.com/CertainLach/serde-yaml", branch = "feature/old-octals-quirk" }
+
 [dependencies.anyhow]
 version = "1.0"
 optional = true
 
 # Serialized stdlib
-[dependencies.serde]
-version = "1.0"
-optional = true
 [dependencies.bincode]
 version = "1.3.1"
-optional = true
-
-# Serde json
-[dependencies.serde_json]
-version = "1.0"
 optional = true
 
 # Explaining traces
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -3,15 +3,17 @@
 	equals,
 	error::{Error::*, Result},
 	operator::evaluate_mod_op,
-	parse_args, primitive_equals, push_frame, throw, with_state, ArrValue, Context,
-	EvaluationState, FuncVal, IndexableVal, LazyVal, Val,
+	parse_args, primitive_equals, push_frame, throw, with_state, ArrValue, Context, FuncVal,
+	IndexableVal, LazyVal, Val,
 };
 use format::{format_arr, format_obj};
 use gcmodule::Cc;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ArgsDesc, ExprLocation};
 use jrsonnet_types::ty;
-use std::{collections::HashMap, path::PathBuf, rc::Rc};
+use serde::Deserialize;
+use serde_yaml::DeserializingQuirks;
+use std::{collections::HashMap, convert::TryFrom, path::PathBuf, rc::Rc};
 
 pub mod stdlib;
 pub use stdlib::*;
@@ -128,6 +130,7 @@
 			("strReplace".into(), builtin_str_replace),
 			("splitLimit".into(), builtin_splitlimit),
 			("parseJson".into(), builtin_parse_json),
+			("parseYaml".into(), builtin_parse_yaml),
 			("asciiUpper".into(), builtin_ascii_upper),
 			("asciiLower".into(), builtin_ascii_lower),
 			("member".into(), builtin_member),
@@ -210,9 +213,30 @@
 	parse_args!(context, "parseJson", args, 1, [
 		0, s: ty!(string) => Val::Str;
 	], {
-		let state = EvaluationState::default();
-		let path = PathBuf::from("std.parseJson").into();
-		state.evaluate_snippet_raw(path ,s)
+		let value: serde_json::Value = serde_json::from_str(&s).map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
+		Ok(Val::try_from(&value)?)
+	})
+}
+
+fn builtin_parse_yaml(context: Context, _loc: &ExprLocation, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "parseYaml", args, 1, [
+		0, s: ty!(string) => Val::Str;
+	], {
+		let value = serde_yaml::Deserializer::from_str_with_quirks(&s, DeserializingQuirks { old_octals: true });
+		let mut out = vec![];
+		for item in value {
+			let value = serde_json::Value::deserialize(item)
+				.map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
+			let val = Val::try_from(&value)?;
+			out.push(val);
+		}
+		if out.is_empty() {
+			Ok(Val::Null)
+		} else if out.len() == 1 {
+			Ok(out.into_iter().next().unwrap())
+		} else {
+			Ok(Val::Arr(out.into()))
+		}
 	})
 }
 
modifiedcrates/jrsonnet-evaluator/src/integrations/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/mod.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/mod.rs
@@ -1,2 +1 @@
-#[cfg(feature = "serde-json")]
 pub mod serde;
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -15,7 +15,7 @@
 			Val::Num(n) => Self::Number(if n.fract() <= f64::EPSILON {
 				(*n as i64).into()
 			} else {
-				Number::from_f64(*n).expect("to json number")
+				Number::from_f64(*n).expect("jsonnet numbers can't be infinite or NaN")
 			}),
 			Val::Arr(a) => {
 				let mut out = Vec::with_capacity(a.len());
@@ -29,7 +29,9 @@
 				for key in o.fields() {
 					out.insert(
 						(&key as &str).into(),
-						(&o.get(key)?.expect("field exists")).try_into()?,
+						(&o.get(key)?
+							.expect("key is present in fields, so value should exist"))
+							.try_into()?,
 					);
 				}
 				Self::Object(out)
@@ -39,27 +41,30 @@
 	}
 }
 
-impl From<&Value> for Val {
-	fn from(v: &Value) -> Self {
-		match v {
+impl TryFrom<&Value> for Val {
+	type Error = LocError;
+	fn try_from(v: &Value) -> Result<Self> {
+		Ok(match v {
 			Value::Null => Self::Null,
 			Value::Bool(v) => Self::Bool(*v),
-			Value::Number(n) => Self::Num(n.as_f64().expect("as f64")),
+			Value::Number(n) => Self::Num(n.as_f64().ok_or_else(|| {
+				RuntimeError(format!("json number can't be represented as jsonnet: {}", n).into())
+			})?),
 			Value::String(s) => Self::Str((s as &str).into()),
 			Value::Array(a) => {
 				let mut out: Vec<Self> = Vec::with_capacity(a.len());
 				for v in a {
-					out.push(v.into());
+					out.push(v.try_into()?);
 				}
 				Self::Arr(out.into())
 			}
 			Value::Object(o) => {
 				let mut builder = ObjValueBuilder::with_capacity(o.len());
 				for (k, v) in o {
-					builder.member((k as &str).into()).value(v.into());
+					builder.member((k as &str).into()).value(v.try_into()?);
 				}
 				Self::Obj(builder.build())
 			}
-		}
+		})
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/trace/mod.rs
1mod location;23use crate::{error::Error, EvaluationState, LocError};4pub use location::*;5use std::path::{Path, PathBuf};67/// The way paths should be displayed8pub enum PathResolver {9	/// Only filename10	FileName,11	/// Absolute path12	Absolute,13	/// Path relative to base directory14	Relative(PathBuf),15}1617impl PathResolver {18	pub fn resolve(&self, from: &Path) -> String {19		match self {20			Self::FileName => from.file_name().unwrap().to_string_lossy().into_owned(),21			Self::Absolute => from.to_string_lossy().into_owned(),22			Self::Relative(base) => {23				if from.is_relative() {24					return from.to_string_lossy().into_owned();25				}26				pathdiff::diff_paths(from, base)27					.unwrap()28					.to_string_lossy()29					.into_owned()30			}31		}32	}33}3435/// Implements pretty-printing of traces36pub trait TraceFormat {37	fn write_trace(38		&self,39		out: &mut dyn std::fmt::Write,40		evaluation_state: &EvaluationState,41		error: &LocError,42	) -> Result<(), std::fmt::Error>;43	// fn print_trace(44	// 	&self,45	// 	evaluation_state: &EvaluationState,46	// 	error: &LocError,47	// ) -> Result<(), std::fmt::Error> {48	// 	self.write_trace(&mut std::fmt::stdout(), evaluation_state, error)49	// }50}5152fn print_code_location(53	out: &mut impl std::fmt::Write,54	start: &CodeLocation,55	end: &CodeLocation,56) -> Result<(), std::fmt::Error> {57	if start.line == end.line {58		if start.column == end.column {59			write!(out, "{}:{}", start.line, end.column - 1)?;60		} else {61			write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;62		}63	} else {64		write!(65			out,66			"{}:{}-{}:{}",67			start.line,68			end.column.saturating_sub(1),69			start.line,70			end.column71		)?;72	}73	Ok(())74}7576/// vanilla-like jsonnet formatting77pub struct CompactFormat {78	pub resolver: PathResolver,79	pub padding: usize,80}8182impl TraceFormat for CompactFormat {83	fn write_trace(84		&self,85		out: &mut dyn std::fmt::Write,86		evaluation_state: &EvaluationState,87		error: &LocError,88	) -> Result<(), std::fmt::Error> {89		write!(out, "{}", error.error())?;90		if let Error::ImportSyntaxError {91			path,92			source_code,93			error,94		} = error.error()95		{96			writeln!(out)?;97			use std::fmt::Write;98			let mut n = self.resolver.resolve(path);99			let mut offset = error.location.offset;100			let is_eof = if offset >= source_code.len() {101				offset = source_code.len() - 1;102				true103			} else {104				false105			};106			let mut location = offset_to_location(source_code, &[offset])107				.into_iter()108				.next()109				.unwrap();110			if is_eof {111				location.column += 1;112			}113114			write!(n, ":").unwrap();115			print_code_location(&mut n, &location, &location).unwrap();116			write!(out, "{:<p$}{}", "", n, p = self.padding,)?;117		}118		let file_names = error119			.trace()120			.0121			.iter()122			.map(|el| &el.location)123			.map(|location| {124				use std::fmt::Write;125				if let Some(location) = location {126					let mut resolved_path = self.resolver.resolve(&location.0);127					// TODO: Process all trace elements first128					let location = evaluation_state129						.map_source_locations(&location.0, &[location.1, location.2]);130					write!(resolved_path, ":").unwrap();131					print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();132					write!(resolved_path, ":").unwrap();133					Some(resolved_path)134				} else {135					None136				}137			})138			.collect::<Vec<_>>();139		let align = file_names140			.iter()141			.flatten()142			.map(|e| e.len())143			.max()144			.unwrap_or(0);145		for (el, file) in error.trace().0.iter().zip(file_names) {146			writeln!(out)?;147			if let Some(file) = file {148				write!(149					out,150					"{:<p$}{:<w$} {}",151					"",152					file,153					el.desc,154					p = self.padding,155					w = align156				)?;157			} else {158				write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;159			}160		}161		Ok(())162	}163}164165pub struct JsFormat;166impl TraceFormat for JsFormat {167	fn write_trace(168		&self,169		out: &mut dyn std::fmt::Write,170		evaluation_state: &EvaluationState,171		error: &LocError,172	) -> Result<(), std::fmt::Error> {173		write!(out, "{}", error.error())?;174		for item in error.trace().0.iter() {175			writeln!(out)?;176			let desc = &item.desc;177			if let Some(source) = &item.location {178				let start_end =179					evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);180181				write!(182					out,183					"    at {} ({}:{}:{})",184					desc,185					source.0.to_str().unwrap(),186					start_end[0].line,187					start_end[0].column,188				)?;189			} else {190				write!(out, "    during {}", desc)?;191			}192		}193		Ok(())194	}195}196197/// rustc-like trace displaying198#[cfg(feature = "explaining-traces")]199pub struct ExplainingFormat {200	pub resolver: PathResolver,201}202#[cfg(feature = "explaining-traces")]203impl TraceFormat for ExplainingFormat {204	fn write_trace(205		&self,206		out: &mut dyn std::fmt::Write,207		evaluation_state: &EvaluationState,208		error: &LocError,209	) -> Result<(), std::fmt::Error> {210		write!(out, "{}", error.error())?;211		if let Error::ImportSyntaxError {212			path,213			source_code,214			error,215		} = error.error()216		{217			writeln!(out)?;218			let offset = error.location.offset;219			let location = offset_to_location(source_code, &[offset])220				.into_iter()221				.next()222				.unwrap();223			let mut end_location = location.clone();224			end_location.offset += 1;225226			self.print_snippet(227				out,228				source_code,229				path,230				&location,231				&end_location,232				"syntax error",233			)?;234		}235		let trace = &error.trace();236		for item in trace.0.iter() {237			writeln!(out)?;238			let desc = &item.desc;239			if let Some(source) = &item.location {240				let start_end =241					evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);242				self.print_snippet(243					out,244					&evaluation_state.get_source(&source.0).unwrap(),245					&source.0,246					&start_end[0],247					&start_end[1],248					desc,249				)?;250			} else {251				write!(out, "{}", desc)?;252			}253		}254		Ok(())255	}256}257258impl ExplainingFormat {259	fn print_snippet(260		&self,261		out: &mut dyn std::fmt::Write,262		source: &str,263		origin: &Path,264		start: &CodeLocation,265		end: &CodeLocation,266		desc: &str,267	) -> Result<(), std::fmt::Error> {268		use annotate_snippets::{269			display_list::{DisplayList, FormatOptions},270			snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},271		};272273		let source_fragment: String = source274			.chars()275			.skip(start.line_start_offset)276			.take(end.line_end_offset - end.line_start_offset)277			.collect();278279		let origin = self.resolver.resolve(origin);280		let snippet = Snippet {281			opt: FormatOptions {282				color: true,283				..Default::default()284			},285			title: None,286			footer: vec![],287			slices: vec![Slice {288				source: &source_fragment,289				line_start: start.line,290				origin: Some(&origin),291				fold: false,292				annotations: vec![SourceAnnotation {293					label: desc,294					annotation_type: AnnotationType::Error,295					range: (296						start.offset - start.line_start_offset,297						(end.offset - start.line_start_offset).min(source_fragment.len()),298					),299				}],300			}],301		};302303		let dl = DisplayList::from(snippet);304		write!(out, "{}", dl)?;305306		Ok(())307	}308}
after · crates/jrsonnet-evaluator/src/trace/mod.rs
1mod location;23use crate::{error::Error, EvaluationState, LocError};4pub use location::*;5use std::path::{Path, PathBuf};67/// The way paths should be displayed8pub enum PathResolver {9	/// Only filename10	FileName,11	/// Absolute path12	Absolute,13	/// Path relative to base directory14	Relative(PathBuf),15}1617impl PathResolver {18	pub fn resolve(&self, from: &Path) -> String {19		match self {20			Self::FileName => from.file_name().unwrap().to_string_lossy().into_owned(),21			Self::Absolute => from.to_string_lossy().into_owned(),22			Self::Relative(base) => {23				if from.is_relative() {24					return from.to_string_lossy().into_owned();25				}26				pathdiff::diff_paths(from, base)27					.unwrap()28					.to_string_lossy()29					.into_owned()30			}31		}32	}33}3435/// Implements pretty-printing of traces36pub trait TraceFormat {37	fn write_trace(38		&self,39		out: &mut dyn std::fmt::Write,40		evaluation_state: &EvaluationState,41		error: &LocError,42	) -> Result<(), std::fmt::Error>;43	// fn print_trace(44	// 	&self,45	// 	evaluation_state: &EvaluationState,46	// 	error: &LocError,47	// ) -> Result<(), std::fmt::Error> {48	// 	self.write_trace(&mut std::fmt::stdout(), evaluation_state, error)49	// }50}5152fn print_code_location(53	out: &mut impl std::fmt::Write,54	start: &CodeLocation,55	end: &CodeLocation,56) -> Result<(), std::fmt::Error> {57	if start.line == end.line {58		if start.column == end.column {59			write!(out, "{}:{}", start.line, end.column - 1)?;60		} else {61			write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;62		}63	} else {64		write!(65			out,66			"{}:{}-{}:{}",67			start.line,68			end.column.saturating_sub(1),69			start.line,70			end.column71		)?;72	}73	Ok(())74}7576/// vanilla-like jsonnet formatting77pub struct CompactFormat {78	pub resolver: PathResolver,79	pub padding: usize,80}8182impl TraceFormat for CompactFormat {83	fn write_trace(84		&self,85		out: &mut dyn std::fmt::Write,86		evaluation_state: &EvaluationState,87		error: &LocError,88	) -> Result<(), std::fmt::Error> {89		write!(out, "{}", error.error())?;90		if let Error::ImportSyntaxError {91			path,92			source_code,93			error,94		} = error.error()95		{96			writeln!(out)?;97			use std::fmt::Write;98			let mut n = self.resolver.resolve(path);99			let mut offset = error.location.offset;100			let is_eof = if offset >= source_code.len() {101				offset = source_code.len().saturating_sub(1);102				true103			} else {104				false105			};106			let mut location = offset_to_location(source_code, &[offset])107				.into_iter()108				.next()109				.unwrap();110			if is_eof {111				location.column += 1;112			}113114			write!(n, ":").unwrap();115			print_code_location(&mut n, &location, &location).unwrap();116			write!(out, "{:<p$}{}", "", n, p = self.padding,)?;117		}118		let file_names = error119			.trace()120			.0121			.iter()122			.map(|el| &el.location)123			.map(|location| {124				use std::fmt::Write;125				if let Some(location) = location {126					let mut resolved_path = self.resolver.resolve(&location.0);127					// TODO: Process all trace elements first128					let location = evaluation_state129						.map_source_locations(&location.0, &[location.1, location.2]);130					write!(resolved_path, ":").unwrap();131					print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();132					write!(resolved_path, ":").unwrap();133					Some(resolved_path)134				} else {135					None136				}137			})138			.collect::<Vec<_>>();139		let align = file_names140			.iter()141			.flatten()142			.map(|e| e.len())143			.max()144			.unwrap_or(0);145		for (el, file) in error.trace().0.iter().zip(file_names) {146			writeln!(out)?;147			if let Some(file) = file {148				write!(149					out,150					"{:<p$}{:<w$} {}",151					"",152					file,153					el.desc,154					p = self.padding,155					w = align156				)?;157			} else {158				write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;159			}160		}161		Ok(())162	}163}164165pub struct JsFormat;166impl TraceFormat for JsFormat {167	fn write_trace(168		&self,169		out: &mut dyn std::fmt::Write,170		evaluation_state: &EvaluationState,171		error: &LocError,172	) -> Result<(), std::fmt::Error> {173		write!(out, "{}", error.error())?;174		for item in error.trace().0.iter() {175			writeln!(out)?;176			let desc = &item.desc;177			if let Some(source) = &item.location {178				let start_end =179					evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);180181				write!(182					out,183					"    at {} ({}:{}:{})",184					desc,185					source.0.to_str().unwrap(),186					start_end[0].line,187					start_end[0].column,188				)?;189			} else {190				write!(out, "    during {}", desc)?;191			}192		}193		Ok(())194	}195}196197/// rustc-like trace displaying198#[cfg(feature = "explaining-traces")]199pub struct ExplainingFormat {200	pub resolver: PathResolver,201}202#[cfg(feature = "explaining-traces")]203impl TraceFormat for ExplainingFormat {204	fn write_trace(205		&self,206		out: &mut dyn std::fmt::Write,207		evaluation_state: &EvaluationState,208		error: &LocError,209	) -> Result<(), std::fmt::Error> {210		write!(out, "{}", error.error())?;211		if let Error::ImportSyntaxError {212			path,213			source_code,214			error,215		} = error.error()216		{217			writeln!(out)?;218			let offset = error.location.offset;219			let location = offset_to_location(source_code, &[offset])220				.into_iter()221				.next()222				.unwrap();223			let mut end_location = location.clone();224			end_location.offset += 1;225226			self.print_snippet(227				out,228				source_code,229				path,230				&location,231				&end_location,232				"syntax error",233			)?;234		}235		let trace = &error.trace();236		for item in trace.0.iter() {237			writeln!(out)?;238			let desc = &item.desc;239			if let Some(source) = &item.location {240				let start_end =241					evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);242				self.print_snippet(243					out,244					&evaluation_state.get_source(&source.0).unwrap(),245					&source.0,246					&start_end[0],247					&start_end[1],248					desc,249				)?;250			} else {251				write!(out, "{}", desc)?;252			}253		}254		Ok(())255	}256}257258impl ExplainingFormat {259	fn print_snippet(260		&self,261		out: &mut dyn std::fmt::Write,262		source: &str,263		origin: &Path,264		start: &CodeLocation,265		end: &CodeLocation,266		desc: &str,267	) -> Result<(), std::fmt::Error> {268		use annotate_snippets::{269			display_list::{DisplayList, FormatOptions},270			snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},271		};272273		let source_fragment: String = source274			.chars()275			.skip(start.line_start_offset)276			.take(end.line_end_offset - end.line_start_offset)277			.collect();278279		let origin = self.resolver.resolve(origin);280		let snippet = Snippet {281			opt: FormatOptions {282				color: true,283				..Default::default()284			},285			title: None,286			footer: vec![],287			slices: vec![Slice {288				source: &source_fragment,289				line_start: start.line,290				origin: Some(&origin),291				fold: false,292				annotations: vec![SourceAnnotation {293					label: desc,294					annotation_type: AnnotationType::Error,295					range: (296						start.offset - start.line_start_offset,297						(end.offset - start.line_start_offset).min(source_fragment.len()),298					),299				}],300			}],301		};302303		let dl = DisplayList::from(snippet);304		write!(out, "{}", dl)?;305306		Ok(())307	}308}
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -23,6 +23,7 @@
   trace:: $intrinsic(trace),
   id:: $intrinsic(id),
   parseJson:: $intrinsic(parseJson),
+  parseYaml:: $intrinsic(parseYaml),
 
   log:: $intrinsic(log),
   pow:: $intrinsic(pow),