Skip to content

Commit 348484d

Browse files
committed
[add] LALR1 作成 & lalr1 フラグ追加
1 parent 2a2e84a commit 348484d

File tree

6 files changed

+287
-0
lines changed

6 files changed

+287
-0
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ copager_parse_lr_common = { path = "./crates/parse_lr_common", optional = true }
1818
copager_parse_lr_lr0 = { path = "./crates/parse_lr_lr0", optional = true }
1919
copager_parse_lr_lr1 = { path = "./crates/parse_lr_lr1", optional = true }
2020
copager_parse_lr_slr1 = { path = "./crates/parse_lr_slr1", optional = true }
21+
copager_parse_lr_lalr1 = { path = "./crates/parse_lr_lalr1", optional = true }
2122
copager_ir = { path = "./crates/ir" }
2223
copager_ir_void = { path = "./crates/ir_void", optional = true }
2324
copager_ir_sexp = { path = "./crates/ir_sexp", optional = true }
@@ -54,6 +55,7 @@ regexlex = ["dep:copager_lex_regex"]
5455
lr0 = ["dep:copager_parse_lr_lr0"]
5556
lr1 = ["dep:copager_parse_lr_lr1"]
5657
slr1 = ["dep:copager_parse_lr_slr1"]
58+
lalr1 = ["dep:copager_parse_lr_lalr1"]
5759

