lexer y parser

Proyecto

Para utilizar lalrpop, vamos a crear un nuevo proyecto en rust.

cargo new incr

Hay que hacer unos cambios al Cargo.toml

[package]
name = "inc"
version = "0.1.0"
authors = ["Humberto Ortiz-Zuazaga <humberto.ortiz@upr.edu>"]
edition = "2018"
build = "build.rs" # LALRPOP preprocessing

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[build-dependencies] # <-- We added this and everything after!
lalrpop = { version = "0.19.0", features = ["lexer"] }

[dependencies]
lalrpop-util = "0.19.0"
regex = "1"

La linea de build indica que queremos que cargo corra codigo adicional cuando hagamos cargo build. El archivo build.rs contiene el codigo a correr (ver abajo). El archivo debe estar ubicado junto con el Cargo.toml. Luego en build-depencencies, indicamos que queremos lalrpop, y que lalrpop genere el lexer. Por ultimo, en dependencies traemos unas utilidades.

El archivo build.rs deben crearlo con el siguente contenido:

extern crate lalrpop;

fn main() {
    lalrpop::process_root().unwrap();
}

lexer y parser

El lexer y parser lo especificaremos en el archivo src/nums.lalrpop:


#![allow(unused)]
fn main() {
use std::str::FromStr;

grammar;

pub Num: i64 = <s:r"[0-9]+"> => i64::from_str(s).unwrap();
}

Este archivo importa la funciones de FromStr que utilizamos para convertir String a enteros. Luego declara una gramatica, que tiene un elemento publico Num. Si encontramos un String que consiste de solo digitos r"[0-9]+" entonces convertimos esa secuencia de digitos a un i64. La expression regular r"[0-9]+" es utilizada para crear el lexer, y lo que va a la derecha de => es código de rust que corre el parser cuando reconoce el lexema de la izquierda.

main

Para terminar el compilador, vamos a crear un src/main.rs que llame el parser y genere el ensamblador.

#[macro_use] extern crate lalrpop_util;

lalrpop_mod!(pub nums); // synthesized by LALRPOP

use std::fs;
use std::env;

fn compile(prog : i64) -> String {
    format!("section .text\n\
            global our_code_starts_here\n\
            our_code_starts_here:\n\
            \tmov RAX, {}\n\
            \tret\n", prog)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args: Vec<String> = env::args().collect();

    let filename = &args[1];

    let prog_text = fs::read_to_string(filename)?;
    let prog = nums::NumParser::new().parse(&prog_text);
    let instr = compile(prog.unwrap());
    println!("{}", instr);
    Ok(())
}

Probando el compilador

Podemos probar el compilador corriendolo con cargo:

cargo run 123.jn