Skip to content

Commit e0afe07

Browse files
committed
Add unsafe fn for modifying GrammarAST
1 parent 5f05d1d commit e0afe07

3 files changed

Lines changed: 61 additions & 7 deletions

File tree

cfgrammar/src/lib/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#![allow(clippy::new_without_default)]
44
#![allow(clippy::unnecessary_wraps)]
55
#![allow(clippy::upper_case_acronyms)]
6-
#![forbid(unsafe_code)]
6+
#![deny(unsafe_code)]
77
#![deny(unreachable_pub)]
88

99
//! A library for manipulating Context Free Grammars (CFG). It is impractical to fully homogenise

cfgrammar/src/lib/yacc/ast.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use crate::{
1717
};
1818
/// Contains a `GrammarAST` structure produced from a grammar source file.
1919
/// As well as any errors which occurred during the construction of the AST.
20+
#[derive(Debug, Clone)]
21+
#[cfg_attr(test, derive(PartialEq))]
2022
pub struct ASTWithValidityInfo {
2123
yacc_kind: YaccKind,
2224
ast: GrammarAST,
@@ -70,6 +72,16 @@ impl ASTWithValidityInfo {
7072
pub fn errors(&self) -> &[YaccGrammarError] {
7173
self.errs.as_slice()
7274
}
75+
76+
#[allow(unsafe_code)]
77+
pub unsafe fn set_ast(&mut self, ast: GrammarAST) {
78+
self.ast = ast;
79+
}
80+
81+
#[allow(unsafe_code)]
82+
pub unsafe fn ast_mut(&mut self) -> &mut GrammarAST {
83+
&mut self.ast
84+
}
7385
}
7486

7587
impl FromStr for ASTWithValidityInfo {
@@ -110,7 +122,8 @@ impl FromStr for ASTWithValidityInfo {
110122
/// An AST representing a grammar. This is built up gradually: when it is finished, the
111123
/// `complete_and_validate` must be called exactly once in order to finish the set-up. At that
112124
/// point, any further mutations made to the struct lead to undefined behaviour.
113-
#[derive(Debug)]
125+
#[derive(Debug, Clone)]
126+
#[cfg_attr(test, derive(PartialEq))]
114127
#[non_exhaustive]
115128
pub struct GrammarAST {
116129
pub start: Option<(String, Span)>,
@@ -140,14 +153,15 @@ pub struct GrammarAST {
140153
pub expect_unused: Vec<Symbol>,
141154
}
142155

143-
#[derive(Debug)]
156+
#[derive(Debug, Clone)]
157+
#[cfg_attr(test, derive(Eq, PartialEq))]
144158
pub struct Rule {
145159
pub name: (String, Span),
146160
pub pidxs: Vec<usize>, // index into GrammarAST.prod
147161
pub actiont: Option<String>,
148162
}
149163

150-
#[derive(Debug)]
164+
#[derive(Debug, Clone)]
151165
#[cfg_attr(test, derive(Eq, PartialEq))]
152166
pub struct Production {
153167
pub symbols: Vec<Symbol>,
@@ -195,7 +209,7 @@ impl fmt::Display for Symbol {
195209
}
196210

197211
impl GrammarAST {
198-
pub fn new() -> GrammarAST {
212+
pub(crate) fn new() -> GrammarAST {
199213
GrammarAST {
200214
start: None,
201215
rules: IndexMap::new(), // Using an IndexMap means that we retain the order
@@ -772,4 +786,44 @@ mod test {
772786
.contains(&ast_validity.ast().tokens.get_index_of("b").unwrap())
773787
);
774788
}
789+
790+
#[test]
791+
#[allow(unsafe_code)]
792+
fn clone_ast_changing_start_rule() {
793+
use super::*;
794+
use crate::yacc::*;
795+
let y_src_contents = r#"
796+
%token A B C
797+
%%
798+
AStart: A ':' BStart ';';
799+
BStart: B ',' C | C ',' B;
800+
"#;
801+
802+
let astart = format!("%start AStart\n{y_src_contents}");
803+
let bcstart = format!("%start BStart\n{y_src_contents}");
804+
805+
let mut astart_ast_validity = ASTWithValidityInfo::new(
806+
YaccKind::Original(YaccOriginalActionKind::NoAction),
807+
&astart,
808+
);
809+
let bstart_ast_validity = ASTWithValidityInfo::new(
810+
YaccKind::Original(YaccOriginalActionKind::NoAction),
811+
&bcstart,
812+
);
813+
814+
assert!(astart_ast_validity.is_valid());
815+
assert!(bstart_ast_validity.is_valid());
816+
817+
// We'll take the `astart` grammar and programatically modify it into the `bcstart` one.
818+
let modified_astart = unsafe { astart_ast_validity.ast_mut() };
819+
820+
let start_len = "%start ".len();
821+
modified_astart.start = Some((
822+
"BStart".to_string(),
823+
Span::new(start_len, start_len + "BStart".len()),
824+
));
825+
// This equality test is really a hack since it relies on all the start rules having the same str length
826+
// So as not to change the spans of any rules after the initial `%start` decl.
827+
assert_eq!(astart_ast_validity, bstart_ast_validity);
828+
}
775829
}

cfgrammar/src/lib/yacc/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use quote::quote;
1717
use serde::{Deserialize, Serialize};
1818

1919
/// The particular Yacc variant this grammar makes use of.
20-
#[derive(Clone, Copy, Debug)]
20+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2121
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2222
#[non_exhaustive]
2323
pub enum YaccKind {
@@ -43,7 +43,7 @@ impl quote::ToTokens for YaccKind {
4343
}
4444
}
4545

46-
#[derive(Clone, Copy, Debug)]
46+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4747
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4848
pub enum YaccOriginalActionKind {
4949
/// Execute user-specified actions attached to each production; also requires a %actiontype

0 commit comments

Comments
 (0)