Implement a minimal evaluator for attrsets

This commit is contained in:
Val Lorentz 2023-08-20 23:14:11 +02:00
parent f885f66244
commit 1ea44a3cb7
6 changed files with 253 additions and 3 deletions

View File

@ -2,4 +2,14 @@
members = [
"autonix",
"eval",
]
[workspace.dependencies]
# Nix parser
rnix = "0.11.0"
# Immutable data structures; allow efficient manipulation of attribute sets
im = "15.1.0"

View File

@ -4,6 +4,3 @@ version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rnix = "0.11.0"

10
eval/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "autonix_eval"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rnix.workspace = true
im.workspace = true

49
eval/src/attr_set.rs Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Valentin Lorentz
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License version 3, as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
/// Type and functions to manipulate attribute sets
use std::ops::Deref;
use im::HashMap;
use super::NixValue;
/// Representation of a Nix attribute set
#[derive(Clone, Debug, Default, PartialEq)]
pub struct AttrSet(HashMap<String, NixValue>);
impl AttrSet {
pub fn new(entries: HashMap<String, NixValue>) -> AttrSet {
AttrSet(entries)
}
/// Returns the old value, if any. See [`HashMap::insert`]
pub fn insert(&mut self, key: String, value: NixValue) -> Option<NixValue> {
self.0.insert(key, value)
}
#[inline(always)]
pub fn get_or_insert(&mut self, key: String, value: NixValue) -> &mut NixValue {
self.0.entry(key).or_insert(value)
}
}
impl Deref for AttrSet {
type Target = HashMap<String, NixValue>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

139
eval/src/lib.rs Normal file
View File

@ -0,0 +1,139 @@
/*
* Copyright (C) 2023 Valentin Lorentz
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License version 3, as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
use im::HashMap;
use rnix::ast::{Expr, HasEntry};
mod attr_set;
pub use attr_set::AttrSet;
#[derive(Clone, Debug, PartialEq)]
pub enum NixValue {
String(String),
Integer(u64),
Float(f64),
Path(NixPath),
Boolean(bool),
Null,
List(Vec<NixValue>),
AttrSet(AttrSet),
}
impl From<AttrSet> for NixValue {
fn from(set: AttrSet) -> NixValue {
NixValue::AttrSet(set)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct NixPath(Vec<String>);
impl<K: ToString, V: Into<NixValue>, I: IntoIterator<Item = (K, V)>> From<I> for AttrSet {
fn from(items: I) -> AttrSet {
AttrSet::new(
items
.into_iter()
.map(|(k, v)| (k.to_string(), v.into()))
.collect(),
)
}
}
/// An [`Evaluator`] that does not allow any side-effect
pub struct PureEvaluator;
impl Evaluator for PureEvaluator {}
pub trait Evaluator {
fn eval_expr(&mut self, expr: &Expr) -> NixValue {
match expr {
Expr::AttrSet(attr_set) => self.eval_attrset(attr_set),
Expr::Str(s) => NixValue::String(self.eval_str(s)),
Expr::Select(select) => {
let mut value = self.eval_expr(&select.expr().unwrap());
assert_eq!(select.default_expr(), None);
for attr in select.attrpath().unwrap().attrs() {
value = match value {
NixValue::AttrSet(attr_set) => attr_set
.get(&self.eval_attr(&attr))
.expect("No such attribute")
.clone(),
value => panic!("Cannot get attribute from{:?}", value),
};
}
value
}
_ => unimplemented!("{:?}", expr),
}
}
fn eval_attrset(&mut self, attr_set: &rnix::ast::AttrSet) -> NixValue {
let mut attributes = AttrSet::new(HashMap::new());
for entry in attr_set.entries() {
match entry {
rnix::ast::Entry::AttrpathValue(attrpathvalue) => {
let attrpath = match attrpathvalue.attrpath() {
Some(attrpath) => attrpath,
None => unimplemented!("attrpathvalue.attrpath() == None"),
};
let inner_value = match attrpathvalue.value() {
Some(value) => value,
None => unimplemented!("attrpathvalue.value() == None"),
};
let mut value = &mut attributes;
let mut attrpath: Vec<_> = attrpath.attrs().collect();
let last_attr = attrpath.pop().expect("Empty path");
for attr in attrpath {
let attr = self.eval_attr(&attr);
value = match value
.get_or_insert(attr, NixValue::AttrSet(AttrSet::default()))
{
NixValue::AttrSet(attr_set) => attr_set,
value => panic!("Cannot set attribute on {:?}", value),
};
}
let last_attr = self.eval_attr(&last_attr);
value.insert(last_attr, self.eval_expr(&inner_value));
}
_ => unimplemented!("{:?}", entry),
}
}
NixValue::AttrSet(attributes)
}
fn eval_attr(&mut self, attr: &rnix::ast::Attr) -> String {
match attr {
rnix::ast::Attr::Str(s) => self.eval_str(&s),
rnix::ast::Attr::Ident(ident) => ident.to_string(),
_ => unimplemented!("{:?}", attr),
}
}
fn eval_str(&mut self, s: &rnix::ast::Str) -> String {
s.normalized_parts()
.into_iter()
.map(|part| match part {
rnix::ast::InterpolPart::Literal(part) => part,
_ => unimplemented!("{:?}", part),
})
.collect::<Vec<_>>()
.join("")
}
}

45
eval/tests/test_eval.rs Normal file
View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Valentin Lorentz
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License version 3, as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
use autonix_eval::{AttrSet, Evaluator, NixValue, PureEvaluator};
use rnix::ast::Expr;
fn parse(s: &str) -> Expr {
rnix::Root::parse(s).ok().unwrap().expr().unwrap()
}
#[test]
fn test_attrset() {
assert_eq!(
PureEvaluator.eval_expr(&parse(r#"{ a = "Foo"; b = "Bar"; }"#)),
AttrSet::from(vec![
("a", NixValue::String("Foo".to_owned())),
("b", NixValue::String("Bar".to_owned()))
])
.into()
);
}
#[test]
fn test_attrset_get() {
assert_eq!(
PureEvaluator.eval_expr(&parse(r#"{ a = "Foo"; b = "Bar"; }.a"#)),
NixValue::String("Foo".to_owned())
);
assert_eq!(
PureEvaluator.eval_expr(&parse(r#"{ a = "Foo"; b = "Bar"; }.b"#)),
NixValue::String("Bar".to_owned())
);
}