Compare commits

...

11 Commits

Author SHA1 Message Date
Taus
263aecf553 Yeast: Use a special field for children with no field 2023-11-29 17:39:25 +00:00
Harry Maclean
e5cc540475 wip: tree output 2023-11-18 17:17:38 +00:00
Harry Maclean
b446982fae Merge pull request #14949 from github/hmac-desugar
yeast: update debug format to be more readable
2023-11-29 11:30:16 +00:00
Harry Maclean
050a18f240 Update debug format to be more readable 2023-11-29 11:21:15 +00:00
Harry Maclean
658fa944ed Merge pull request #14947 from hmac/hmac-desugar-parse-input
yeast: parse input into the AST
2023-11-29 10:29:35 +00:00
Harry Maclean
03473c2147 Parse input into the AST 2023-11-29 10:22:33 +00:00
Harry Maclean
34b21af46f Add basic AST type
We store the AST as a vector of nodes, with ids as indexes into the
vector.
2023-11-29 09:50:13 +00:00
Harry Maclean
cef3ce1cde add a basic failing test 2023-11-28 15:32:16 +00:00
Harry Maclean
17234d3939 Add rust project skeleton 2023-11-28 15:01:14 +00:00
Harry Maclean
9b6d00b737 Add basic nix config 2023-11-28 15:00:32 +00:00
Harry Maclean
267f9acc4c yeast initial commit 2023-11-28 14:40:26 +00:00
14 changed files with 949 additions and 0 deletions

1
shared/yeast/.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

1
shared/yeast/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

0
shared/yeast/.gitkeep Normal file
View File

181
shared/yeast/Cargo.lock generated Normal file
View File

@@ -0,0 +1,181 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "serde"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tree-sitter"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
dependencies = [
"cc",
"regex",
]
[[package]]
name = "tree-sitter-python"
version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c93b1b1fbd0d399db3445f51fd3058e43d0b4dcff62ddbdb46e66550978aa5"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-ruby"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ac30cbb1560363ae76e1ccde543d6d99087421e228cc47afcec004b86bb711a"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "yeast"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"tree-sitter",
"tree-sitter-python",
"tree-sitter-ruby",
]

13
shared/yeast/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "yeast"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
tree-sitter = "0.20.10"
[dev-dependencies]
tree-sitter-ruby = "0.20.0"
tree-sitter-python = "0.20.4"

85
shared/yeast/flake.lock generated Normal file
View File

@@ -0,0 +1,85 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1697730408,
"narHash": "sha256-Ww//zzukdTrwTrCUkaJA/NsaLEfUfQpWZXBdXBYfhak=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ff0a5a776b56e0ca32d47a4a47695452ec7f7d80",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1697767917,
"narHash": "sha256-9+FjCVE1Y7iUKohBF43yD05KoQB+FPcw/XL2rlKkjqY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "679ea0878edc749f23516ea6d7ffa974c6304bf5",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

31
shared/yeast/flake.nix Normal file
View File

@@ -0,0 +1,31 @@
{
description = "YEAST elaborates abstract syntax trees";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-utils.follows = "flake-utils";
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
rust = rust-overlay.packages.${system}.rust;
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
pkgs.tree-sitter
rust
rust-analyzer
libiconv
];
};
});
}

302
shared/yeast/src/lib.rs Normal file
View File

