Skip to content

Commit

Permalink
finish python bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
codekansas committed Oct 31, 2024
1 parent e858f84 commit 5672c6e
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 70 deletions.
4 changes: 2 additions & 2 deletions klang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ prost-build = "^0.13.3"

[[bin]]

name = "kcompile"
path = "src/bin/kcompile.rs"
name = "kompile"
path = "src/bin/kompile.rs"

[[test]]
name = "integration_test"
Expand Down
File renamed without changes.
37 changes: 22 additions & 15 deletions klang/src/parser/errors.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,49 @@
use crate::parser::Rule;
use pest::iterators::Pair;
use prost::{DecodeError, EncodeError};
use std::error::Error;
use std::fmt;

#[derive(Debug)]
pub struct ParseError {
pub message: String,
pub line: usize,
pub column: usize,
}

impl ParseError {
pub fn new(message: String) -> ParseError {
ParseError {
message,
line: 0,
column: 0,
}
ParseError { message }
}

pub fn from_pair(message: String, pair: Pair<Rule>) -> ParseError {
let (line, column) = pair.as_span().start_pos().line_col();
ParseError {
message,
line,
column,
message: format!("{} (line: {}, column: {})", message, line, column),
}
}
}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} (line: {}, column: {})",
self.message, self.line, self.column
)
write!(f, "{}", self.message)
}
}

impl Error for ParseError {}

impl From<std::io::Error> for ParseError {
fn from(error: std::io::Error) -> Self {
ParseError::new(format!("{}", error))
}
}

impl From<EncodeError> for ParseError {
fn from(error: EncodeError) -> Self {
ParseError::new(format!("{}", error))
}
}

impl From<DecodeError> for ParseError {
fn from(error: DecodeError) -> Self {
ParseError::new(format!("{}", error))
}
}
5 changes: 1 addition & 4 deletions klang/src/parser/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ pub fn parse_program(pair: pest::iterators::Pair<Rule>) -> Result<KlangProgram,
let ir_program = Program { lines: all_lines };
let ast_program = ir_to_ast(&ir_program)?;

Ok(KlangProgram {
ast_program,
ir_program,
})
Ok(KlangProgram::from_ast(&ast_program))
}

