From 9ee13b5bd2a079c414e477ac241c58717cbb59e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20=C4=8Euri=C5=A1?= Date: Wed, 28 Feb 2024 09:14:19 +0100 Subject: [PATCH] Add rule modifiers and rule tags support --- example.yar | 2 +- src/lexer/mod.rs | 6 ++ src/parser/grammar/expressions.rs | 21 ++----- src/parser/grammar/items.rs | 21 ++++++- src/parser/syntax_kind/generated.rs | 10 +++- src/syntax/ast.rs | 1 + src/syntax/ast/generated/nodes.rs | 93 +++++++++++++++++++++++++++++ src/syntax/ast/traits.rs | 9 ++- src/syntax/tests/ast_src.rs | 16 ++++- src/syntax/tests/sourcegen_ast.rs | 30 ++++++++++ tests/test10.in | 7 +++ tests/test10.out | 37 ++++++++++++ tests/test11.in | 7 +++ tests/test11.out | 38 ++++++++++++ tests/test6.err | 16 ++--- tests/test8.err | 6 +- tests/test8.out | 14 +++-- yara.ungram | 10 +++- 18 files changed, 303 insertions(+), 41 deletions(-) create mode 100644 tests/test10.in create mode 100644 tests/test10.out create mode 100644 tests/test11.in create mode 100644 tests/test11.out diff --git a/example.yar b/example.yar index 5d28bec..24cbdf2 100644 --- a/example.yar +++ b/example.yar @@ -1,7 +1,7 @@ //Global comment //Rule comment -rule test +rule test : bla test { //Rule block comment diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 3b48118..63a9519 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -43,6 +43,10 @@ pub(crate) enum LogosToken { // Keywords #[token("rule")] Rule, + #[token("private")] + Private, + #[token("global")] + Global, #[token("meta")] Meta, #[token("strings")] @@ -149,6 +153,8 @@ pub fn tokenize(text: &str) -> (Vec, Vec) { fn logos_tokenkind_to_syntaxkind(token: LogosToken) -> SyntaxKind { match token { LogosToken::Rule => SyntaxKind::RULE_KW, + LogosToken::Private => SyntaxKind::PRIVATE_KW, + LogosToken::Global => SyntaxKind::GLOBAL_KW, LogosToken::Meta => SyntaxKind::META_KW, LogosToken::Strings => SyntaxKind::STRINGS_KW, LogosToken::Condition => SyntaxKind::CONDITION_KW, diff --git a/src/parser/grammar/expressions.rs b/src/parser/grammar/expressions.rs index 99b6541..d889272 100644 --- a/src/parser/grammar/expressions.rs +++ b/src/parser/grammar/expressions.rs @@ -2,12 +2,6 @@ mod atom; use super::*; -/// Recovery set for `strings` block. This also should be adjusted and tweaked to -/// better represents recovery set later on -const STRINGS_RECOVERY_SET: TokenSet = TokenSet::new(&[T![strings]]); - -const META_RECOVERY_SET: TokenSet = TokenSet::new(&[T![identifier]]); - /// Parse a rule body /// A rule body consists `{`, rule_body and `}` /// This can probably be later simplified to not have both @@ -32,6 +26,7 @@ pub(super) fn rule_body(p: &mut Parser) { let mut has_strings = false; let mut has_condition = false; let mut has_meta = false; + while !p.at(EOF) && !p.at(T!['}']) { match p.current() { T![meta] => { @@ -66,15 +61,7 @@ pub(super) fn rule_body(p: &mut Parser) { // but we can still try to parse their body and throw an error for parent // for now it just looks at next 2 tokens to differenciate between valid strings // body or condition body. This should probably be adjusted later - p.err_and_bump("expected strings or condition"); - if p.current() == T![:] { - p.eat(T![:]); - if p.current() == T![variable] && p.nth(1) == T![=] { - strings_body(p) - } else if let Some(_) = expression(p, None, 1) { - condition_body(p); - } - } + p.err_and_bump("expected meta, strings or condition keyword"); } } } @@ -121,7 +108,7 @@ pub(super) fn meta_body(p: &mut Parser) { if p.at(T![identifier]) { p.bump(T![identifier]); } else { - p.err_recover("expected an identifier", META_RECOVERY_SET); + p.err_and_bump("expected an identifier"); } p.expect(T![=]); match p.current() { @@ -145,7 +132,7 @@ pub(super) fn strings_body(p: &mut Parser) { if p.at(T![variable]) { p.bump(T![variable]); } else { - p.err_recover("expected a variable", STRINGS_RECOVERY_SET); + p.err_and_bump("expected a variable"); } p.expect(T![=]); // so far only strings are supported, later add match for hex strings and regex diff --git a/src/parser/grammar/items.rs b/src/parser/grammar/items.rs index d6e93b6..f85ce38 100644 --- a/src/parser/grammar/items.rs +++ b/src/parser/grammar/items.rs @@ -48,9 +48,15 @@ pub(super) fn process_top_level(p: &mut Parser, stop_on_r_brace: bool) { // In the future, also imports and includes will be supported here pub(super) fn opt_rule_import_include(p: &mut Parser, m: Marker) -> Result<(), Marker> { // add rule modifiers to match current and lookahead next with p.nth(1) for RULE or ERROR - match p.current() { - T![rule] => rule(p, m), - _ => return Err(m), + while p.at_ts(TokenSet::new(&[T![private], T![global]])) { + let m = p.start(); + p.bump_any(); + m.complete(p, MODIFIER); + } + if p.at(T![rule]) { + rule(p, m); + } else { + return Err(m); } Ok(()) } @@ -58,6 +64,7 @@ pub(super) fn opt_rule_import_include(p: &mut Parser, m: Marker) -> Result<(), M // Parse a rule // It consists of rule name [`IDENTIFIER`] and a body [`block_expr`] fn rule(p: &mut Parser, m: Marker) { + assert!(p.at(T![rule])); p.bump(T![rule]); if p.at(IDENTIFIER) { p.bump(IDENTIFIER); @@ -65,6 +72,14 @@ fn rule(p: &mut Parser, m: Marker) { p.err_recover("expected a name", RULE_RECOVERY_SET); } // add optional support for rule tags + if p.at(T![:]) { + p.bump(T![:]); + while p.at(IDENTIFIER) { + let m = p.start(); + p.bump(IDENTIFIER); + m.complete(p, TAG); + } + } expressions::block_expr(p); m.complete(p, RULE); } diff --git a/src/parser/syntax_kind/generated.rs b/src/parser/syntax_kind/generated.rs index fd39e88..d4a0cd6 100644 --- a/src/parser/syntax_kind/generated.rs +++ b/src/parser/syntax_kind/generated.rs @@ -25,6 +25,8 @@ pub enum SyntaxKind { STRINGS_KW, CONDITION_KW, META_KW, + PRIVATE_KW, + GLOBAL_KW, STRING_LIT, INT_LIT, FLOAT_LIT, @@ -34,6 +36,8 @@ pub enum SyntaxKind { COMMENT, ERROR, RULE, + MODIFIER, + TAG, STRINGS, META, CONDITION, @@ -63,6 +67,8 @@ impl SyntaxKind { | STRINGS_KW | CONDITION_KW | META_KW + | PRIVATE_KW + | GLOBAL_KW ) } pub fn is_punct(self) -> bool { @@ -82,6 +88,8 @@ impl SyntaxKind { "strings" => STRINGS_KW, "condition" => CONDITION_KW, "meta" => META_KW, + "private" => PRIVATE_KW, + "global" => GLOBAL_KW, _ => return None, }; Some(kw) @@ -101,5 +109,5 @@ impl SyntaxKind { } } #[macro_export] -macro_rules ! T { [:] => { $ crate :: SyntaxKind :: COLON } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_BRACE } ; ['}'] => { $ crate :: SyntaxKind :: R_BRACE } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; [=] => { $ crate :: SyntaxKind :: ASSIGN } ; [and] => { $ crate :: SyntaxKind :: AND_KW } ; [or] => { $ crate :: SyntaxKind :: OR_KW } ; [not] => { $ crate :: SyntaxKind :: NOT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [rule] => { $ crate :: SyntaxKind :: RULE_KW } ; [strings] => { $ crate :: SyntaxKind :: STRINGS_KW } ; [condition] => { $ crate :: SyntaxKind :: CONDITION_KW } ; [meta] => { $ crate :: SyntaxKind :: META_KW } ; [identifier] => { $ crate :: SyntaxKind :: IDENTIFIER } ; [variable] => { $ crate :: SyntaxKind :: VARIABLE } ; [string_lit] => { $ crate :: SyntaxKind :: STRING_LIT } ; [int_lit] => { $ crate :: SyntaxKind :: INT_LIT } ; [float_lit] => { $ crate :: SyntaxKind :: FLOAT_LIT } ; } +macro_rules ! T { [:] => { $ crate :: SyntaxKind :: COLON } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_BRACE } ; ['}'] => { $ crate :: SyntaxKind :: R_BRACE } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; [=] => { $ crate :: SyntaxKind :: ASSIGN } ; [and] => { $ crate :: SyntaxKind :: AND_KW } ; [or] => { $ crate :: SyntaxKind :: OR_KW } ; [not] => { $ crate :: SyntaxKind :: NOT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [rule] => { $ crate :: SyntaxKind :: RULE_KW } ; [strings] => { $ crate :: SyntaxKind :: STRINGS_KW } ; [condition] => { $ crate :: SyntaxKind :: CONDITION_KW } ; [meta] => { $ crate :: SyntaxKind :: META_KW } ; [private] => { $ crate :: SyntaxKind :: PRIVATE_KW } ; [global] => { $ crate :: SyntaxKind :: GLOBAL_KW } ; [identifier] => { $ crate :: SyntaxKind :: IDENTIFIER } ; [variable] => { $ crate :: SyntaxKind :: VARIABLE } ; [string_lit] => { $ crate :: SyntaxKind :: STRING_LIT } ; [int_lit] => { $ crate :: SyntaxKind :: INT_LIT } ; [float_lit] => { $ crate :: SyntaxKind :: FLOAT_LIT } ; } pub use T; diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 42dd536..6dcd51a 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -18,6 +18,7 @@ pub use self::{ generated::{nodes::*, tokens::*}, operators::*, traits::HasComments, + traits::HasModifier, }; /// Zero runtime cost conversion to AST layer diff --git a/src/syntax/ast/generated/nodes.rs b/src/syntax/ast/generated/nodes.rs index 7b68822..f8ff621 100644 --- a/src/syntax/ast/generated/nodes.rs +++ b/src/syntax/ast/generated/nodes.rs @@ -22,6 +22,7 @@ impl SourceFile { pub struct Rule { pub(crate) syntax: SyntaxNode, } +impl ast::HasModifier for Rule {} impl ast::HasComments for Rule {} impl Rule { pub fn rule_token(&self) -> Option { @@ -30,11 +31,40 @@ impl Rule { pub fn identifier_token(&self) -> Option { support::token(&self.syntax, T![identifier]) } + pub fn colon_token(&self) -> Option { + support::token(&self.syntax, T![:]) + } + pub fn tags(&self) -> AstChildren { + support::children(&self.syntax) + } pub fn body(&self) -> Option { support::child(&self.syntax) } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Modifier { + pub(crate) syntax: SyntaxNode, +} +impl Modifier { + pub fn private_token(&self) -> Option { + support::token(&self.syntax, T![private]) + } + pub fn global_token(&self) -> Option { + support::token(&self.syntax, T![global]) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Tag { + pub(crate) syntax: SyntaxNode, +} +impl Tag { + pub fn identifier_token(&self) -> Option { + support::token(&self.syntax, T![identifier]) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct BlockExpr { pub(crate) syntax: SyntaxNode, @@ -209,6 +239,12 @@ pub struct AnyHasComments { pub(crate) syntax: SyntaxNode, } impl ast::HasComments for AnyHasComments {} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AnyHasModifier { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasModifier for AnyHasModifier {} impl AstNode for SourceFile { fn can_cast(kind: SyntaxKind) -> bool { kind == SOURCE_FILE @@ -239,6 +275,36 @@ impl AstNode for Rule { &self.syntax } } +impl AstNode for Modifier { + fn can_cast(kind: SyntaxKind) -> bool { + kind == MODIFIER + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl AstNode for Tag { + fn can_cast(kind: SyntaxKind) -> bool { + kind == TAG + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} impl AstNode for BlockExpr { fn can_cast(kind: SyntaxKind) -> bool { kind == BLOCK_EXPR @@ -457,6 +523,23 @@ impl AstNode for AnyHasComments { &self.syntax } } +impl AnyHasModifier { + #[inline] + pub fn new(node: T) -> AnyHasModifier { + AnyHasModifier { syntax: node.syntax().clone() } + } +} +impl AstNode for AnyHasModifier { + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, RULE) + } + fn cast(syntax: SyntaxNode) -> Option { + Self::can_cast(syntax.kind()).then_some(AnyHasModifier { syntax }) + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} impl std::fmt::Display for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -472,6 +555,16 @@ impl std::fmt::Display for Rule { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for Modifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for Tag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for BlockExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/src/syntax/ast/traits.rs b/src/syntax/ast/traits.rs index b7c0845..d4233d3 100644 --- a/src/syntax/ast/traits.rs +++ b/src/syntax/ast/traits.rs @@ -3,11 +3,18 @@ //! to iterate over comments in the syntax tree //! This can be easily extended to support other traits -use crate::syntax::ast::{self, AstNode}; +use crate::syntax::ast::{self, support, AstNode}; use crate::syntax::syntax_node::SyntaxElementChildren; use super::AstToken; +pub trait HasModifier: AstNode { + fn modifier(&self) -> Vec { + support::children::(self.syntax()) + .map(|m| m.syntax().text().to_string()) + .collect::>() + } +} pub trait HasComments: AstNode { fn comments(&self) -> CommentIter { CommentIter { iter: self.syntax().children_with_tokens() } diff --git a/src/syntax/tests/ast_src.rs b/src/syntax/tests/ast_src.rs index 2435ec7..51bfe25 100644 --- a/src/syntax/tests/ast_src.rs +++ b/src/syntax/tests/ast_src.rs @@ -18,11 +18,25 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { (",", "COMMA"), ("=", "ASSIGN"), ], - keywords: &["and", "or", "not", "true", "false", "rule", "strings", "condition", "meta"], + keywords: &[ + "and", + "or", + "not", + "true", + "false", + "rule", + "strings", + "condition", + "meta", + "private", + "global", + ], literals: &["STRING_LIT", "INT_LIT", "FLOAT_LIT"], tokens: &["IDENTIFIER", "VARIABLE", "WHITESPACE", "COMMENT", "ERROR"], nodes: &[ "RULE", + "MODIFIER", + "TAG", "STRINGS", "META", "CONDITION", diff --git a/src/syntax/tests/sourcegen_ast.rs b/src/syntax/tests/sourcegen_ast.rs index 50b36d0..9b84c96 100644 --- a/src/syntax/tests/sourcegen_ast.rs +++ b/src/syntax/tests/sourcegen_ast.rs @@ -607,6 +607,14 @@ fn lower_comma_list( //TODO: possible deduplication and enum extraction and struct traits, so far not needed fn extract_struct_traits(ast: &mut AstSrc) { + let traits: &[(&str, &[&str])] = &[("HasModifier", &["modifier"])]; + + for node in &mut ast.nodes { + for (name, methods) in traits { + extract_struct_trait(node, name, methods); + } + } + let nodes_with_comments = ["SourceFile", "Rule", "BlockExpr", "Strings", "Condition"]; for node in &mut ast.nodes { @@ -615,3 +623,25 @@ fn extract_struct_traits(ast: &mut AstSrc) { } } } + +fn extract_struct_trait(node: &mut AstNodeSrc, trait_name: &str, methods: &[&str]) { + let mut to_remove = Vec::new(); + for (i, field) in node.fields.iter().enumerate() { + let method_name = field.method_name().to_string(); + if methods.iter().any(|&it| it == method_name) { + to_remove.push(i); + } + } + if to_remove.len() == methods.len() { + node.traits.push(trait_name.to_string()); + node.remove_field(to_remove); + } +} + +impl AstNodeSrc { + fn remove_field(&mut self, to_remove: Vec) { + to_remove.into_iter().rev().for_each(|idx| { + self.fields.remove(idx); + }); + } +} diff --git a/tests/test10.in b/tests/test10.in new file mode 100644 index 0000000..ca9c6a2 --- /dev/null +++ b/tests/test10.in @@ -0,0 +1,7 @@ +global private rule test +{ + strings: + $a = "foo" + condition: + $a +} diff --git a/tests/test10.out b/tests/test10.out new file mode 100644 index 0000000..23b73d6 --- /dev/null +++ b/tests/test10.out @@ -0,0 +1,37 @@ +SOURCE_FILE@0..69 + RULE@0..68 + MODIFIER@0..6 + GLOBAL_KW@0..6 "global" + WHITESPACE@6..7 " " + MODIFIER@7..14 + PRIVATE_KW@7..14 "private" + WHITESPACE@14..15 " " + RULE_KW@15..19 "rule" + WHITESPACE@19..20 " " + IDENTIFIER@20..24 "test" + WHITESPACE@24..25 "\n" + BLOCK_EXPR@25..68 + L_BRACE@25..26 "{" + WHITESPACE@26..28 "\n\t" + STRINGS@28..49 + STRINGS_KW@28..35 "strings" + COLON@35..36 ":" + WHITESPACE@36..39 "\n\t\t" + VARIABLE_STMT@39..49 + VARIABLE@39..41 "$a" + WHITESPACE@41..42 " " + ASSIGN@42..43 "=" + WHITESPACE@43..44 " " + PATTERN@44..49 + STRING_LIT@44..49 "\"foo\"" + WHITESPACE@49..51 "\n\t" + CONDITION@51..66 + CONDITION_KW@51..60 "condition" + COLON@60..61 ":" + WHITESPACE@61..64 "\n\t\t" + EXPRESSION_STMT@64..66 + LITERAL@64..66 + VARIABLE@64..66 "$a" + WHITESPACE@66..67 "\n" + R_BRACE@67..68 "}" + WHITESPACE@68..69 "\n" diff --git a/tests/test11.in b/tests/test11.in new file mode 100644 index 0000000..c034753 --- /dev/null +++ b/tests/test11.in @@ -0,0 +1,7 @@ +rule test : tag1 tag2 +{ + strings: + $a = "foo" + condition: + $a +} \ No newline at end of file diff --git a/tests/test11.out b/tests/test11.out new file mode 100644 index 0000000..6f5eda6 --- /dev/null +++ b/tests/test11.out @@ -0,0 +1,38 @@ +SOURCE_FILE@0..65 + RULE@0..65 + RULE_KW@0..4 "rule" + WHITESPACE@4..5 " " + IDENTIFIER@5..9 "test" + WHITESPACE@9..10 " " + COLON@10..11 ":" + WHITESPACE@11..12 " " + TAG@12..16 + IDENTIFIER@12..16 "tag1" + WHITESPACE@16..17 " " + TAG@17..21 + IDENTIFIER@17..21 "tag2" + WHITESPACE@21..22 "\n" + BLOCK_EXPR@22..65 + L_BRACE@22..23 "{" + WHITESPACE@23..25 "\n\t" + STRINGS@25..46 + STRINGS_KW@25..32 "strings" + COLON@32..33 ":" + WHITESPACE@33..36 "\n\t\t" + VARIABLE_STMT@36..46 + VARIABLE@36..38 "$a" + WHITESPACE@38..39 " " + ASSIGN@39..40 "=" + WHITESPACE@40..41 " " + PATTERN@41..46 + STRING_LIT@41..46 "\"foo\"" + WHITESPACE@46..48 "\n\t" + CONDITION@48..63 + CONDITION_KW@48..57 "condition" + COLON@57..58 ":" + WHITESPACE@58..61 "\n\t\t" + EXPRESSION_STMT@61..63 + LITERAL@61..63 + VARIABLE@61..63 "$a" + WHITESPACE@63..64 "\n" + R_BRACE@64..65 "}" diff --git a/tests/test6.err b/tests/test6.err index 54dd076..fcece6b 100644 --- a/tests/test6.err +++ b/tests/test6.err @@ -1,12 +1,12 @@ SyntaxError("expected a name", 38..38) -SyntaxError("expected strings or condition", 92..92) -SyntaxError("expected strings or condition", 98..98) -SyntaxError("expected strings or condition", 102..102) -SyntaxError("expected strings or condition", 104..104) -SyntaxError("expected strings or condition", 106..106) -SyntaxError("expected strings or condition", 114..114) -SyntaxError("expected strings or condition", 117..117) -SyntaxError("expected strings or condition", 119..119) +SyntaxError("expected meta, strings or condition keyword", 92..92) +SyntaxError("expected meta, strings or condition keyword", 98..98) +SyntaxError("expected meta, strings or condition keyword", 102..102) +SyntaxError("expected meta, strings or condition keyword", 104..104) +SyntaxError("expected meta, strings or condition keyword", 106..106) +SyntaxError("expected meta, strings or condition keyword", 114..114) +SyntaxError("expected meta, strings or condition keyword", 117..117) +SyntaxError("expected meta, strings or condition keyword", 119..119) SyntaxError("unsupported expression", 139..139) SyntaxError("unsupported expression", 141..141) SyntaxError("unsupported expression", 150..150) diff --git a/tests/test8.err b/tests/test8.err index 7f0b377..8fba1da 100644 --- a/tests/test8.err +++ b/tests/test8.err @@ -1 +1,5 @@ -SyntaxError("expected strings or condition", 87..87) \ No newline at end of file +SyntaxError("expected meta, strings or condition keyword", 87..87) +SyntaxError("expected meta, strings or condition keyword", 93..93) +SyntaxError("expected meta, strings or condition keyword", 97..97) +SyntaxError("expected meta, strings or condition keyword", 100..100) +SyntaxError("expected meta, strings or condition keyword", 102..102) \ No newline at end of file diff --git a/tests/test8.out b/tests/test8.out index d9c69bd..65de368 100644 --- a/tests/test8.out +++ b/tests/test8.out @@ -17,15 +17,17 @@ SOURCE_FILE@0..149 WHITESPACE@85..87 "\n\t" ERROR@87..93 IDENTIFIER@87..93 "string" - COLON@93..94 ":" + ERROR@93..94 + COLON@93..94 ":" WHITESPACE@94..97 "\n\t\t" - VARIABLE_STMT@97..107 + ERROR@97..99 VARIABLE@97..99 "$b" - WHITESPACE@99..100 " " + WHITESPACE@99..100 " " + ERROR@100..101 ASSIGN@100..101 "=" - WHITESPACE@101..102 " " - PATTERN@102..107 - STRING_LIT@102..107 "\"bar\"" + WHITESPACE@101..102 " " + ERROR@102..107 + STRING_LIT@102..107 "\"bar\"" WHITESPACE@107..109 "\n\t" CONDITION@109..146 CONDITION_KW@109..118 "condition" diff --git a/yara.ungram b/yara.ungram index f36bb71..a758c01 100644 --- a/yara.ungram +++ b/yara.ungram @@ -1,8 +1,14 @@ SourceFile = Rule* Rule = - 'rule' 'identifier' - body:BlockExpr + Modifier? 'rule' 'identifier' ':'? Tag* + body:BlockExpr + +Modifier = + 'private' | 'global' + +Tag = + 'identifier' BlockExpr = '{'