@@ -0,0 +1,302 @@
use std::collections::BTreeMap;
use serde::Serialize;
use serde_json::{json, Value};
pub mod print;
mod range;
mod visitor;
/// Node ids are indexes into the arena
type Id = usize;
/// Field and Kind ids are provided by tree-sitter
type FieldId = u16;
type KindId = u16;
pub const CHILD_FIELD: u16 = u16::MAX;
const CHILD_FIELD_NAME: &str = "child";
#[derive(Debug)]
pub struct Cursor<'a> {
ast: &'a Ast,
parents: Vec<Id>,
node: &'a Node,
locations: Vec<(usize, usize)>,
}
impl<'a> Cursor<'a> {
pub fn new(ast: &'a Ast) -> Self {
let node = ast.get_node(0).unwrap();
Self {
ast,
parents: vec![],
node,
locations: vec![],
}
}
pub fn node(&mut self) -> &'a Node {
self.node
}
pub fn field_id(&self) -> Option<FieldId> {
let parent_id = self.parents.last()?;
let (field_index, _) = self.locations.last().unwrap();
let (field_id, _) = self
.ast
.get_node(*parent_id)
.unwrap()
.fields
.iter()
.nth(*field_index)?;
Some(*field_id)
}
pub fn goto_first_child(&mut self) -> bool {
let location = (0, 0);
let parent = self.node.id;
self.node = match self.node.fields.iter().next() {
Some((_field_id, child_ids)) if !child_ids.is_empty() => {
self.ast.get_node(child_ids[0]).unwrap()
}
_ => return false,
};
self.locations.push(location);
self.parents.push(parent);
true
}
pub fn goto_next_sibling(&mut self) -> bool {
let (node_id, location) = match self.parents.last() {
None => {
return false;
}
Some(parent) => {
let parent = self.ast.get_node(*parent).unwrap();
let (field_index, child_index) = self.locations.pop().unwrap();
if field_index == parent.fields.len() {
return false;
} else {
let (_field_id, children) = parent.fields.iter().nth(field_index).unwrap();
if child_index == children.len() - 1 {
// end of field
let location = (field_index + 1, 0);
let node_id = match parent.fields.iter().nth(field_index + 1) {
Some((_field_id, children)) => children[0],
None => return false,
};
(node_id, location)
} else {
let loc = (field_index, child_index + 1);
let (_, children) = parent.fields.iter().nth(field_index).unwrap();
let node_id = children[child_index + 1];
(node_id, loc)
}
}
}
};
self.locations.push(location);
self.node = self.ast.get_node(node_id).unwrap();
true
}
pub fn goto_parent(&mut self) -> bool {
match self.parents.pop() {
None => false,
Some(parent) => {
self.node = self.ast.get_node(parent).unwrap();
true
}
}
}
}
/// Our AST
#[derive(PartialEq, Eq, Debug)]
pub struct Ast {
nodes: Vec<Node>,
language: tree_sitter::Language,
}
impl Ast {
/// Construct an AST from a TS tree
pub fn from_tree(language: tree_sitter::Language, tree: &tree_sitter::Tree) -> Self {
let mut visitor = visitor::Visitor::new(language);
visitor.visit(tree);
visitor.build()
}
pub fn nodes(&self) -> &[Node] {
&self.nodes
}
pub fn get_node(&self, id: Id) -> Option<&Node> {
self.nodes.get(id)
}
pub fn print(&self, source: &str) -> Value {
let root = self.nodes().first().unwrap();
serde_json::to_value(self.print_node(root, source)).unwrap()
}
fn field_name_for_id(&self, id: FieldId) -> Option<&str> {
if id == CHILD_FIELD {
Some(CHILD_FIELD_NAME)
} else {
self.language.field_name_for_id(id)
}
}
/// Print a node for debugging
fn print_node(&self, node: &Node, source: &str) -> Value {
let children: Vec<Value> = node
.fields
.get(&CHILD_FIELD)
.unwrap_or(&vec![])
.iter()
.map(|id| self.print_node(self.get_node(*id).unwrap(), source))
.collect();
let mut fields = BTreeMap::new();
if !children.is_empty() {
fields.insert("rest", children);
}
for (field_id, nodes) in &node.fields {
if field_id == &CHILD_FIELD {
continue;
}
let field_name = self.field_name_for_id(*field_id).unwrap();
let nodes: Vec<Value> = nodes
.iter()
.map(|id| self.print_node(self.get_node(*id).unwrap(), source))
.collect();
fields.insert(field_name, nodes);
}
let mut value = BTreeMap::new();
let kind = self.language.node_kind_for_id(node.kind).unwrap();
let content = match node.content {
NodeContent::Range(range) => {
let len = range.end_byte - range.start_byte;
let end = range.start_byte + len;
source.as_bytes()[range.start_byte..end]
.iter()
.map(|b| *b as char)
.collect()
}
NodeContent::String(s) => s.to_string(),
};
if fields.is_empty() {
value.insert(kind, json!(content));
} else {
let mut fields: BTreeMap<_, _> =
fields.into_iter().map(|(k, v)| (k, json!(v))).collect();
fields.insert("content", json!(content));
value.insert(kind, json!(fields));
}
json!(value)
}
/// Return an example AST, for testing and to fill implementation gaps
pub fn example(language: tree_sitter::Language) -> Self {
// x = 1
Self {
language,
nodes: vec![
// assignment
Node {
id: 0,
kind: 276,
fields: {
let mut map = BTreeMap::new();
map.insert(18, vec![1]);
map.insert(28, vec![3]);
map
},
content: NodeContent::String("x = 1"),
},
// identifier
Node {
id: 1,
kind: 1,
fields: BTreeMap::new(),
content: NodeContent::String("x"),
},
// "="
Node {
id: 2,
kind: 17,
fields: BTreeMap::new(),
content: NodeContent::String("="),
},
// integer
Node {
id: 3,
kind: 110,
fields: BTreeMap::new(),
content: NodeContent::String("1"),
},
],
}
}
}
/// A node in our AST
#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
pub struct Node {
id: Id,
kind: KindId,
fields: BTreeMap<FieldId, Vec<Id>>,
content: NodeContent,
}
/// The contents of a node is either a range in the original source file,
/// or a new string if the node is synthesized.
#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
enum NodeContent {
Range(#[serde(with = "range::Range")] tree_sitter::Range),
String(&'static str),
}
impl From<&'static str> for NodeContent {
fn from(value: &'static str) -> Self {
NodeContent::String(value)
}
}
impl From<tree_sitter::Range> for NodeContent {
fn from(value: tree_sitter::Range) -> Self {
NodeContent::Range(value)
}
}
pub struct Query {}
impl Query {
pub fn new() -> Self {
Self {}
}
}
pub struct Rule {}
impl Rule {
pub fn new(query: Query, transform: impl Fn(Match) -> Ast) -> Self {
Self {}
}
}
pub struct Match {}
pub struct Runner {
language: tree_sitter::Language,
}
impl Runner {
pub fn new(language: tree_sitter::Language, rules: Vec<Rule>) -> Self {
Self { language }
}
pub fn run(&self, input: &str) -> Ast {
// Parse the input into an AST
let mut parser = tree_sitter::Parser::new();
parser.set_language(self.language).unwrap();
let tree = parser.parse(input, None).unwrap();
let ast = Ast::from_tree(self.language, &tree);
ast
}
}

34
shared/yeast/src/print.rs Normal file
View File

@@ -0,0 +1,34 @@
use crate::{Cursor, Node};
pub struct Printer {}
impl Printer {
pub fn visit<'a>(&mut self, mut cursor: Cursor<'a>) {
self.enter_node(cursor.node());
let mut recurse = true;
loop {
if recurse && cursor.goto_first_child() {
recurse = self.enter_node(cursor.node());
} else {
self.leave_node(cursor.node());
if cursor.goto_next_sibling() {
recurse = self.enter_node(cursor.node());
} else if cursor.goto_parent() {
recurse = false;
} else {
break;
}
}
}
}
pub fn enter_node(&mut self, node: &Node) -> bool {
println!("enter_node: {:?}", node);
true
}
pub fn leave_node(&mut self, node: &Node) -> bool {
println!("leave_node: {:?}", node);
true
}
}

