12345678use std::marker::PhantomData;910use linked_hash_map::LinkedHashMap;11use peg::str::LineCol;12use se_impl::MySerialize;13use serde::{Deserialize, Serialize};1415mod de_impl;16mod se_impl;17mod to_string;1819pub use to_string::escape_string;2021#[derive(thiserror::Error, Debug)]22pub enum Error {23 #[error("bad number")]24 BadNumber,25 #[error("expected {0}")]26 Expected(&'static str),27 #[error("parse error")]28 ParseError(#[from] peg::error::ParseError<LineCol>),29 #[error("{0}")]30 Custom(String),31 #[error("io: {0}")]32 Io(#[from] std::io::Error),33 #[error("fmt: {0}")]34 Fmt(#[from] std::fmt::Error),35}3637#[derive(Debug)]38pub enum Value {39 Number(i64),40 String(String),41 Boolean(bool),42 Object(LinkedHashMap<String, Value>),43 Array(Vec<Value>),44 Import(NixImport),45 Null,46}4748#[derive(Debug, Serialize, Deserialize)]49pub struct NixImport {50 #[serde(rename = "__magic_import")]51 import: String,52 53 54 __magic_marker: PhantomData<()>,55}5657impl NixImport {58 pub fn new(import: impl AsRef<str>) -> Self {59 Self {60 import: import.as_ref().to_string(),61 __magic_marker: PhantomData,62 }63 }64}6566fn count_spaces(l: &str) -> usize {67 l.chars().take_while(|&c| c == ' ').count()68}69fn is_significant(l: &str) -> bool {70 count_spaces(l) != l.len()71}7273fn dedent(l: &str, by: usize) -> &str {74 assert!(75 l[0..by.min(l.len())].chars().all(|c| c == ' '),76 "dedent calculation is wrong"77 );78 &l[by.min(l.len())..]79}8081fn process_multiline(lines: Vec<&str>) -> String {82 83 84 let dedent_by = lines85 .iter()86 .copied()87 .filter(|c| is_significant(c))88 .map(count_spaces)89 .min()90 .unwrap_or(0);9192 let mut out = String::new();9394 let mut had_first = false;95 for (i, line) in lines.into_iter().enumerate() {96 97 if i == 0 && !is_significant(line) {98 continue;99 }100 if had_first {101 out.push('\n');102 }103 had_first = true;104 105 for (i, part) in dedent(line, dedent_by).split("'''").enumerate() {106 if i != 0 {107 out.push_str(r#"""""#);108 }109 110 out.push_str(&part.replace("''${", "${").replace("''\\t", "\t"));111 }112 }113114 out115}116117peg::parser! {118pub grammar nixlike() for str {119 rule number() -> i64120 = quiet! { v:$(['0'..='9' | '+' | '-']+) {? v.parse().map_err(|_| "<number>")} } / expected!("<number>")121 rule string_char() -> &'input str122 = "\\\"" { "\"" }123 / "\\\\" { "\\" }124 / "\\n" { "\n" }125 / "\\t" { "\t" }126 / "\\r" { "\r" }127 / "\\$" { "$" }128 / c:$([_]) { c }129 rule string() -> String = singleline_string() / multiline_string();130 rule singleline_string() -> String131 = quiet! { "\"" v:(!"\"" c:string_char() {c})* "\"" { v.into_iter().collect() } } / expected!("<string>")132 pub rule multiline_string() -> String133 = "''"134 135 136 lines:$(("'''" / !"''" [_])*) "''"137 {138 process_multiline(lines.split('\n').collect())139 }140 rule boolean() -> bool141 = quiet! { "true" {true}142 / "false" {false} } / expected!("<boolean>")143 rule indent() -> String144 = quiet! {145 s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-']+) { s.to_owned() }146 / "\"" s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '.']+) "\"" { s.to_owned() }147 } / expected!("<identifier>")148 rule object() -> LinkedHashMap<String, Value>149 = "{" _150 e:(k:indent()++(_ "." _) _ "=" _ v:value() _ ";" _ {(k, v)})*151 "}" {?152 let mut out = LinkedHashMap::new();153 for (k, v) in e {154 let mut map = &mut out;155 for v in k.iter().take(k.len() - 1) {156 map = match map.entry(v.clone()).or_insert_with(|| Value::Object(Default::default())) {157 Value::Object(v) => v,158 _ => return Err("expected object"),159 }160 }161162 let key = k.into_iter().next_back().unwrap();163 if map.contains_key(&key) {164 return Err("can't override object");165 }166 map.insert(key, v);167 }168 Ok(out)169 }170171 rule array() -> Vec<Value>172 = "[" _ v:value()**_ _ "]" {v}173174 rule import() -> NixImport175 = "import" _ s:string() {NixImport::new(s)}176177 rule value() -> Value178 = i:import() { Value::Import(i) }179 / o:object() { Value::Object(o) }180 / a:array() { Value::Array(a) }181 / s:string() { Value::String(s) }182 / "null" { Value::Null }183 / b:boolean() { Value::Boolean(b) }184 / n:number() { Value::Number(n) }185186 pub rule root() -> Value187 = _ v:value() _ { v }188189 rule _()190 = ( quiet!{ [' ' | '\t' | '\n']+ }191 / "#" (!['\n'] [_])* "\n" )*192}193}194195pub fn parse_str<'de, D: Deserialize<'de>>(s: &str) -> Result<D, Error> {196 let value = nixlike::root(s)?;197 D::deserialize(value)198}199200pub fn parse_value<'de, D: Deserialize<'de>>(value: Value) -> Result<D, Error> {201 D::deserialize(value)202}203204pub fn serialize_value_pretty(value: Value) -> String {205 to_string::write_nix(&value)206}207208pub fn serialize<S: Serialize>(value: S) -> Result<String, Error> {209 let value: Value = value.serialize(MySerialize)?;210 Ok(serialize_value_pretty(value))211}212213pub fn format_identifier(i: &str) -> String {214 let mut out = String::new();215 to_string::write_identifier(i, &mut out);216 out217}218219pub fn format_nix(value: &String) -> String {220 221 value.to_owned()222}223224#[cfg(test)]225mod tests {226 use super::*;227228 #[test]229 fn test() {230 assert_eq!(serialize("Hello\nworld").unwrap(), "\"Hello\\nworld\"");231 }232233 #[test]234 fn parse_multiline() {235 236 assert_eq!(nixlike::multiline_string("''\n''").expect("parse"), "");237 238 assert_eq!(nixlike::multiline_string("''\n\n''").expect("parse"), "\n");239 240 assert_eq!(nixlike::multiline_string("''t\n''").expect("parse"), "t\n");241 242 assert_eq!(nixlike::multiline_string("''''").expect("parse"), "");243 244 245 assert_eq!(nixlike::multiline_string("'' ''").expect("parse"), "");246 }247248 #[test]249 fn test_nix_import_roundtrip() {250 let import = NixImport::new("./some/path.nix");251252 let serialized = serialize(&import).expect("serialize");253 assert_eq!(serialized, "import \"./some/path.nix\"");254255 let deserialized: NixImport = parse_str(&serialized).expect("deserialize");256 assert_eq!(deserialized.import, "./some/path.nix");257 }258}