Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better typed api #105

Merged
merged 33 commits into from
Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9f56e12
more types; add match_ast; literalkind
oberblastmeister Oct 1, 2021
61211a3
use rowan AstNode
oberblastmeister Jul 19, 2022
3743c07
transition AST type to Parse type
oberblastmeister Jul 19, 2022
6706b12
implement From for enum AstNodes
oberblastmeister Jul 19, 2022
6590dea
start literal node in string and add comment
oberblastmeister Jul 19, 2022
ae449c1
distinguish between keys and attrs
oberblastmeister Jul 19, 2022
acabaa3
use rowan AstChildren
oberblastmeister Jul 19, 2022
46148ba
fix macro indentation
oberblastmeister Jul 19, 2022
57bb3f1
api improvements
oberblastmeister Jul 19, 2022
8a3ad43
use Root::parse function
oberblastmeister Jul 19, 2022
f9d4281
update tests to make string a literal also
oberblastmeister Jul 19, 2022
91717e2
separate string related stuff from value.rs
oberblastmeister Jul 19, 2022
e2c669f
Update src/ast/expr_ext.rs
oberblastmeister Jul 19, 2022
dc58b01
Update src/ast/expr_ext.rs
oberblastmeister Jul 19, 2022
3f8bfb6
remove value.rs
oberblastmeister Jul 20, 2022
460b07e
delete types.rs
oberblastmeister Jul 20, 2022
ef22d2c
update examples
oberblastmeister Jul 20, 2022
62052fb
Update src/ast/str_util.rs
oberblastmeister Jul 21, 2022
3e6c10f
don't put NODE_STRING under NODE_LITERAL
oberblastmeister Jul 21, 2022
1ea3f7b
add catch all case for macro
oberblastmeister Jul 21, 2022
f0fda83
Better macro syntax
oberblastmeister Jul 21, 2022
9a1cee9
updae list-fns example
oberblastmeister Jul 21, 2022
fcaa745
change token macros
oberblastmeister Jul 21, 2022
4bb1ccd
Update examples/list-fns.rs
oberblastmeister Jul 21, 2022
141ea37
port some tests
oberblastmeister Jul 22, 2022
b0a5d72
port another test
oberblastmeister Jul 22, 2022
d87d171
ok
oberblastmeister Jul 22, 2022
63952e3
update match_ast documentation
oberblastmeister Jul 22, 2022
340fd89
delete stray file
oberblastmeister Jul 22, 2022
059206d
token getters for InheritFrom
oberblastmeister Jul 22, 2022
518df53
node improvments
oberblastmeister Jul 22, 2022
ecd0ca8
rename EntryHolder to HasEntry
oberblastmeister Jul 22, 2022
7ef4dd2
make children_tokens generic
oberblastmeister Jul 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion benches/all-packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ fn all_packages(c: &mut Criterion) {
let mut group = c.benchmark_group("all-packages");
group.throughput(Throughput::Bytes(input.len() as u64));
group.sample_size(30);
group.bench_with_input("all-packages", input, move |b, input| b.iter(|| rnix::parse(input)));
group.bench_with_input("all-packages", input, move |b, input| {
b.iter(|| rnix::Root::parse(input))
});
group.finish();
}

Expand Down
8 changes: 3 additions & 5 deletions examples/dump-ast.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use rnix::types::*;
use std::{env, fs};

fn main() {
Expand All @@ -15,12 +14,11 @@ fn main() {
return;
}
};
let ast = rnix::parse(&content);
let parse = rnix::Root::parse(&content);