21
shared/yeast/src/range.rs Normal file
View File

@@ -0,0 +1,21 @@
//! (de)-serialize helpers for tree_sitter::Range
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(remote = "tree_sitter::Point")]
pub struct Point {
pub row: usize,
pub column: usize,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "tree_sitter::Range")]
pub struct Range {
pub start_byte: usize,
pub end_byte: usize,
#[serde(with = "Point")]
pub start_point: tree_sitter::Point,
#[serde(with = "Point")]
pub end_point: tree_sitter::Point,
}

104
shared/yeast/src/visitor.rs Normal file
View File

@@ -0,0 +1,104 @@
use std::collections::BTreeMap;
use tree_sitter::{Language, Tree};
use crate::{Ast, Id, Node, NodeContent, CHILD_FIELD};
#[derive(Debug)]
struct VisitorNode {
inner: Node,
parent: Option<Id>,
}
/// A type that can walk a TS tree and produce an `Ast`.
#[derive(Debug)]
pub(crate) struct Visitor {
nodes: Vec<VisitorNode>,
current: Option<Id>,
language: Language,
}
impl Visitor {
pub fn new(language: Language) -> Self {
Self {
nodes: Vec::new(),
current: None,
language,
}
}
pub fn visit(&mut self, tree: &Tree) {
let cursor = &mut tree.walk();
self.enter_node(cursor.node());
let mut recurse = true;
loop {
if recurse && cursor.goto_first_child() {
recurse = self.enter_node(cursor.node());
} else {
self.leave_node(cursor.field_name(), cursor.node());
if cursor.goto_next_sibling() {
recurse = self.enter_node(cursor.node());
} else if cursor.goto_parent() {
recurse = false;
} else {
break;
}
}
}
}
pub fn build(self) -> Ast {
Ast {
language: self.language,
nodes: self.nodes.into_iter().map(|n| n.inner).collect(),
}
}
fn add_node(&mut self, n: tree_sitter::Node<'_>, content: NodeContent, is_named: bool) -> Id {
let id = self.nodes.len();
self.nodes.push(VisitorNode {
inner: Node {
id,
kind: self.language.id_for_node_kind(n.kind(), is_named),
content,
fields: BTreeMap::new(),
},
parent: self.current,
});
id
}
fn enter_node(&mut self, node: tree_sitter::Node<'_>) -> bool {
let id = self.add_node(node, node.range().into(), node.is_named());
self.current = Some(id);
true
}
fn leave_node(&mut self, field_name: Option<&'static str>, _node: tree_sitter::Node<'_>) {
let node = self.current.map(|i| &self.nodes[i]).unwrap();
let node_id = node.inner.id;
let node_parent = node.parent;
if let Some(parent_id) = node.parent {
let parent = self.nodes.get_mut(parent_id).unwrap();
if let Some(field) = field_name {
let field_id = self.language.field_id_for_name(field).unwrap();
parent
.inner
.fields
.entry(field_id)
.or_default()
.push(node_id);
} else {
parent
.inner
.fields
.entry(CHILD_FIELD)
.or_default()
.push(node_id);
}
}
self.current = node_parent;
}
}

