Rust implementation of the Grammatical Framework runtime.
Authors's bio: ππ Hi, I'm CryptoPatrick! I'm currently enrolled as an
Undergraduate student in Mathematics, at Chalmers & the University of Gothenburg, Sweden.
If you have any questions or need more info, then please join my Discord Channel: AiMath
What is GF-CORE β’ Features β’ How To Use β’ Documentation β’ License
- This is not a full Grammatical Framework compiler, only a runtime
- Requires pre-compiled PGF files generated by the official GF compiler
Table of Contents
gf-core is a pure Rust implementation of the Grammatical Framework runtime that enables developers to integrate GF-powered multilingual natural language processing directly into Rust applications without requiring a separate Haskell-based GF server.
- Multilingual Applications: Build applications that support multiple languages with consistent grammar rules
- Natural Language Interfaces: Create command-line tools and APIs with natural language parsing capabilities
- Translation Systems: Implement controlled translation between supported languages
- Grammar-Based Text Generation: Generate natural language text from abstract syntax trees
- Embedded Systems: Deploy GF capabilities in resource-constrained environments where Haskell dependencies are impractical
The library provides a complete GF runtime environment:
- PGF Loading: Parse and load Portable Grammar Format files
- Abstract Syntax: Work with grammar-defined abstract syntax trees
- Concrete Syntax: Handle multiple concrete language implementations
- Bidirectional Processing: Parse natural language to AST and linearize AST to natural language
- PGF Parser: Load and parse Portable Grammar Format files generated by GF compiler
- Abstract Syntax Trees: Full support for GF abstract syntax with tree manipulation
- Multi-language Support: Handle multiple concrete grammars simultaneously
- Type Safety: Rust's type system ensures memory safety and correctness
- Natural Language Parsing: Convert text strings to abstract syntax trees
- Tree Linearization: Generate natural language text from abstract syntax
- Bidirectional Processing: Seamlessly switch between parsing and generation
- Grammar Validation: Ensure input conforms to loaded grammar rules
- Binary Format: Direct loading of compiled PGF files
- JSON Conversion: Convert PGF data to/from JSON for interoperability
- Grammar Introspection: Access grammar metadata and structure
- Multiple Concrete Grammars: Support for multilingual grammar files
- Serialization: Convert grammar structures to JSON format
- Deserialization: Load grammars from JSON representations
- API Compatibility: Easy integration with web services and APIs
- Debug Output: Human-readable grammar inspection
gf-core requires:
- Rust 1.70 or higher
- Pre-compiled PGF files (generated using the official GF compiler)
Install with cargo:
cargo add gf-coreTo use this crate we need a grammar. Typically we want to write three grammars; an abstract grammar, and two concrete grammars.
-- a "Hello World" grammar
abstract Hello = {
flags startcat = Greeting ;
cat Greeting ; Recipient ;
fun
Hello : Recipient -> Greeting ;
World, Mum, Friends : Recipient ;
}concrete HelloEng of Hello = {
lincat Greeting, Recipient = {s : Str} ;
lin
Hello recip = {s = "hello" ++ recip.s} ;
World = {s = "world"} ;
Mum = {s = "mum"} ;
Friends = {s = "friends"} ;
}concrete HelloIta of Hello = {
lincat Greeting, Recipient = {s : Str} ;
lin
Hello recip = {s = "ciao" ++ recip.s} ;
World = {s = "mondo"} ;
Mum = {s = "mamma"} ;
Friends = {s = "amici"} ;
}
Once we have our grammars written, we need to compile them into PGF (Portable Grammar Format). To do this, we need the GF CLI, which is part of the GF binary.
The GF binary and cli can be downloaded from this page
# Compile the grammar into Hello.pgf
gf -make HelloEng.gf HelloIta.gfRunning this command, GF will look at Hello.gf (since it's what the concrete syntaxes depend on) and then produce the PGF file: Hello.pgf.
Here's a complete example demonstrating the gf-core runtime:
use gf_core::*;
use std::fs;
use bytes::Bytes;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Uncomment the line below to see debug output from GF-Core operations
set_debug(true);
// Read the binary file containing the PGF grammar.
let pgf_content = fs::read("grammars/Food/Food.pgf")?;
// Parse the binary content of the PGF file.
let pgf = pgf2json::parse_pgf(&Bytes::from(pgf_content))?;
// Convert the PGF data into JSON formatl
let json_string = pgf2json::pgf_to_json(&pgf)?;
// Parse JSON into JSON values.
let json_value: serde_json::Value = serde_json::from_str(&json_string)?;
// Create a JSON valued PGF struct.
let pgf_struct: PGF = serde_json::from_value(json_value)?;
println!("TODO: Understand content of (pgf_struct): {:?}", pgf_struct);
// Create a GF grammar from the PGF struct.
let grammar: GFGrammar = GFGrammar::from_json(pgf_struct);
// Test our fixed abstract syntax parsing
let test_cases = [
"Fish",
"This(Fish)",
"Is(This(Fish), Delicious)",
"MakeS (NP) (VP)",
];
for test_case in test_cases {
println!("\nTesting abstract syntax: '{}'", test_case);
match grammar.abstract_grammar.parse_tree(test_case, None) {
Some(tree) => {
println!("β Parsed successfully: {}", tree.print());
// Try to linearize this tree
if let Some(eng_concrete) = grammar.concretes.get("FoodEng") {
println!("LOG: Attempting linearization with FoodEng concrete grammar");
let english_output = eng_concrete.linearize(&tree);
println!(" English: '{}'", english_output);
}
}
None => {
println!("β Failed to parse");
}
}
}
// Also test the natural language parsing for comparison
println!("\n=== Natural Language Parsing (for comparison) ===");
if let Some(eng_concrete) = grammar.concretes.get("FoodEng") {
let trees = eng_concrete.parse_string("this fish is delicious", &grammar.abstract_grammar.startcat);
if trees.is_empty() {
println!("No valid parse found for 'this fish is delicious'");
} else {
println!("Found {} parse tree(s):", trees.len());
for (i, tree) in trees.iter().enumerate() {
println!(" Tree {}: {}", i + 1, tree.print());
// Linearize back to English
let english_output = eng_concrete.linearize(tree);
println!(" English: {}", english_output);
}
}
}
// Note: FoodIta concrete grammar not available in this PGF file
println!("Available concrete grammars: {:?}", grammar.concretes.keys().collect::<Vec<_>>());
Ok(())
}This program will:
- Load the
Food.pgfgrammar file - Parse it and convert it to JSON
- Create a GFGrammar from the JSON
- Parse abstract syntax expressions into ASTs
- Linearize ASTs back to natural language text
- Demonstrate natural language parsing capabilities
Comprehensive documentation is available at docs.rs/gf-core, including:
- API reference for all public types and functions
- Tutorial on working with GF grammars in Rust
- Examples of different parsing and linearization strategies
- Performance considerations and best practices
Keybase Verification:
https://keybase.io/cryptopatrick/sigs/8epNh5h2FtIX1UNNmf8YQ-k33M8J-Md4LnAN
Leave a β if you think this project is cool.
Found a bug? Missing a specific feature? Contributions are welcome! Please see our contributing guidelines for details on:
- Code style and testing requirements
- Submitting bug reports and feature requests
- Development setup and workflow
This project is licensed under MIT. See LICENSE for details.