for error in ast.errors() {
for error in parse.errors() {
println!("error: {}", error);
}

println!("{}", ast.root().dump());
println!("{:#?}", parse.tree());
}
}
2 changes: 1 addition & 1 deletion examples/error-report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn main() {
return;
}
};
let ast = rnix::parse(&content);
let ast = rnix::Root::parse(&content);
for error in ast.errors() {
let range = match error {
ParseError::Unexpected(range) => range,
Expand Down
5 changes: 2 additions & 3 deletions examples/from-stdin.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use rnix::types::*;
use std::{io, io::Read};

fn main() {
let mut content = String::new();
io::stdin().read_to_string(&mut content).expect("could not read nix from stdin");
let ast = rnix::parse(&content);
let ast = rnix::Root::parse(&content);

for error in ast.errors() {
println!("error: {}", error);
}

println!("{}", ast.root().dump());
println!("{}", ast.tree());
}
105 changes: 64 additions & 41 deletions examples/list-fns.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
use std::{env, error::Error, fs};

use rnix::{types::*, NodeOrToken, SyntaxKind::*, SyntaxNode};
use rnix::{
ast::{self, AstToken, HasEntry},
match_ast, NodeOrToken, SyntaxNode,
};
use rowan::ast::AstNode;

macro_rules! single_match {
($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? => $captured:expr) => {
match $expression {
$( $pattern )|+ $( if $guard )? => Some($captured),
_ => None
}
};
}

fn main() -> Result<(), Box<dyn Error>> {
let file = match env::args().nth(1) {
Expand All @@ -11,28 +24,47 @@ fn main() -> Result<(), Box<dyn Error>> {
}
};
let content = fs::read_to_string(&file)?;
let ast = rnix::parse(&content).as_result()?;
let set = ast.root().inner().and_then(AttrSet::cast).ok_or("root isn't a set")?;

let ast = rnix::Root::parse(&content).ok()?;
let expr = ast.expr().unwrap();
let set = match expr {
ast::Expr::AttrSet(set) => set,
_ => return Err("root isn't a set".into()),
};
for entry in set.entries() {
if let Some(lambda) = entry.value().and_then(Lambda::cast) {
if let Some(attr) = entry.key() {
let ident = attr.path().last().and_then(Ident::cast);
let s = ident.as_ref().map_or_else(|| "error".to_string(), Ident::to_inner_string);
if let ast::Entry::KeyValue(key_value) = entry {
if let Some(ast::Expr::Lambda(lambda)) = key_value.value() {
let key = key_value.key().unwrap();
let ident = key.attrs().last().and_then(|attr| match attr {
ast::Attr::Ident(ident) => Some(ident),
_ => None,
});
let s = ident.as_ref().map_or_else(
|| "error".to_string(),
|ident| ident.ident_token().unwrap().text().to_string(),
);
println!("Function name: {}", s);
if let Some(comment) = find_comment(attr.node().clone()) {
println!("-> Doc: {}", comment);
{
let comments = comments_before(key_value.syntax());
if !comments.is_empty() {
println!("--> Doc: {comments}");
}
}

let mut value = Some(lambda);
while let Some(lambda) = value {
let ident = lambda.arg().and_then(Ident::cast);
let s = ident.as_ref().map_or_else(|| "error".to_string(), Ident::to_inner_string);
println!("-> Arg: {}", s);
if let Some(comment) = lambda.arg().and_then(find_comment) {
println!("--> Doc: {}", comment);
let s = lambda
.param()
.as_ref()
.map_or_else(|| "error".to_string(), |param| param.to_string());
println!("-> Param: {}", s);
{
let comments = comments_before(lambda.syntax());
if !comments.is_empty() {
println!("--> Doc: {comments}");
}
}
value = lambda.body().and_then(Lambda::cast);
value =
single_match!(lambda.body().unwrap(), ast::Expr::Lambda(lambda) => lambda);
}
println!();
}
Expand All @@ -41,32 +73,23 @@ fn main() -> Result<(), Box<dyn Error>> {

Ok(())
}
fn find_comment(node: SyntaxNode) -> Option<String> {
let mut node = NodeOrToken::Node(node);
let mut comments = Vec::new();
loop {
loop {
if let Some(new) = node.prev_sibling_or_token() {
node = new;
break;
} else {
node = NodeOrToken::Node(node.parent()?);
}
}

match node.kind() {
TOKEN_COMMENT => match &node {
NodeOrToken::Token(token) => comments.push(token.text().to_string()),
NodeOrToken::Node(_) => unreachable!(),
fn comments_before(node: &SyntaxNode) -> String {
node.siblings_with_tokens(rowan::Direction::Prev)
// rowan always returns the first node for some reason
.skip(1)
.map_while(|element| match element {
NodeOrToken::Token(token) => match_ast! {
match token {
ast::Comment(it) => Some(Some(it)),
ast::Whitespace(_) => Some(None),
_ => None,
}
},
t if t.is_trivia() => (),
_ => break,
}
}
let doc = comments
.iter()
.map(|it| it.trim_start_matches('#').trim())
_ => None,
})
.flatten()
.map(|s| s.text().trim().to_string())
.collect::<Vec<_>>()
.join("\n ");
Some(doc).filter(|it| !it.is_empty())
.join("\n ")
}
2 changes: 1 addition & 1 deletion examples/preserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ fn main() {
return;
}
};
print!("{}", rnix::parse(&content).node());
print!("{}", rnix::Root::parse(&content).tree());
}
}
2 changes: 1 addition & 1 deletion examples/test-nixpkgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn recurse(path: &Path) -> Result<(), Box<dyn Error>> {
return Ok(());
}

let parsed = rnix::parse(&original).as_result()?.node().to_string();
let parsed = rnix::Root::parse(&original).tree().to_string();
if original != parsed {
eprintln!("Original input does not match parsed output!");
println!("Input:");
Expand Down
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import-cargo.url = "github:edolstra/import-cargo";
};

# first comment
# second comment
outputs = { self, nixpkgs, utils, import-cargo }:
{
overlay = final: prev: let
Expand Down
68 changes: 68 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! Provides a type system for the AST, in some sense

mod expr_ext;
mod nodes;
mod operators;
mod str_util;
mod tokens;

use crate::{NixLanguage, SyntaxKind, SyntaxToken};

pub use nodes::*;
pub use operators::{BinOpKind, UnaryOpKind};
pub use str_util::StrPart;
pub use tokens::*;

pub trait AstNode: rowan::ast::AstNode<Language = NixLanguage> {}

impl<T> AstNode for T where T: rowan::ast::AstNode<Language = NixLanguage> {}

pub trait AstToken {
fn can_cast(from: SyntaxKind) -> bool
where
Self: Sized;

/// Cast an untyped token into this strongly-typed token. This will return
/// None if the type was not correct.
fn cast(from: SyntaxToken) -> Option<Self>
where
Self: Sized;

fn syntax(&self) -> &SyntaxToken;
}

mod support {
use rowan::ast::AstChildren;

use super::{AstNode, AstToken};
use crate::{SyntaxElement, SyntaxKind, SyntaxToken};

pub(super) fn nth<N: AstNode, NN: AstNode>(parent: &N, n: usize) -> Option<NN> {
parent.syntax().children().flat_map(NN::cast).nth(n)
}

pub(super) fn children<N: AstNode, NN: AstNode>(parent: &N) -> AstChildren<NN> {
rowan::ast::support::children(parent.syntax())
}

pub(super) fn token<N: AstNode, T: AstToken>(parent: &N) -> Option<T> {
children_tokens(parent).nth(0)
}

/// Token untyped
pub(super) fn token_u<N: AstNode>(parent: &N, kind: SyntaxKind) -> Option<SyntaxToken> {
children_tokens_u(parent).find(|it| it.kind() == kind)
}

pub(super) fn children_tokens<N: AstNode, T: AstToken>(parent: &N) -> impl Iterator<Item = T> {
parent
.syntax()
.children_with_tokens()
.filter_map(SyntaxElement::into_token)
.filter_map(T::cast)
}

pub(super) fn children_tokens_u<N: AstNode>(parent: &N) -> impl Iterator<Item = SyntaxToken> {
parent.syntax().children_with_tokens().filter_map(SyntaxElement::into_token)
}
}
34 changes: 34 additions & 0 deletions src/ast/expr_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::ast::{self, support::*};

// this is a separate type because it mixes tokens and nodes
// for example, a Str is a node because it can contain nested subexpressions but an Integer is a token.
// This means that we have to write it out manually instead of using the macro to create the type for us.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LiteralKind {
oberblastmeister marked this conversation as resolved.
Show resolved Hide resolved
Float(ast::Float),
Integer(ast::Integer),
Path(ast::Path),
Uri(ast::Uri),
}

impl ast::Literal {
pub fn kind(&self) -> LiteralKind {
oberblastmeister marked this conversation as resolved.
Show resolved Hide resolved
if let Some(it) = token(self) {
return LiteralKind::Float(it);
}

if let Some(it) = token(self) {
return LiteralKind::Integer(it);
}

if let Some(it) = token(self) {
return LiteralKind::Path(it);
}

if let Some(it) = token(self) {
return LiteralKind::Uri(it);
}
oberblastmeister marked this conversation as resolved.
Show resolved Hide resolved

unreachable!()
}
}
Loading