5860
# ir
5961
void = ["dep:copager_ir_void"]
@@ -76,6 +78,7 @@ members = [
7678
"./crates/parse_lr_lr0",
7779
"./crates/parse_lr_lr1",
7880
"./crates/parse_lr_slr1",
81+
"./crates/parse_lr_lalr1",
7982
"./crates/ir",
8083
"./crates/ir_void",
8184
"./crates/ir_sexp",

crates/parse_lr_lalr1/Cargo.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
cargo-features = ["edition2024"]
2+
3+
[package]
4+
name = "copager_parse_lr_lalr1"
5+
version = "0.2.0"
6+
edition = "2024"
7+
8+
[dependencies]
9+
anyhow = { workspace = true }
10+
thiserror = { workspace = true }
11+
serde = { workspace = true, features = ["derive"] }
12+
copager_cfg = { path = "../cfg" }
13+
copager_lex = { path = "../lex" }
14+
copager_parse = { path = "../parse" }
15+
copager_parse_common = { path = "../parse_common" }
16+
copager_parse_lr_common = { path = "../parse_lr_common" }
17+
copager_utils = { path = "../utils" }
18+
19+
[dev-dependencies]
20+
copager_core = { path = "../core" }
21+
copager_lex = { path = "../lex", features = ["derive"] }
22+
copager_lex_regex = { path = "../lex_regex" }
23+
copager_parse = { path = "../parse", features = ["derive"] }
24+
copager_ir_void = { path = "../ir_void" }

crates/parse_lr_lalr1/src/lib.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#![feature(gen_blocks)]
2+
3+
use std::marker::PhantomData;
4+
5+
use serde::{Serialize, Deserialize};
6+
7+
use copager_cfg::token::{Token, TokenTag};
8+
use copager_cfg::rule::{Rule, RuleElem, RuleTag};
9+
use copager_lex::LexSource;
10+
use copager_parse::{BaseParser, ParseSource, ParseEvent};
11+
use copager_parse_common::rule::FirstSet;
12+
use copager_parse_lr_common::lr1::LR1DFA;
13+
use copager_parse_lr_common::lalr1::item::LALR1Item;
14+
use copager_parse_lr_common::lalr1::LALR1DFA;
15+
use copager_parse_lr_common::{LRDriver, LRAction, LRTable, LRTableBuilder};
16+
use copager_utils::cache::Cacheable;
17+
18+
pub struct LALR1<T, R>
19+
where
20+
T: TokenTag,
21+
R: RuleTag<T>
22+
{
23+
table: LRTable<T, R>,
24+
}
25+
26+
impl<Sl, Sp> BaseParser<Sl, Sp> for LALR1<Sl::Tag, Sp::Tag>
27+
where
28+
Sl: LexSource,
29+
Sp: ParseSource<Sl::Tag>,
30+
{
31+
fn try_from((_, source_p): (Sl, Sp)) -> anyhow::Result<Self> {
32+
let table = LALR1Table::try_from(source_p)?;
33+
Ok(LALR1 { table })
34+
}
35+
36+
gen fn run<'input, Il>(&self, mut lexer: Il) -> ParseEvent<'input, Sl::Tag, Sp::Tag>
37+
where
38+
Il: Iterator<Item = Token<'input, Sl::Tag>>,
39+
{
40+
let mut driver = LRDriver::from(&self.table);
41+
while !driver.accepted() {
42+
for event in driver.consume(lexer.next()).collect::<Vec<_>>() {
43+
yield event;
44+
}
45+
}
46+
}
47+
}
48+
49+
impl<Sl, Sp> Cacheable<(Sl, Sp)> for LALR1<Sl::Tag, Sp::Tag>
50+
where
51+
Sl: LexSource,
52+
Sl::Tag: Serialize + for<'de> Deserialize<'de>,
53+
Sp: ParseSource<Sl::Tag>,
54+
Sp::Tag: Serialize + for<'de> Deserialize<'de>,
55+
{
56+
type Cache = LRTable<Sl::Tag, Sp::Tag>;
57+
58+
fn new((_, source_p): (Sl, Sp)) -> anyhow::Result<Self::Cache> {
59+
let table = LALR1Table::try_from(source_p)?;
60+
Ok(table)
61+
}
62+
63+
fn restore(table: Self::Cache) -> Self {
64+
LALR1 { table }
65+
}
66+
}
67+
68+
pub struct LALR1Table<T, R>
69+
where
70+
T: TokenTag,
71+
R: RuleTag<T>
72+
{
73+
_phantom_t: PhantomData<T>,
74+
_phantom_r: PhantomData<R>,
75+
}
76+
77+
impl<T, R> LALR1Table<T, R>
78+
where
79+
T: TokenTag,
80+
R: RuleTag<T>,
81+
{
82+
fn try_from<Sp>(source_p: Sp) -> anyhow::Result<LRTable<T, R>>
83+
where
84+
Sp: ParseSource<T, Tag = R>,
85+
{
86+
// 最上位規則を追加して RuleSet を更新
87+
let mut ruleset = source_p.into_ruleset();
88+
let top_dummy = Rule::new(
89+
None,
90+
RuleElem::new_nonterm("__top_dummy"),
91+
vec![RuleElem::new_nonterm(&ruleset.top)],
92+
);
93+
ruleset.update_top(top_dummy.clone());
94+
95+
// First 集合作成
96+
let first_set = FirstSet::from(&ruleset);
97+
98+
// LALR(1) オートマトン作成
99+
let dfa = LR1DFA::from((&ruleset, &first_set));
100+
let dfa = LALR1DFA::from(dfa);
101+
102+
// LALR(1) 構文解析表作成
103+
let mut builder = LRTableBuilder::from(&dfa);
104+
for node in &dfa.nodes {
105+
for (rule, la_tokens) in node.find_all_by(is_lalr1_reduce_state) {
106+
// A -> α β . [la_token] を含む場合,la_token 列に対して Reduce をマーク
107+
for la_token in la_tokens {
108+
match la_token {
109+
RuleElem::Term(term) => {
110+
builder.try_set(node.id, Some(*term), LRAction::Reduce(rule.clone()))?;
111+
}
112+
RuleElem::EOF => {
113+
builder.try_set(node.id, None, LRAction::Reduce(rule.clone()))?;
114+
}
115+
_ => {}
116+
}
117+
}
118+
119+
// S -> Top . を含む場合,EOF 列に対して Accept をマーク
120+
if rule == &top_dummy {
121+
builder.set(node.id, None, LRAction::Accept);
122+
}
123+
}
124+
}
125+
let table = builder.build();
126+
127+
Ok(table)
128+
}
129+
}
130+
131+
fn is_lalr1_reduce_state<T, R>(item: &&LALR1Item<T, R>) -> bool
132+
where
133+
T: TokenTag,
134+
R: RuleTag<T>,
135+
{
136+
item.check_next_elem().is_none()
137+
}

crates/parse_lr_lalr1/tests/simple.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use copager_core::{Language, Processor};
2+
use copager_cfg::token::TokenTag;
3+
use copager_cfg::rule::{RuleTag, Rule, RuleElem};
4+
use copager_lex::LexSource;
5+
use copager_lex_regex::RegexLexer;
6+
use copager_parse::ParseSource;
7+
use copager_parse_lr_lalr1::LALR1;
8+
use copager_ir_void::Void;
9+
10+
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq, LexSource)]
11+
enum TestToken {
12+
#[default]
13+
#[token(text = r"\+")]
14+
Plus,
15+
#[token(text = r"-")]
16+
Minus,
17+
#[token(text = r"\*")]
18+
Mul,
19+
#[token(text = r"/")]
20+
Div,
21+
#[token(text = r"\(")]
22+
BracketL,
23+
#[token(text = r"\)")]
24+
BracketR,
25+
#[token(text = r"[1-9][0-9]*")]
26+
Num,
27+
#[token(text = r"[ \t\n]+", ignored)]
28+
_Whitespace,
29+
}
30+
31+
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq, ParseSource)]
32+
enum TestRule {
33+
#[default]
34+
#[rule("<expr> ::= <expr> Plus <term>")]
35+
#[rule("<expr> ::= <expr> Minus <term>")]
36+
#[rule("<expr> ::= <term>")]
37+
Expr,
38+
#[rule("<term> ::= <term> Mul <num>")]
39+
#[rule("<term> ::= <term> Div <num>")]
40+
#[rule("<term> ::= <num>")]
41+
Term,
42+
#[rule("<num> ::= BracketL <expr> BracketR")]
43+
#[rule("<num> ::= Num")]
44+
Num,
45+
}
46+
47+
type TestLanguage = Language<TestToken, TestRule>;
48+
type TestLexer = RegexLexer<TestToken>;
49+
type TestParser = LALR1<TestToken, TestRule>;
50+
type TestProcessor = Processor<TestLanguage, TestLexer, TestParser>;
51+
52+
#[test]
53+
fn simple_success() {
54+
const OK_INPUTS: [&str; 10] = [
55+
"10",
56+
"10 + 20",
57+
"10 - 20",
58+
"10 * 20",
59+
"10 / 20",
60+
"10 + 20 * 30 - 40",
61+
"(10)",
62+
"((((10))))",
63+
"10 * (20 - 30)",
64+
"((10 + 20) * (30 / 40)) - 50",
65+
];
66+
67+
let processor = TestProcessor::new()
68+
.build_lexer()
69+
.unwrap()
70+
.build_parser()
71+
.unwrap();
72+
73+
for input in &OK_INPUTS {
74+
println!("input: {}", input);
75+
processor.process::<Void>(input).unwrap();
76+
}
77+
}
78+
79+
#[test]
80+
fn simple_failure() {
81+
const ERR_INPUTS: [&str; 7] = [
82+
"()",
83+
"(10 -",
84+
"10 +",
85+
"*",
86+
"10 20 + 30",
87+
"10 + 20 * 30 / 40 (",
88+
"(((10))",
89+
];
90+
91+
let processor = TestProcessor::new()
92+
.build_lexer()
93+
.unwrap()
94+
.build_parser()
95+
.unwrap();
96+
97+
for input in &ERR_INPUTS {
98+
assert!(processor.process::<Void>(input).is_err(), "input: {}", input);
99+
}
100+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ pub mod parse {
2222
pub use copager_parse_lr_lr0::*;
2323
#[cfg(feature = "lr1")]
2424
pub use copager_parse_lr_lr1::*;
25+
#[cfg(feature = "slr1")]
26+
pub use copager_parse_lr_slr1::*;
27+
#[cfg(feature = "lalr1")]
28+
pub use copager_parse_lr_lalr1::*;
2529
}
2630

2731
pub mod ir {

0 commit comments

Comments
 (0)