From b37e34fd29535be1dc47e2c4f5a602caf64947d1 Mon Sep 17 00:00:00 2001 From: q1-silver Date: Fri, 23 Dec 2022 16:13:57 +0300 Subject: [PATCH] add: generate ast --- Cargo.lock | 10 ++ Cargo.toml | 3 +- src/ahk.pest | 12 +-- src/main.rs | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 295 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bbeb82..0e43e89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,7 @@ dependencies = [ name = "dog-lexer" version = "0.1.0" dependencies = [ + "litrs", "pest", "pest_derive", ] @@ -70,6 +71,15 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +[[package]] +name = "litrs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b487d13a3f4b465df87895a37b24e364907019afa12d943528df5b7abe0836f1" +dependencies = [ + "proc-macro2", +] + [[package]] name = "once_cell" version = "1.16.0" diff --git a/Cargo.toml b/Cargo.toml index 8a8c161..9b65e8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" [dependencies] pest = "2.5" -pest_derive = "2.5" \ No newline at end of file +pest_derive = "2.5" +litrs = "0.3" \ No newline at end of file diff --git a/src/ahk.pest b/src/ahk.pest index 8d05093..6b4361d 100644 --- a/src/ahk.pest +++ b/src/ahk.pest @@ -1,22 +1,23 @@ WHITESPACE = _{ " " | "\t" | "\n" | "\r" | "\r\n" | "\u{feff}" } -COMMENT = _{ ("/*" ~ (!"*/" ~ ANY)* ~ "*/") | (";" ~ (!"\n" ~ ANY)* ~ "\n") } +COMMENT = _{ ("/*" ~ (!"*/" ~ ANY)* ~ "*/") | (";" ~ (!"\n" ~ ANY)* ~ "\n") } script = _{ SOI ~ (command | globConfig )* ~ EOI} ident = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } integer = @{ ASCII_DIGIT+ } string = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" } +rawLine = @{ (!"\n" ~ ANY)* } command = { trigger ~ "::" ~ body } -globConfig = { "SendMode" ~ "Input" } +globConfig = { "SendMode" ~ "Input" | "#ifWinActive" ~ rawLine } trigger = { modifier* ~ hotkey } -modifier = { "^" | "~" | "!" | "+" } +modifier = { "^" | "~" | "!" | "+" | "$" } hotkey = { ident } body = { expr* ~ ("Return" | EOI)} -expr = { action | assgmtExpr | controlFlow } // | funcCall } +expr = { action | assgmtExpr | controlFlow | funcCall } assgmtExpr = { ident ~ "=" ~ integer } @@ -31,10 +32,9 @@ action = { sendRawAction | sendAction | sleepAction } sendRawAction = { "SendRaw" ~ target } sendAction = { "Send" ~ target } sleepAction = { "Sleep" ~ (integer | variable) } -target = { button | variable | rawLine } +target = _{ button | variable | rawLine } button = { "{" ~ ident ~ "}" } variable = { "%" ~ ident ~ "%" } -rawLine = @{ (!"\n" ~ ANY)* } funcCall = { ident ~ "(" ~ params ~ ")"} params = { param ~ ("," ~ param)* } diff --git a/src/main.rs b/src/main.rs index d45ca1f..674ca76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,285 @@ use pest::Parser; use pest_derive::Parser; fn main() { - let unparsed_file = std::fs::read_to_string("test/BnS.ahk").expect("cannot read file"); - let successful_parse = AHKParser::parse(Rule::script, &unparsed_file).unwrap(); - println!("{:?}", successful_parse); + let mut args = std::env::args(); + let exec_name = args.next().unwrap(); + let unparsed_file = std::fs::read_to_string( + args.next() + .expect(&format!("Usage: {} FILE_TO_PARSE", exec_name)), + ) + .expect("cannot read file"); + + let astnode = parse(&unparsed_file).expect("unsuccessful parse"); + println!("{:?}", &astnode); +} + +#[derive(Debug, Clone)] +pub enum AstNode { + Print(Box), + GlobConfig(Vec), + Integer(i32), + Ident(String), + Trigger { + modifiers: Modifiers, + hotkey: String, + }, + Sleep(usize), + Send(Box), + SendRaw(Box), + Assignment { + ident: String, + expr: Box, + }, + Button(String), + Variable(Box), + RawLine(String), + Body(Vec), + Command { + trigger: Box, + body: Box, + }, + LoopFlow(Box), + IfFlow { + condition: Box, + body: Box, + }, + WhileFlow { + condition: Box, + body: Box, + }, + FuncCall { + ident: String, + params: Box, + }, + Params(Vec), + Break, +} + +#[derive(Debug, Clone)] +pub struct GlobConfigNode { + lhs: String, + rhs: String, +} + +impl From> for GlobConfigNode { + fn from(pair: pest::iterators::Pair) -> Self { + let words = pair.as_str().split_once(' ').unwrap(); + GlobConfigNode { + lhs: words.0.into(), + rhs: words.1.into(), + } + } +} + +#[derive(Debug, Clone)] +pub struct Modifiers { + pub exclusive: bool, + pub control: bool, + pub shift: bool, + pub alt: bool, + pub hook_mode: bool, +} + +impl Modifiers { + fn new() -> Modifiers { + Modifiers { + exclusive: false, + control: false, + shift: false, + alt: false, + hook_mode: false, + } + } } #[derive(Parser)] #[grammar = "ahk.pest"] pub struct AHKParser; + +fn parse(source: &str) -> Result, pest::error::Error> { + let mut ast = vec![]; + let mut glob_config: Vec = vec![]; + + let pairs = AHKParser::parse(Rule::script, source)?; + for pair in pairs { + match pair.as_rule() { + Rule::command => { + ast.push(AstNode::Print(Box::new(parse_command(pair)))); + } + Rule::globConfig => glob_config.push(pair.into()), + _ => {} + } + } + + ast.push(AstNode::GlobConfig(glob_config)); + + Ok(ast) +} + +fn parse_command(pair: pest::iterators::Pair) -> AstNode { + let mut pairs = pair.into_inner(); + let trigger = parse_trigger(pairs.next().unwrap()); + let body = parse_body(pairs.next().unwrap().into_inner()); + AstNode::Command { + trigger: Box::new(trigger), + body: Box::new(body), + } +} + +fn parse_body(pairs: pest::iterators::Pairs) -> AstNode { + let mut body = vec![]; + + for pair in pairs { + match pair.as_rule() { + Rule::expr => body.push(build_ast_from_expr(pair)), + Rule::EOI => {} + _ => unreachable!("Unknown command part: {:?}", pair), + } + } + + AstNode::Body(body) +} + +fn parse_assgmt(pair: pest::iterators::Pair) -> AstNode { + let mut pairs = pair.into_inner(); + let ident = String::from(pairs.next().unwrap().as_str()); + + let expr = build_ast_from_expr(pairs.next().unwrap()); + AstNode::Assignment { + ident, + expr: Box::new(expr), + } +} + +fn build_ast_from_expr(pair: pest::iterators::Pair) -> AstNode { + match pair.as_rule() { + Rule::expr => build_ast_from_expr(pair.into_inner().next().unwrap()), + Rule::action => parse_action(pair.into_inner().next().unwrap()), + Rule::assgmtExpr => { + let mut pairs = pair.into_inner(); + let ident = pairs.next().unwrap().as_str().into(); + let expr = build_ast_from_expr(pairs.next().unwrap()); + AstNode::Assignment { + ident, + expr: Box::new(expr), + } + } + Rule::controlFlow => parse_control_flow(pair.into_inner().next().unwrap()), + Rule::funcCall => { + let mut pairs = pair.into_inner(); + let ident = pairs.next().unwrap().as_str().into(); + let params = pairs + .next() + .unwrap() + .into_inner() + .map(build_ast_from_expr) + .collect(); + + AstNode::FuncCall { + ident, + params: Box::new(AstNode::Params(params)), + } + } + Rule::integer => AstNode::Integer(pair.as_str().parse().unwrap()), + Rule::string => AstNode::Ident( + litrs::StringLit::parse(pair.as_str()) + .unwrap() + .value() + .to_string(), + ), + _ => unreachable!("Unknown expression: {:?}", pair), + } +} + +fn parse_control_flow(pair: pest::iterators::Pair) -> AstNode { + match pair.as_rule() { + Rule::loopFlow => AstNode::LoopFlow(Box::new(parse_body( + pair.into_inner().next().unwrap().into_inner(), + ))), + Rule::ifFlow => { + let mut pairs = pair.into_inner(); + let condition = build_ast_from_expr(pairs.next().unwrap()); + let body = parse_body(pairs.next().unwrap().into_inner()); + AstNode::IfFlow { + condition: Box::new(condition), + body: Box::new(body), + } + } + Rule::whileFlow => { + let mut pairs = pair.into_inner(); + let condition = build_ast_from_expr(pairs.next().unwrap()); + let body = parse_body(pairs.next().unwrap().into_inner()); + AstNode::WhileFlow { + condition: Box::new(condition), + body: Box::new(body), + } + } + Rule::breakFlow => AstNode::Break, + _ => unreachable!("Unknown flow control: {:?}", pair), + } +} + +fn parse_action(pair: pest::iterators::Pair) -> AstNode { + match pair.as_rule() { + Rule::sleepAction => AstNode::Sleep( + pair.into_inner() + .next() + .unwrap() + .as_str() + .parse::() + .unwrap(), + ), + Rule::sendAction => { + AstNode::Send(Box::new(parse_target(pair.into_inner().next().unwrap()))) + } + Rule::sendRawAction => { + AstNode::Send(Box::new(parse_target(pair.into_inner().next().unwrap()))) + } + _ => unreachable!("Unexpected action: {:?}", pair), + } +} + +fn parse_target(pair: pest::iterators::Pair) -> AstNode { + match pair.as_rule() { + Rule::button => AstNode::Button(String::from(pair.into_inner().next().unwrap().as_str())), + Rule::variable => AstNode::Variable(Box::new(build_ast_from_term( + pair.into_inner().next().unwrap(), + ))), + Rule::rawLine => AstNode::RawLine(String::from(pair.as_str())), + _ => unreachable!("Unexpected target: {:?}", pair), + } +} + +fn parse_trigger(pair: pest::iterators::Pair) -> AstNode { + let mut modifiers = Modifiers::new(); + let mut hotkey = String::from(""); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::modifier => { + let modifier = pair; + match modifier.as_str() { + "~" => modifiers.exclusive = true, + "^" => modifiers.control = true, + "!" => modifiers.alt = true, + "+" => modifiers.shift = true, + "$" => modifiers.hook_mode = true, + unknown_mod => { + unreachable!("Unexpected modifier: {}", unknown_mod); + } + } + } + Rule::hotkey => { + hotkey = pair.into_inner().next().unwrap().as_str().to_string(); + } + _ => {} + } + } + AstNode::Trigger { modifiers, hotkey } +} + +fn build_ast_from_term(pair: pest::iterators::Pair) -> AstNode { + match pair.as_rule() { + Rule::ident => AstNode::Ident(String::from(pair.as_str())), + unknown_expr => unreachable!("Unexpected term: {:?}", unknown_expr), + } +}