Skip to content

Commit

Permalink
Starts prototype for the parser
Browse files Browse the repository at this point in the history
  • Loading branch information
cuducos committed Sep 22, 2023
1 parent 6da2751 commit 47519d3
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 54 deletions.
10 changes: 7 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
mod model;
mod parser;
mod reader;
mod tokenizer;

use anyhow::Result;

use crate::model::model_to_text_cli;
use crate::tokenizer::tokenize_cli;
use model::model_to_text_cli;
use parser::parser_cli;
use tokenizer::tokenize_cli;

pub fn tokenize(path: &String) -> Result<()> {
tokenize_cli(path)
Expand All @@ -14,3 +15,6 @@ pub fn tokenize(path: &String) -> Result<()> {
pub fn model_to_text() -> Result<()> {
model_to_text_cli()
}
pub fn parser(path: &String) -> Result<()> {
parser_cli(path)
}
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::env::args;

use anyhow::Result;
use createnv::{model_to_text, tokenize};
use createnv::{model_to_text, parser, tokenize};

fn main() -> Result<()> {
if let Some(path) = args().nth(1) {
tokenize(&path)?;
parser(&path)?;

return Ok(());
}
Expand Down
94 changes: 47 additions & 47 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ use rand::{thread_rng, Rng};
const DEFAULT_RANDOM_CHARS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*(-_=+)";

struct Comment {
pub struct Comment {
contents: String,
}

impl Comment {
fn new(contents: &str) -> Self {
pub fn new(contents: &str) -> Self {
Self {
contents: contents.to_string(),
}
Expand All @@ -33,7 +33,7 @@ trait Variable {
}
}

struct SimpleVariable {
pub struct SimpleVariable {
input: Option<String>,

name: String,
Expand Down Expand Up @@ -73,7 +73,7 @@ impl Variable for SimpleVariable {
}
}

struct AutoGeneratedVariable {
pub struct AutoGeneratedVariable {
name: String,
pattern: String,

Expand Down Expand Up @@ -110,7 +110,7 @@ impl Variable for AutoGeneratedVariable {
}
}

struct VariableWithRandomValue {
pub struct VariableWithRandomValue {
name: String,
length: Option<i32>,
}
Expand Down Expand Up @@ -144,51 +144,55 @@ impl Variable for VariableWithRandomValue {
}
}

enum VariableType {
pub enum VariableType {
Input(SimpleVariable),
AutoGenerated(AutoGeneratedVariable),
Random(VariableWithRandomValue),
}

struct Block {
pub struct Block {
title: Comment,
description: Option<Comment>,
variables: Vec<VariableType>,

context: HashMap<String, String>,
}

impl Block {
fn new(title: Comment, description: Option<Comment>, variables: Vec<VariableType>) -> Self {
let context: HashMap<String, String> = HashMap::new();
let has_auto_generated_variables = variables
.iter()
.any(|v| matches!(v, VariableType::AutoGenerated(_)));

let mut block = Self {
pub fn new(title: Comment, description: Option<Comment>) -> Self {
Self {
title,
description,
variables,
context,
};
variables: vec![],
}
}

if has_auto_generated_variables {
for variable in &block.variables {
match variable {
VariableType::Input(var) => block.context.insert(var.key(), var.value()),
VariableType::AutoGenerated(_) => None,
VariableType::Random(var) => block.context.insert(var.key(), var.value()),
};
}
fn has_auto_generated_variables(&self) -> bool {
self.variables
.iter()
.any(|v| matches!(v, VariableType::AutoGenerated(_)))
}

for variable in &mut block.variables {
if let VariableType::AutoGenerated(var) = variable {
var.load_context(&block.context);
}
}
pub fn push(&mut self, variable: VariableType) {
self.variables.push(variable);
if !self.has_auto_generated_variables() {
return;
}

block
let mut context = HashMap::new();
for var in &self.variables {
match var {
VariableType::AutoGenerated(_) => None,
VariableType::Input(v) => context.insert(v.key(), v.value()),
VariableType::Random(v) => context.insert(v.key(), v.value()),
};
}

let mut variables: Vec<VariableType> = vec![];
for variable in &mut variables {
if let VariableType::AutoGenerated(var) = variable {
var.load_context(&context);
}
}
self.variables = variables;
}
}

Expand Down Expand Up @@ -280,11 +284,9 @@ mod tests {
let mut variable1 = SimpleVariable::new("ANSWER", None, None);
variable1.user_input("42");
let variable2 = SimpleVariable::new("AS_TEXT", Some("fourty two"), None);
let variables = vec![
VariableType::Input(variable1),
VariableType::Input(variable2),
];
let block = Block::new(title, description, variables);
let mut block = Block::new(title, description);
block.push(VariableType::Input(variable1));
block.push(VariableType::Input(variable2));
let got = block.to_string();
assert_eq!(got, "# 42\n# Fourty-two\nANSWER=42\nAS_TEXT=fourty two")
}
Expand All @@ -305,15 +307,13 @@ pub fn model_to_text_cli() -> Result<()> {
let variable5 = VariableWithRandomValue::new("SECRET_KEY", None);
let variable6 = AutoGeneratedVariable::new("AUTO_GENERATED", "{ANSWER}-{DEFAULT_VALUE_ONE}");

let variables = vec![
VariableType::Input(variable1),
VariableType::Input(variable2),
VariableType::Input(variable3),
VariableType::Input(variable4),
VariableType::Random(variable5),
VariableType::AutoGenerated(variable6),
];
let block = Block::new(title, description, variables);
let mut block = Block::new(title, description);
block.push(VariableType::Input(variable1));
block.push(VariableType::Input(variable2));
block.push(VariableType::Input(variable3));
block.push(VariableType::Input(variable4));
block.push(VariableType::Random(variable5));
block.push(VariableType::AutoGenerated(variable6));
println!("{block}");

Ok(())
Expand Down
126 changes: 126 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use std::path::PathBuf;

use anyhow::{anyhow, Result};

use crate::{
model::{Block, Comment},
tokenizer::{Token, Tokenizer},
};

struct Parser {
tokens: Tokenizer,
path: String,
previous_token: Option<Token>,
current_token: Option<Token>,
}

impl Parser {
pub fn new(path: &String) -> Result<Self> {
Ok(Self {
tokens: Tokenizer::new(PathBuf::from(path))?,
path: path.clone(),
current_token: None,
previous_token: None,
})
}

fn load_next_token(&mut self) -> Result<()> {
self.previous_token = self.current_token.take();
match self.tokens.next() {
Some(token) => self.current_token = Some(token?),
None => self.current_token = None,
}

Ok(())
}

fn error(&self, msg: &str) -> anyhow::Error {
let prefix = if let Some(curr) = &self.current_token {
curr.error_prefix(&self.path)
} else if let Some(prev) = &self.previous_token {
prev.error_prefix(&self.path)
} else {
"EOF".to_string()
};

anyhow!("{}: {}", prefix, msg)
}

fn parse_title(&mut self) -> Result<String> {
self.load_next_token()?;
match self.current_token {
Some(Token::CommentMark(_, _)) => (),
Some(_) => return Err(self.error("Expected a title line starting with `#`")),
None => {
return Err(
self.error("Expected a title line starting with `#`, got the end of the file")
)
}
}

self.load_next_token()?;
match &self.current_token {
Some(Token::Text(_, _, text)) => Ok(text.clone()),
Some(_) => Err(self.error("Expected the text of the title")),
None => Err(self.error("Expected the text of the title, got the end of the file")),
}
}

fn parse_description(&mut self) -> Result<Option<String>> {
self.load_next_token()?;
match self.current_token {
Some(Token::CommentMark(_, _)) => (),
Some(_) => return Ok(None),
None => return Err(self.error("Expected a descrition line starting with `#` or a variable definition, got the end of the file")),
}

self.load_next_token()?;
match &self.current_token {
Some(Token::Text(_, _, text)) => Ok(Some(text.clone())),
Some(_) => Err(self.error("Expected a descrition text")),
None => Err(self.error("Expected a descrition text, got the end of the file")),
}
}

pub fn parse(&mut self) -> Result<Vec<Block>> {
let mut blocks: Vec<Block> = vec![];
let title = Comment::new(self.parse_title()?.as_str());
let descrition = self
.parse_description()?
.map(|desc| Comment::new(desc.as_str()));
blocks.push(Block::new(title, descrition));

Ok(blocks)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parser() {
let sample = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join(".env.sample")
.into_os_string()
.into_string();
let parsed = Parser::new(&sample.unwrap()).unwrap().parse().unwrap();
let got = parsed
.iter()
.map(|block| block.to_string())
.collect::<Vec<String>>()
.join("\n");
let expected = "# Createnv\n# This is a simple example of how Createnv works".to_string();
assert_eq!(expected, got);
}
}
//
// TODO: remove (just written for manual tests & debug)
pub fn parser_cli(path: &String) -> Result<()> {
let mut parser = Parser::new(path)?;
for block in parser.parse()? {
println!("{block}");
}

Ok(())
}
6 changes: 3 additions & 3 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ use anyhow::Result;
use crate::reader::{CharReader, CharType};

#[derive(Debug, PartialEq)]
enum Token {
pub enum Token {
Text(usize, usize, String),
CommentMark(usize, usize),
HelpMark(usize, usize),
EqualSign(usize, usize),
}

impl Token {
fn error_prefix(&self, path: &String) -> String {
pub fn error_prefix(&self, path: &String) -> String {
let (line, column) = match self {
Token::Text(x, y, _) => (x, y),
Token::CommentMark(x, y) => (x, y),
Expand All @@ -25,7 +25,7 @@ impl Token {
}
}

struct Tokenizer {
pub struct Tokenizer {
reader: CharReader,
buffer: Option<Token>,
}
Expand Down

0 comments on commit 47519d3

Please sign in to comment.