Skip to content

Commit

Permalink
rs/import: Labels must be serched as well for recovery rules
Browse files Browse the repository at this point in the history
The dependency finder of a rule was alware of Identifiers used within
any subexpressions.  But we also need to check for labels which might
have recovery rules defined with the same name and add them as
dependencies as well.  e.g.:

  // File leaf.peg
  @import Val from "./dependecy.peg"
  Main <- Val ("," Val)*

  // File dependency.peg
  Val <- Hex / Dec / Str / Bool
  Hex <- '0x' [a-fA-F0-9]+^LabelHex
  LabelHex <- (!Val .)*

The importer wasn't pulling "LabelHex" into the file "leaf.peg"
without this change.

This also changes Comipler.compile to take the starting rule as an
optional value, and if it is not present, the first rule that appears
in the grammar ast will be used.
  • Loading branch information
clarete committed May 6, 2024
1 parent 71c702a commit 3776c05
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 11 deletions.
13 changes: 10 additions & 3 deletions langlang/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ enum Command {

/// Choose what's the first production to run
#[arg(short, long)]
start_rule: String,
start_rule: Option<String>,

/// Path to the content to be matched against the grammar;
/// Omitting it will drop you in an interactive shell
Expand Down Expand Up @@ -58,13 +58,20 @@ fn outputfn(name: &str) -> FormattingFunc {

fn command_run(
grammar_file: &Path,
start_rule: &str,
start_rule: &Option<String>,
input_file: &Option<PathBuf>,
output_format: &Option<String>,
) -> Result<(), langlang_lib::Error> {
let importer = import::ImportResolver::new(import::RelativeImportLoader::default());
let ast = importer.resolve(grammar_file)?;
let program = compiler::Compiler::default().compile(&ast, start_rule)?;
// This is a little ugly but it's converting from &Option<String> to Option<&str>
let program = compiler::Compiler::default().compile(
&ast,
match start_rule {
Some(n) => Some(&n),
None => None,
},
)?;
let fmt = outputfn(output_format.as_ref().unwrap_or(&"raw".to_string()));

match input_file {
Expand Down
27 changes: 23 additions & 4 deletions langlang_lib/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ pub struct Compiler {
// Map from set of positions of the first instruction of rules to
// the position of their index in the strings map
identifiers: HashMap<usize, usize>,
// Vec of identifier addresses sorted by the order they appear in
// the grammar.
identifier_names: Vec<usize>,
// Map from call site addresses to production names that keeps
// calls that need to be patched because they occurred syntaticaly
// before the definition of the production
Expand Down Expand Up @@ -110,6 +113,7 @@ impl Compiler {
strings: vec![],
strings_map: HashMap::new(),
identifiers: HashMap::new(),
identifier_names: Vec::new(),
funcs: HashMap::new(),
addrs: HashMap::new(),
labels: HashMap::new(),
Expand All @@ -122,7 +126,11 @@ impl Compiler {

/// compile a Grammar in its AST form into a program executable by
/// the virtual machine
pub fn compile(&mut self, grammar: &ast::Grammar, main: &str) -> Result<Program, Error> {
pub fn compile(
&mut self,
grammar: &ast::Grammar,
main: Option<&str>,
) -> Result<Program, Error> {
DetectLeftRec::default().run(grammar, &mut self.left_rec)?;
self.code_gen(grammar);
self.backpatch_callsites()?;
Expand Down Expand Up @@ -212,11 +220,21 @@ impl Compiler {
/// Find the address of the production `main` and write a call
/// instruction pointing to such address at the first entry of the
/// code vector.
fn pick_main(&mut self, main: &str) {
let id = self.push_string(main);
fn pick_main(&mut self, main: Option<&str>) {
let (id, name) = match main {
None => (
self.identifier_names[0],
&self.strings[self.identifier_names[0]],
),
Some(name) => {
let sid = self.push_string(name);
(sid, &self.strings[sid])
}
};
// let id = self.push_string(main);
let addr = self.funcs[&id];
// Mark Ps as left recursive if the detector marked it as such
let lr = if self.left_rec.get(main).is_some() && self.left_rec[main] {
let lr = if self.left_rec.get(name).is_some() && self.left_rec[name] {
1
} else {
0
Expand Down Expand Up @@ -273,6 +291,7 @@ impl<'ast> Visitor<'ast> for Compiler {
let addr = self.cursor;
let strid = self.push_string(&n.name);
self.identifiers.insert(addr, strid);
self.identifier_names.push(strid);
self.visit_expression(&n.expr);
self.emit(Instruction::Return);
self.funcs.insert(strid, addr);
Expand Down
9 changes: 9 additions & 0 deletions langlang_lib/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ impl<'ast> Visitor<'ast> for DepFinder<'ast> {
self.visit_definition(def);
}
}

fn visit_label(&mut self, n: &'ast ast::Label) {
if self.deps.get(&n.label).is_none() {
if let Some(def) = self.grammar.definitions.get(&n.label) {
self.deps.insert(&n.label, def);
self.visit_definition(def);
}
}
}
}

#[derive(Default)]
Expand Down
4 changes: 4 additions & 0 deletions tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ path = "unit.rs"
[[test]]
name = "wshinsert"
path = "wshinsert.rs"

[[test]]
name = "import_integration"
path = "import_integration.rs"
8 changes: 6 additions & 2 deletions tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ pub fn compile(cc: &compiler::Config, grammar: &str, start: &str) -> vm::Program
let ast = importer.resolve(Path::new("main")).unwrap();
println!("PEG:\n{}", ast.to_string());
let mut c = compiler::Compiler::new(cc.clone());
let program = c.compile(&ast, start).unwrap();
let program = c.compile(&ast, Some(start)).unwrap();
println!("PROGRAM:\n{}", program);
program
}

#[allow(dead_code)]
pub fn compile_file(cc: &compiler::Config, grammar_file: &str, start_rule: &str) -> vm::Program {
pub fn compile_file(
cc: &compiler::Config,
grammar_file: &str,
start_rule: Option<&str>,
) -> vm::Program {
let importer = import::ImportResolver::new(import::RelativeImportLoader::default());
let ast = importer.resolve(Path::new(grammar_file)).unwrap();
let mut c = compiler::Compiler::new(cc.clone());
Expand Down
6 changes: 6 additions & 0 deletions tests/import_gr_expr.peg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@import Value from "./import_gr_value.peg"

Expr <- Term EOF^eof
Term <- Multi (( '+' / '-' ) Multi^term_right_operand )*
Multi <- Primary (( '*' / '/' ) Primary^multi_right_operand)*
Primary <- Value / '(' Expr ')'
6 changes: 6 additions & 0 deletions tests/import_gr_number.peg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This grammar has no dependencies
Number <- Binary / Hexadecimal / Float / Decimal
Float <- Decimal #('.' Decimal)
Decimal <- [1-9][0-9]* / '0'
Binary <- '#' [bB] [0-1]+
Hexadecimal <- '#' [xX] [a-fA-F0-9]+
3 changes: 3 additions & 0 deletions tests/import_gr_string.peg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
String <- SingleQuote / DoubleQuote
SingleQuote <- "'" (!"'" .)* "'"
DoubleQuote <- '"' (!'"' .)* '"'
10 changes: 10 additions & 0 deletions tests/import_gr_value.peg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@import Number from "./import_gr_number.peg"
@import String from "./import_gr_string.peg"

Value <- String / Number / Boolean
Boolean <- 'true' / 'false'

// overrides what's defined within Number
Binary <- '0b' [0-1]+
Hexadecimal <- '0x' [a-fA-F0-9]+^LabelHex
LabelHex <- (!' ' .)*
17 changes: 17 additions & 0 deletions tests/import_integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
mod helpers;
use helpers::{assert_match, compile_file, run_str};

use langlang_lib::compiler;

#[test]
fn test_import() {
let cc = compiler::Config::default();
let program = compile_file(&cc, "./import_gr_expr.peg", None);

// This test ensures that `LabelHex` is also imported as a
// dependency from the `Hexadecimal` production.
assert_match(
"Expr[Term[Multi[Primary[Value[Number[Hexadecimal[0xError[LabelHex]]]]]]+Multi[Primary[Value[Number[Decimal[3]]]]]]]",
run_str(&program, "0xG + 3"),
)
}
2 changes: 1 addition & 1 deletion tests/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ fn test_expand_tree_0() {
let rewrite = compiler::expand(&original_ast);

let mut c = compiler::Compiler::new(cc);
let list_program = c.compile(&rewrite, "A").unwrap();
let list_program = c.compile(&rewrite, Some("A")).unwrap();
let value = vm::VM::new(&list_program).run(vec![output.unwrap().unwrap()]);
assert_match("A[A[F]]", value);
}
2 changes: 1 addition & 1 deletion tests/wshinsert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ fn test_lexification_on_single_item() {

fn run(start: &str, input: &str) -> Result<Option<Value>, vm::Error> {
let cc = compiler::Config::default();
let program = helpers::compile_file(&cc, "wshinsert.peg", start);
let program = helpers::compile_file(&cc, "wshinsert.peg", Some(start));
helpers::run_str(&program, input)
}

0 comments on commit 3776c05

Please sign in to comment.