fn parse_line(line: Pair<Rule>) -> Result<Vec<Line>, ParseError> {
Expand Down
47 changes: 2 additions & 45 deletions klang/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,51 +43,8 @@ pub fn write_program_to_file(
binary: bool,
) -> Result<(), ParseError> {
if binary {
let mut buf = Vec::new();
prost::Message::encode(&program.ast_program, &mut buf).map_err(|e| {
ParseError::new(format!(
"Error encoding program to file '{}': {}",
file_path.display(),
e
))
})?;

fs::write(file_path, &buf).map_err(|e| {
ParseError::new(format!(
"Error writing program to file '{}': {}",
file_path.display(),
e
))
})?;
program.save_binary(file_path)
} else {
let mut output = String::new();

fn render_command(cmd: &ast::Command, indent: usize) -> String {
let mut result = format!("{:indent$}{}", "", cmd.text, indent = indent);
if !cmd.children.is_empty() {
result.push_str(" {\n");
for child in &cmd.children {
result.push_str(&render_command(child, indent + 2));
}
result.push_str(&format!("{:indent$}}}\n", "", indent = indent));
} else {
result.push('\n');
}
result
}

for command in &program.ast_program.commands {
output.push_str(&render_command(command, 0));
}

fs::write(file_path, &output).map_err(|e| {
ParseError::new(format!(
"Error writing program to file '{}': {}",
file_path.display(),
e
))
})?;
program.save_text(file_path)
}

Ok(())
}
98 changes: 94 additions & 4 deletions klang/src/parser/structs.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,102 @@
use super::ast::Program as AstProgram;
use super::ir::Program as IrProgram;
use super::ast::{Command as AstCommand, Program as AstProgram};
use super::errors::ParseError;
use pest_derive::Parser;
use std::fs;
use std::path::Path;

#[derive(Parser)]
#[grammar = "pest/klang.pest"]
pub struct PestParser;

pub struct Node {
pub text: String,
pub children: Vec<Node>,
}

impl Node {
pub fn to_ast(&self) -> AstCommand {
AstCommand {
text: self.text.clone(),
children: self.children.iter().map(|child| child.to_ast()).collect(),
}
}

pub fn from_ast(ast: &AstCommand) -> Self {
Node {
text: ast.text.clone(),
children: ast
.children
.iter()
.map(|child| Node::from_ast(child))

Check failure on line 30 in klang/src/parser/structs.rs

View workflow job for this annotation

GitHub Actions / Lints

redundant closure
.collect(),
}
}

pub fn to_string(&self, indent: usize) -> String {
let mut result = format!("{:indent$}{}", " ", self.text, indent = indent);
if !self.children.is_empty() {
result.push_str(" {\n");
for child in &self.children {
result.push_str(&child.to_string(indent + 2));
}
result.push_str(&format!("{:indent$}}}", " ", indent = indent));
}
result.push('\n');
result
}
}

pub struct KlangProgram {
pub ast_program: AstProgram,
pub ir_program: IrProgram,
pub program: Vec<Node>,
}

impl KlangProgram {
pub fn to_ast(&self) -> AstProgram {
AstProgram {
commands: self.program.iter().map(|node| node.to_ast()).collect(),
}
}

pub fn from_ast(ast: &AstProgram) -> Self {
KlangProgram {
program: ast
.commands
.iter()
.map(|command| Node::from_ast(command))

Check failure on line 65 in klang/src/parser/structs.rs

View workflow job for this annotation

GitHub Actions / Lints

redundant closure
.collect(),
}
}

pub fn save_binary(&self, path: &Path) -> Result<(), ParseError> {
let mut buf = Vec::new();
prost::Message::encode(&self.to_ast(), &mut buf)?;
fs::write(path, &buf)?;
Ok(())
}

pub fn load_binary(path: &Path) -> Result<Self, ParseError> {
let buf = fs::read(path)?;
let program = prost::Message::decode(&*buf)?;
Ok(KlangProgram::from_ast(&program))
}

pub fn save_text(&self, path: &Path) -> Result<(), ParseError> {
let output = self.to_text();
fs::write(path, &output)?;
Ok(())
}

pub fn to_text(&self) -> String {
self.program
.iter()
.map(|node| node.to_string(0))
.collect::<Vec<String>>()
.join("\n")
}
}

impl std::fmt::Display for KlangProgram {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_text())
}
}
21 changes: 21 additions & 0 deletions pyklang/bindings.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@
# ruff: noqa: E501, F401


class PyKlangProgram:
def save_binary(self, path:str) -> None:
...

def save_text(self, path:str) -> None:
...

def __repr__(self) -> str:
...

@staticmethod
def load_binary(path:str) -> PyKlangProgram:
...


def get_version() -> str:
...

def parse_file(path:str) -> PyKlangProgram:
...

def parse_string(input:str) -> PyKlangProgram:
...

29 changes: 29 additions & 0 deletions pyklang/kompile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Defines the PyKlang CLI."""

import argparse
from pathlib import Path

from pyklang.bindings import parse_file


def main() -> None:
parser = argparse.ArgumentParser(description="Kompile a Klang program.")
parser.add_argument("input", help="The input file to compile.")
parser.add_argument("-o", "--output", help="The output file to compile.")
parser.add_argument("-t", "--text", action="store_true", help="Output the text representation of the program.")
args = parser.parse_args()

program = parse_file(args.input)

if args.output is None:
print(program)
else:
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
if args.text:
program.save_text(args.output)
else:
program.save_binary(args.output)


if __name__ == "__main__":
main()
Loading

0 comments on commit 5672c6e

Please sign in to comment.