View File

@@ -0,0 +1,68 @@
{
"program": {
"content": "x, y, z = foo()\n",
"rest": [
{
"assignment": {
"content": "x, y, z = foo()",
"left": [
{
"left_assignment_list": {
"content": "x, y, z",
"rest": [
{
"identifier": "x"
},
{
",": ","
},
{
"identifier": "y"
},
{
",": ","
},
{
"identifier": "z"
}
]
}
}
],
"rest": [
{
"=": "="
}
],
"right": [
{
"call": {
"arguments": [
{
"argument_list": {
"content": "()",
"rest": [
{
"(": "("
},
{
")": ")"
}
]
}
}
],
"content": "foo()",
"method": [
{
"identifier": "foo"
}
]
}
}
]
}
}
]
}
}

1
shared/yeast/tests/fixtures/1.rb vendored Normal file
View File

@@ -0,0 +1 @@
x, y, z = foo()

107
shared/yeast/tests/test.rs Normal file
View File

@@ -0,0 +1,107 @@
#![cfg(test)]
use std::path::Path;
use yeast::{print::Printer, *};
#[test]
fn test_ruby_multiple_assignment() {
// We want to convert this
//
// x, y, z = e
//
// into this
//
// tmp = e
// x = tmp[0]
// y = tmp[1]
// z = tmp[2]
// Define a desugaring rule, which is a query together with a transformation.
let rule = {
let query = Query::new();
let transform = |_captures| {
// construct the new tree here maybe
// captures is probably a HashMap from capture name to AST node
Ast::example(tree_sitter_ruby::language())
};
Rule::new(query, transform)
};
let input = "x, y, z = e";
// Construct the thing that runs our desugaring process
let runner = Runner::new(tree_sitter_ruby::language(), vec![rule]);
// Run it on our example
let output = runner.run(input);
// we could create a macro for this
// let expected_output = ast! {
// assignment {
// left: identifier { name: "__tmp" },
// right: identifier { name: "e" },
// },
// assignment {
// left: identifier { name: "x" },
// right: element_reference {
// object: identifier { name: "__tmp" },
// index: integer(0)
// },
// },
// assignment {
// left: identifier { name: "y" },
// right: element_reference {
// object: identifier { name: "__tmp" },
// index: integer(1)
// },
// },
// assignment {
// left: identifier { name: "z" },
// right: element_reference {
// object: identifier { name: "__tmp" },
// index: integer(2)
// },
// },
// };
let expected_output = todo!();
assert_eq!(output, expected_output);
}
#[test]
fn test_parse_input() {
let input = std::fs::read_to_string("tests/fixtures/1.rb").unwrap();
let parsed_expected = std::fs::read_to_string("tests/fixtures/1.parsed.json").unwrap();
let runner = Runner::new(tree_sitter_ruby::language(), vec![]);
let ast = runner.run(&input);
let parsed_actual = serde_json::to_string_pretty(&ast.print(&input)).unwrap();
assert_eq!(parsed_actual, parsed_expected);
}
/// Useful for updating fixtures
/// ```
/// write_expected("tests/fixtures/1.parsed.json", &parsed_actual);
/// ```
fn write_expected<P: AsRef<Path>>(file: P, content: &str) {
use std::io::Write;
std::fs::File::create(file)
.unwrap()
.write_all(content.as_bytes())
.unwrap();
}
#[test]
fn test_output() {
let input = std::fs::read_to_string("tests/fixtures/1.rb").unwrap();
let parsed_expected = std::fs::read_to_string("tests/fixtures/1.parsed.json").unwrap();
let runner = Runner::new(tree_sitter_ruby::language(), vec![]);
let ast = runner.run(&input);
let cursor = Cursor::new(&ast);
let mut printer = Printer {};
printer.visit(cursor);
panic!()
}