Skip to content

cryptopatrick/gf-core

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

42 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 


GF-CORE
GF-CORE

Rust implementation of the Grammatical Framework runtime.

Crates.io Downloads Test workflow status Documentation GitHub license

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

πŸ›Ž Important Notices

  • 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

Table of Contents
  1. What is GF-CORE
  2. Features
  3. How to Use
  4. Documentation
  5. Author
  6. Support
  7. Contributing
  8. License

πŸ€” What is GF-CORE

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.

Use Cases

  • 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

Architecture

The library provides a complete GF runtime environment:

  1. PGF Loading: Parse and load Portable Grammar Format files
  2. Abstract Syntax: Work with grammar-defined abstract syntax trees
  3. Concrete Syntax: Handle multiple concrete language implementations
  4. Bidirectional Processing: Parse natural language to AST and linearize AST to natural language

πŸ“· Features

Core Runtime Capabilities

  • 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

Parsing & Linearization

  • 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

PGF Support

  • 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

JSON Integration

  • 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

πŸš™ How to Use

Requirements

gf-core requires:

  • Rust 1.70 or higher
  • Pre-compiled PGF files (generated using the official GF compiler)

Installation

Install with cargo:

cargo add gf-core

Step 1: Write a grammar

To use this crate we need a grammar. Typically we want to write three grammars; an abstract grammar, and two concrete grammars.

Abstact Grammar File: Hello.gf

 -- a "Hello World" grammar
    abstract Hello = {
  
      flags startcat = Greeting ;
  
      cat Greeting ; Recipient ;
  
      fun
        Hello : Recipient -> Greeting ;
        World, Mum, Friends : Recipient ;
    }

Concrete English Grammar File: HelloEng.gf

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 Italian Grammar File: HelloIta.gf

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"} ;
    }

Step 2: Compile the grammars into PGF

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.gf

Running 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.

Step 3: Use the runtime

Example

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:

  1. Load the Food.pgf grammar file
  2. Parse it and convert it to JSON
  3. Create a GFGrammar from the JSON
  4. Parse abstract syntax expressions into ASTs
  5. Linearize ASTs back to natural language text
  6. Demonstrate natural language parsing capabilities

πŸ“š Documentation

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

πŸ–Š Author

CryptoPatrick

Keybase Verification:
https://keybase.io/cryptopatrick/sigs/8epNh5h2FtIX1UNNmf8YQ-k33M8J-Md4LnAN

🐣 Support

Leave a ⭐ if you think this project is cool.

🀝 Contributing

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

πŸ—„ License

This project is licensed under MIT. See LICENSE for details.

About

Runtime for Grammatical Framework

Resources

License

MIT and 2 other licenses found

Licenses found

MIT
LICENSE
Unlicense
UNLICENSE
Unknown
COPYING

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •