use tree_sitter::Node;
use super::helpers::{
classify_arg_node, node_text, parse_assignment, parse_redirect, resolve_node_text,
};
use super::{Arg, ArgMeta, Assignment, List, ListOp, Pipeline, Redirect, SimpleCommand, Statement};
pub(crate) fn convert_arg_node(node: Node, source: &str) -> Arg {
Arg {
text: resolve_node_text(node, source),
meta: classify_arg_node(node, source),
}
}
pub fn convert_program(node: Node, source: &str) -> Statement {
let mut children: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let mut i = 0;
while i < named.len() {
let child = named[i];
if child.is_error() {
if let Some(prev) = children.last_mut() {
merge_error_into_statement(prev, child, source);
} else {
children.push(Statement::Opaque(node_text(child, source).to_string()));
}
} else {
children.push(convert_node(child, source));
}
i += 1;
}
match children.len() {
0 => Statement::Opaque(String::new()),
1 => children.into_iter().next().unwrap(),
_ => {
let mut iter = children.into_iter();
let first = Box::new(iter.next().unwrap());
let rest: Vec<(ListOp, Statement)> = iter.map(|s| (ListOp::Semi, s)).collect();
Statement::List(List { first, rest })
}
}
}
fn merge_error_into_statement(stmt: &mut Statement, error_node: Node, source: &str) {
let error_text = node_text(error_node, source).to_string();
match stmt {
Statement::SimpleCommand(cmd) => {
cmd.argv.push(Arg {
text: error_text,
meta: ArgMeta::UnsafeString,
});
}
Statement::Pipeline(pipeline) => {
if let Some(last) = pipeline.stages.last_mut() {
merge_error_into_statement(last, error_node, source);
}
}
_ => {
}
}
}
fn convert_node(node: Node, source: &str) -> Statement {
match node.kind() {
"command" => convert_command(node, source),
"pipeline" => convert_pipeline(node, source),
"list" => convert_list(node, source),
"subshell" => convert_subshell(node, source),
"command_substitution" => convert_command_substitution(node, source),
"redirected_statement" => convert_redirected_statement(node, source),
"negated_command" => convert_negated_command(node, source),
"variable_assignment" => convert_bare_assignment(node, source),
"variable_assignments" => convert_bare_assignment(node, source),
"for_statement" | "c_style_for_statement" => convert_for_statement(node, source),
"while_statement" => convert_while_statement(node, source),
"if_statement" => convert_if_statement(node, source),
"case_statement" => convert_case_statement(node, source),
"compound_statement" => convert_compound_statement(node, source),
"function_definition" => convert_function_definition(node, source),
"declaration_command" => convert_declaration_command(node, source),
"unset_command" => convert_unset_command(node, source),
"test_command" => convert_test_command(node, source),
"comment" => Statement::Empty,
_ => Statement::Opaque(node_text(node, source).to_string()),
}
}
fn collect_descendant_substitutions(node: Node, source: &str, out: &mut Vec<Statement>) {
if node.kind() == "command_substitution" || node.kind() == "process_substitution" {
out.push(convert_command_substitution(node, source));
return;
}
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
collect_descendant_substitutions(child, source, out);
}
}
pub fn collect_descendant_substitutions_pub(node: Node, source: &str, out: &mut Vec<Statement>) {
collect_descendant_substitutions(node, source, out);
}
fn convert_command(node: Node, source: &str) -> Statement {
let mut name: Option<String> = None;
let mut argv: Vec<Arg> = Vec::new();
let mut redirects: Vec<Redirect> = Vec::new();
let mut assignments: Vec<Assignment> = Vec::new();
let mut embedded_substitutions: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"command_name" => {
name = Some(resolve_node_text(child, source));
}
"command_substitution" => {
argv.push(convert_arg_node(child, source));
embedded_substitutions.push(convert_command_substitution(child, source));
}
"word" | "string" | "raw_string" | "number" | "concatenation" | "simple_expansion"
| "expansion" => {
argv.push(convert_arg_node(child, source));
collect_descendant_substitutions(child, source, &mut embedded_substitutions);
}
"variable_assignment" => {
let (assignment, subs) = parse_assignment(child, source);
assignments.push(assignment);
embedded_substitutions.extend(subs);
}
"file_redirect" | "heredoc_redirect" | "herestring_redirect" => {
let (redirect, subs) = parse_redirect(child, source);
redirects.push(redirect);
embedded_substitutions.extend(subs);
}
_ => {
argv.push(convert_arg_node(child, source));
collect_descendant_substitutions(child, source, &mut embedded_substitutions);
}
}
}
Statement::SimpleCommand(SimpleCommand {
name,
argv,
redirects,
assignments,
embedded_substitutions,
})
}
fn convert_pipeline(node: Node, source: &str) -> Statement {
let mut stages: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
stages.push(convert_node(child, source));
}
Statement::Pipeline(Pipeline {
stages,
negated: false,
})
}
fn convert_list(node: Node, source: &str) -> Statement {
let mut items: Vec<Statement> = Vec::new();
let mut operators: Vec<ListOp> = Vec::new();
let child_count = node.child_count();
for i in 0..child_count as u32 {
let child = node.child(i).unwrap();
if child.is_named() {
items.push(convert_node(child, source));
} else {
let text = node_text(child, source);
match text {
"&&" => operators.push(ListOp::And),
"||" => operators.push(ListOp::Or),
";" => operators.push(ListOp::Semi),
_ => {}
}
}
}
if items.is_empty() {
return Statement::Opaque(node_text(node, source).to_string());
}
let mut iter = items.into_iter();
let first = Box::new(iter.next().unwrap());
let rest: Vec<(ListOp, Statement)> = operators.into_iter().zip(iter).collect();
Statement::List(List { first, rest })
}
fn convert_subshell(node: Node, source: &str) -> Statement {
let mut cursor = node.walk();
let inner = node
.named_children(&mut cursor)
.next()
.map(|child| convert_node(child, source))
.unwrap_or_else(|| Statement::Opaque(node_text(node, source).to_string()));
Statement::Subshell(Box::new(inner))
}
pub fn convert_command_substitution(node: Node, source: &str) -> Statement {
let mut cursor = node.walk();
let inner = node
.named_children(&mut cursor)
.next()
.map(|child| convert_node(child, source))
.unwrap_or_else(|| Statement::Opaque(node_text(node, source).to_string()));
Statement::CommandSubstitution(Box::new(inner))
}
fn convert_redirected_statement(node: Node, source: &str) -> Statement {
let mut body: Option<Statement> = None;
let mut redirects: Vec<Redirect> = Vec::new();
let mut redirect_substitutions: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"file_redirect" | "heredoc_redirect" | "herestring_redirect" => {
let (redirect, subs) = parse_redirect(child, source);
redirects.push(redirect);
redirect_substitutions.extend(subs);
}
_ => {
if body.is_none() {
body = Some(convert_node(child, source));
}
}
}
}
let mut stmt = body.unwrap_or_else(|| Statement::Opaque(node_text(node, source).to_string()));
if let Statement::SimpleCommand(ref mut cmd) = stmt {
if !redirects.is_empty() {
cmd.redirects.extend(redirects);
}
cmd.embedded_substitutions.extend(redirect_substitutions);
} else {
if !redirects.is_empty() {
inject_redirects_into_leaves(&mut stmt, &redirects);
}
if !redirect_substitutions.is_empty() {
let mut parts = vec![stmt];
parts.extend(redirect_substitutions);
return wrap_as_list(parts);
}
}
stmt
}
fn inject_redirects_into_leaves(stmt: &mut Statement, redirects: &[Redirect]) {
match stmt {
Statement::SimpleCommand(cmd) => cmd.redirects.extend_from_slice(redirects),
Statement::Pipeline(p) => {
for stage in &mut p.stages {
inject_redirects_into_leaves(stage, redirects);
}
}
Statement::List(l) => {
inject_redirects_into_leaves(&mut l.first, redirects);
for (_, s) in &mut l.rest {
inject_redirects_into_leaves(s, redirects);
}
}
Statement::Subshell(inner) | Statement::CommandSubstitution(inner) => {
inject_redirects_into_leaves(inner, redirects);
}
Statement::Opaque(_) | Statement::Empty => {}
}
}
fn convert_negated_command(node: Node, source: &str) -> Statement {
let mut cursor = node.walk();
let inner = node
.named_children(&mut cursor)
.next()
.map(|child| convert_node(child, source))
.unwrap_or_else(|| Statement::Opaque(node_text(node, source).to_string()));
match inner {
Statement::Pipeline(mut p) => {
p.negated = true;
Statement::Pipeline(p)
}
other => Statement::Pipeline(Pipeline {
stages: vec![other],
negated: true,
}),
}
}
fn convert_bare_assignment(node: Node, source: &str) -> Statement {
let mut assignments = Vec::new();
let mut embedded_substitutions = Vec::new();
if node.kind() == "variable_assignment" {
let (assignment, subs) = parse_assignment(node, source);
assignments.push(assignment);
embedded_substitutions.extend(subs);
} else {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "variable_assignment" {
let (assignment, subs) = parse_assignment(child, source);
assignments.push(assignment);
embedded_substitutions.extend(subs);
}
}
}
Statement::SimpleCommand(SimpleCommand {
name: None,
argv: vec![],
redirects: vec![],
assignments,
embedded_substitutions,
})
}
fn convert_declaration_command(node: Node, source: &str) -> Statement {
let keyword = node
.child(0)
.map(|c| node_text(c, source).to_string())
.unwrap_or_default();
let mut argv: Vec<Arg> = Vec::new();
let mut assignments: Vec<Assignment> = Vec::new();
let mut embedded_substitutions: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"variable_assignment" => {
let (assignment, subs) = parse_assignment(child, source);
assignments.push(assignment);
embedded_substitutions.extend(subs);
}
_ => {
argv.push(convert_arg_node(child, source));
collect_descendant_substitutions(child, source, &mut embedded_substitutions);
}
}
}
Statement::SimpleCommand(SimpleCommand {
name: Some(keyword),
argv,
redirects: vec![],
assignments,
embedded_substitutions,
})
}
fn convert_unset_command(node: Node, source: &str) -> Statement {
let keyword = node
.child(0)
.map(|c| node_text(c, source).to_string())
.unwrap_or_default();
let mut argv: Vec<Arg> = Vec::new();
let mut embedded_substitutions: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
argv.push(convert_arg_node(child, source));
collect_descendant_substitutions(child, source, &mut embedded_substitutions);
}
Statement::SimpleCommand(SimpleCommand {
name: Some(keyword),
argv,
redirects: vec![],
assignments: vec![],
embedded_substitutions,
})
}
fn convert_for_statement(node: Node, source: &str) -> Statement {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "do_group" {
return convert_do_group(child, source);
}
}
Statement::Opaque(node_text(node, source).to_string())
}
fn convert_while_statement(node: Node, source: &str) -> Statement {
let mut condition: Option<Statement> = None;
let mut body: Option<Statement> = None;
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"do_group" => {
body = Some(convert_do_group(child, source));
}
_ => {
if condition.is_none() {
condition = Some(convert_node(child, source));
}
}
}
}
match (condition, body) {
(Some(c), Some(b)) => wrap_as_list(vec![c, b]),
(None, Some(b)) => b,
(Some(c), None) => c,
(None, None) => Statement::Opaque(node_text(node, source).to_string()),
}
}
fn convert_if_statement(node: Node, source: &str) -> Statement {
let mut parts: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"elif_clause" | "else_clause" => {
let mut inner_cursor = child.walk();
for inner in child.named_children(&mut inner_cursor) {
parts.push(convert_node(inner, source));
}
}
_ => {
parts.push(convert_node(child, source));
}
}
}
wrap_as_list(parts)
}
fn convert_case_statement(node: Node, source: &str) -> Statement {
let mut parts: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "case_item" {
let mut inner_cursor = child.walk();
for inner in child.named_children(&mut inner_cursor) {
if inner.kind() != "word" && inner.kind() != "concatenation" {
parts.push(convert_node(inner, source));
}
}
}
}
wrap_as_list(parts)
}
fn convert_compound_statement(node: Node, source: &str) -> Statement {
let mut parts: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
parts.push(convert_node(child, source));
}
wrap_as_list(parts)
}
fn convert_function_definition(node: Node, source: &str) -> Statement {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "compound_statement" {
return convert_compound_statement(child, source);
}
}
Statement::Empty
}
fn convert_test_command(node: Node, source: &str) -> Statement {
let mut substitutions: Vec<Statement> = Vec::new();
fn find_substitutions(node: Node, source: &str, out: &mut Vec<Statement>) {
if node.kind() == "command_substitution" {
out.push(convert_command_substitution(node, source));
}
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
find_substitutions(child, source, out);
}
}
find_substitutions(node, source, &mut substitutions);
if substitutions.is_empty() {
Statement::Empty
} else {
wrap_as_list(substitutions)
}
}
fn convert_do_group(node: Node, source: &str) -> Statement {
let mut parts: Vec<Statement> = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
parts.push(convert_node(child, source));
}
wrap_as_list(parts)
}
fn wrap_as_list(parts: Vec<Statement>) -> Statement {
match parts.len() {
0 => Statement::Empty,
1 => parts.into_iter().next().unwrap(),
_ => {
let mut iter = parts.into_iter();
let first = Box::new(iter.next().unwrap());
let rest: Vec<(ListOp, Statement)> = iter.map(|s| (ListOp::Semi, s)).collect();
Statement::List(List { first, rest })
}
}
}