first draft after day 1
This commit is contained in:
commit
4534d405da
11 changed files with 676 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
23
Cargo.lock
generated
Normal file
23
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "vis"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "vis"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
unicode-segmentation = "1.1"
|
||||
64
README.md
Normal file
64
README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
Just started
|
||||
|
||||
sample vis file:
|
||||
|
||||
```
|
||||
markup {
|
||||
lanes {
|
||||
functions {
|
||||
calc: "Calculation"
|
||||
acc_interest_calc: "Account interest Calculation"
|
||||
interest_rates: "Interest Rates"
|
||||
config: "Configuration"
|
||||
nob: "NoB" {
|
||||
nob_execution: "NoB Execution"
|
||||
coll_reinst_inst: "Collection of Reinstatement instructions"
|
||||
}
|
||||
reporting: "Reporting"
|
||||
}
|
||||
systems {
|
||||
bank: "Bank" {
|
||||
bank_motor: "Bank Motor"
|
||||
bank_scripts: "Bank Scripts"
|
||||
bank_client: "Bank Client"
|
||||
bank_db: "Bank DB"
|
||||
}
|
||||
interest_engine: "InterestEngine"
|
||||
}
|
||||
}
|
||||
bank-->calc
|
||||
bank_scripts--<>bank_db
|
||||
bank_motor--<>bank_db
|
||||
interest_engine-->calc
|
||||
}
|
||||
|
||||
styles {
|
||||
lanes(group) {
|
||||
type: textnode
|
||||
orientation: horizontal
|
||||
shape: rectangle
|
||||
font-family: arial
|
||||
border-width: 1px
|
||||
border-color: gray
|
||||
}
|
||||
functions(group) {
|
||||
background-color: yellow
|
||||
font-family: arial
|
||||
border-radius: 20px
|
||||
border-width: 1px
|
||||
border-color: gray
|
||||
}
|
||||
systems(group) {
|
||||
background-color: lightblue
|
||||
}
|
||||
tag1: "⚭" { // how will this work?
|
||||
right:0px
|
||||
top:0px
|
||||
}
|
||||
tag2: {
|
||||
itchy: scratchy
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Will have to be turned into an architecture diagram... we'll see how it goes!
|
||||
56
src/cashpool.vis
Normal file
56
src/cashpool.vis
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
markup {
|
||||
lanes {
|
||||
functions {
|
||||
calc: "Calculation"
|
||||
acc_interest_calc: "Account interest Calculation"
|
||||
interest_rates: "Interest Rates"
|
||||
config: "Configuration"
|
||||
nob: "NoB" {
|
||||
nob_execution: "NoB Execution"
|
||||
coll_reinst_inst: "Collection of Reinstatement instructions"
|
||||
}
|
||||
reporting: "Reporting"
|
||||
}
|
||||
systems {
|
||||
bank: "Bank" {
|
||||
bank_motor: "Bank Motor"
|
||||
bank_scripts: "Bank Scripts"
|
||||
bank_client: "Bank Client"
|
||||
bank_db: "Bank DB"
|
||||
}
|
||||
interest_engine: "InterestEngine"
|
||||
}
|
||||
}
|
||||
bank-->calc
|
||||
bank_scripts--<>bank_db
|
||||
bank_motor--<>bank_db
|
||||
interest_engine-->calc
|
||||
}
|
||||
|
||||
styles {
|
||||
lanes(group) {
|
||||
type: textnode
|
||||
orientation: horizontal
|
||||
shape: rectangle
|
||||
font-family: arial
|
||||
border-width: 1px
|
||||
border-color: gray
|
||||
}
|
||||
functions(group) {
|
||||
background-color: yellow
|
||||
font-family: arial
|
||||
border-radius: 20px
|
||||
border-width: 1px
|
||||
border-color: gray
|
||||
}
|
||||
systems(group) {
|
||||
background-color: lightblue
|
||||
}
|
||||
tag1: "⚭" { // how will this work?
|
||||
right:0px
|
||||
top:0px
|
||||
}
|
||||
tag2: {
|
||||
itchy: scratchy
|
||||
}
|
||||
}
|
||||
25
src/grammar.txt
Normal file
25
src/grammar.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
node: markup | styles
|
||||
markup: nodes
|
||||
elements: "{" element* "}"
|
||||
element: node | edge
|
||||
node: (id (":" title)? nodes?) | edgenode
|
||||
edge: idref arrow idref
|
||||
arrow: ArrowLeft | ArrowRight | DiamondArrowLeft | DiamondArrowRight
|
||||
ArrowLeft: "<--"
|
||||
ArrowRight: "-->"
|
||||
DiamondArrowLeft: "<>--"
|
||||
DiamondArrowRight: "--<>"
|
||||
id: text
|
||||
title: string
|
||||
string: """ text """
|
||||
|
||||
|
||||
styles: style*
|
||||
style: idref attributes? ":" style-elements
|
||||
style-elements: "{" style-element "}"
|
||||
style-element: key ":" value
|
||||
key: text
|
||||
value: text
|
||||
idref: text
|
||||
attributes: "(" attribute ")"
|
||||
attribute: text ("," attribute)*
|
||||
32
src/lib.rs
Normal file
32
src/lib.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use tokens::TokenType;
|
||||
|
||||
pub mod parser;
|
||||
mod scanner;
|
||||
mod tokens;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Vis {
|
||||
pub markup: Vec<Element>,
|
||||
pub styles: Vec<StyleNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Element {
|
||||
Node(String, Option<String>, Vec<Element>),
|
||||
Edge(String, String, TokenType, Option<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StyleNode {
|
||||
pub id_ref: String,
|
||||
pub containertype: ContainerType,
|
||||
pub attributes: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ContainerType {
|
||||
Node,
|
||||
Group,
|
||||
}
|
||||
41
src/main.rs
Normal file
41
src/main.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use anyhow::anyhow;
|
||||
use std::env::args;
|
||||
use std::fs;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args: Vec<String> = args().collect();
|
||||
if args.len() != 2 {
|
||||
return Err(anyhow!("Usage: vis vis-file"));
|
||||
} else {
|
||||
let vis_file = read_file(&args[1])?;
|
||||
let vis = vis::parser::parse_vis(vis_file.as_str())?;
|
||||
println!("{:?}", vis);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_file(file_name: &str) -> anyhow::Result<String> {
|
||||
fs::read_to_string(file_name).map_err(|e| anyhow!("Cannot read file '{}': {}", file_name, e))
|
||||
}
|
||||
|
||||
//frankensteinwoorden:
|
||||
//loopbaanrecord
|
||||
//vriendendienstplicht
|
||||
//brandweerbericht
|
||||
//antiloopgraaf
|
||||
//blikveldslag
|
||||
//zeurpietenhuis
|
||||
//krentenbroodmager
|
||||
//opzijweg
|
||||
//doorstroomversnelling
|
||||
//koektrommelvlies
|
||||
//draaideuropening
|
||||
//luchtsteuntrekker
|
||||
//boekhoudhakker
|
||||
//krantenkopstoot
|
||||
//prijzenoorlogsverslaggeving
|
||||
//dwaallichtknop
|
||||
//slaapwandeltocht
|
||||
//rampspoedafdeling
|
||||
//
|
||||
163
src/parser.rs
Normal file
163
src/parser.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
use crate::{
|
||||
tokens::{
|
||||
Token,
|
||||
TokenType::{self, *},
|
||||
},
|
||||
Element, StyleNode, Vis,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
|
||||
pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
|
||||
let tokens = crate::scanner::scan(contents)?;
|
||||
println!("{:?}", tokens);
|
||||
let mut parser = Parser::new(tokens);
|
||||
|
||||
Ok(Vis {
|
||||
markup: parser.markup()?,
|
||||
styles: parser.styles()?,
|
||||
})
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Self { tokens, current: 0 }
|
||||
}
|
||||
|
||||
fn markup(&mut self) -> anyhow::Result<Vec<Element>> {
|
||||
if self.match_token(Markup) {
|
||||
self.nodes()
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
fn nodes(&mut self) -> anyhow::Result<Vec<Element>> {
|
||||
println!("nodes {:?}", self.peek());
|
||||
self.consume(LeftBrace, "Expected '{'")?;
|
||||
let mut nodes = vec![];
|
||||
while !self.match_token(RightBrace) {
|
||||
nodes.push(self.node()?);
|
||||
}
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
fn node(&mut self) -> anyhow::Result<Element> {
|
||||
println!("node {:?}", self.peek());
|
||||
let id = self.id()?;
|
||||
println!("id {}", id);
|
||||
let current = self.peek().clone();
|
||||
if self.match_tokens(vec![
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
DiamondArrowRight,
|
||||
DiamondArrowLeft,
|
||||
]) {
|
||||
self.edge(id, current)
|
||||
} else {
|
||||
let title = self.title()?;
|
||||
println!("title {:?}", title);
|
||||
let children = if self.check(&LeftBrace) {
|
||||
self.nodes()?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Ok(Element::Node(id, title, children))
|
||||
}
|
||||
}
|
||||
|
||||
fn edge(&mut self, from_id: String, arrow: Token) -> anyhow::Result<Element> {
|
||||
let to_id = self.id()?;
|
||||
let title = self.title()?;
|
||||
Ok(Element::Edge(from_id, to_id, arrow.tokentype, title))
|
||||
}
|
||||
|
||||
fn title(&mut self) -> anyhow::Result<Option<String>> {
|
||||
if self.check(&Colon) {
|
||||
self.advance();
|
||||
Ok(Some(self.string()?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&mut self) -> anyhow::Result<String> {
|
||||
self.text()
|
||||
}
|
||||
|
||||
fn string(&mut self) -> anyhow::Result<String> {
|
||||
let text = self.peek().clone();
|
||||
self.consume(Str, "Expected quoted string")?;
|
||||
Ok(text.lexeme.to_owned())
|
||||
}
|
||||
|
||||
fn text(&mut self) -> anyhow::Result<String> {
|
||||
let text = self.peek().clone();
|
||||
self.consume(Identifier, "Expected text")?;
|
||||
Ok(text.lexeme.to_owned())
|
||||
}
|
||||
|
||||
fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn consume(&mut self, tokentype: TokenType, expect: &str) -> anyhow::Result<&Token> {
|
||||
let current = self.peek();
|
||||
if self.check(&tokentype) {
|
||||
Ok(self.advance())
|
||||
} else {
|
||||
Err(anyhow!("Error: {} on line {}", expect, current.line))
|
||||
}
|
||||
}
|
||||
|
||||
fn match_tokens(&mut self, tokentypes: Vec<TokenType>) -> bool {
|
||||
for tokentype in tokentypes.iter() {
|
||||
if self.check(tokentype) {
|
||||
self.advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn match_token(&mut self, tokentype: TokenType) -> bool {
|
||||
if self.check(&tokentype) {
|
||||
self.advance();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn check(&self, tokentype: &TokenType) -> bool {
|
||||
if self.is_at_end() {
|
||||
false
|
||||
} else {
|
||||
&self.peek().tokentype == tokentype
|
||||
}
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &Token {
|
||||
if !self.is_at_end() {
|
||||
self.current += 1;
|
||||
}
|
||||
self.previous()
|
||||
}
|
||||
|
||||
fn previous(&self) -> &Token {
|
||||
&self.tokens[self.current - 1]
|
||||
}
|
||||
|
||||
fn is_at_end(&self) -> bool {
|
||||
self.peek().tokentype == TokenType::Eof
|
||||
}
|
||||
|
||||
fn peek(&self) -> &Token {
|
||||
&self.tokens[self.current]
|
||||
}
|
||||
}
|
||||
209
src/scanner.rs
Normal file
209
src/scanner.rs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::tokens::{
|
||||
Token,
|
||||
TokenType::{self, *},
|
||||
KEYWORDS,
|
||||
};
|
||||
|
||||
pub fn scan(vis: &str) -> anyhow::Result<Vec<Token>> {
|
||||
let mut scanner = Scanner::new(vis);
|
||||
scanner.scan();
|
||||
Ok(scanner.tokens)
|
||||
}
|
||||
|
||||
struct Scanner<'a> {
|
||||
tokens: Vec<Token>,
|
||||
chars: Vec<&'a str>,
|
||||
current_pos: usize,
|
||||
current_line: usize,
|
||||
start_pos: usize,
|
||||
}
|
||||
impl<'a> Scanner<'a> {
|
||||
fn new(vis: &'a str) -> Self {
|
||||
Self {
|
||||
tokens: vec![],
|
||||
chars: UnicodeSegmentation::graphemes(vis, true).collect::<Vec<&str>>(),
|
||||
current_pos: 0,
|
||||
current_line: 0,
|
||||
start_pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn scan(&mut self) {
|
||||
while !self.is_at_end() {
|
||||
self.start_pos = self.current_pos;
|
||||
self.scan_token();
|
||||
}
|
||||
self.add_token(Eof);
|
||||
}
|
||||
|
||||
fn scan_token(&mut self) {
|
||||
let c = self.advance();
|
||||
match c {
|
||||
"(" => self.add_token(LeftParen),
|
||||
")" => self.add_token(RightParen),
|
||||
"{" => self.add_token(LeftBrace),
|
||||
"}" => self.add_token(RightBrace),
|
||||
"," => self.add_token(Comma),
|
||||
"." => self.add_token(Dot),
|
||||
|
||||
"-" => {
|
||||
if self.match_token("-") {
|
||||
if self.match_token(">") {
|
||||
self.add_token(ArrowRight);
|
||||
} else if self.match_token("<") {
|
||||
if self.match_token(">") {
|
||||
self.add_token(DiamondArrowRight);
|
||||
} else {
|
||||
println!("Wrong arrow at {}", self.current_line);
|
||||
}
|
||||
} else {
|
||||
println!("Wrong arrow at {}", self.current_line);
|
||||
}
|
||||
} else {
|
||||
self.add_token(Minus);
|
||||
}
|
||||
}
|
||||
"<" => {
|
||||
if self.match_token("-") {
|
||||
if self.match_token("-") {
|
||||
self.add_token(ArrowLeft);
|
||||
} else {
|
||||
println!("Wrong arrow at {}", self.current_line);
|
||||
}
|
||||
} else if self.match_token(">") {
|
||||
if self.match_token("-") {
|
||||
if self.match_token("-") {
|
||||
self.add_token(DiamondArrowLeft);
|
||||
} else {
|
||||
println!("Wrong arrow at {}", self.current_line);
|
||||
}
|
||||
} else {
|
||||
println!("Wrong arrow at {}", self.current_line);
|
||||
}
|
||||
}
|
||||
}
|
||||
":" => self.add_token(Colon),
|
||||
"/" => {
|
||||
if self.match_token("/") {
|
||||
while self.peek() != "\n" && !self.is_at_end() {
|
||||
self.advance();
|
||||
}
|
||||
} else {
|
||||
self.add_token(Slash);
|
||||
}
|
||||
}
|
||||
" " | "\t" | "\r" => {}
|
||||
"\n" => {
|
||||
self.current_line += 1;
|
||||
}
|
||||
"\"" => self.string(),
|
||||
_ => {
|
||||
if is_digit(c) || c == "." {
|
||||
self.number()
|
||||
} else if is_alpha(c) {
|
||||
self.identifier()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&self) -> &str {
|
||||
if self.current_pos < self.chars.len() {
|
||||
self.chars[self.current_pos]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_next(&self) -> &str {
|
||||
self.chars[self.current_pos + 1]
|
||||
}
|
||||
|
||||
fn identifier(&mut self) {
|
||||
while is_alpha(self.peek()) || is_digit(self.peek()) {
|
||||
self.advance();
|
||||
}
|
||||
let text = self.chars[self.start_pos..self.current_pos].concat();
|
||||
let tokentype = KEYWORDS
|
||||
.get(text.as_str())
|
||||
.map(|d| *d)
|
||||
.unwrap_or(Identifier);
|
||||
|
||||
self.add_token(tokentype);
|
||||
}
|
||||
|
||||
fn number(&mut self) {
|
||||
while is_digit(self.peek()) || (self.peek() == "." && is_digit(self.peek_next())) {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
self.add_token(TokenType::Number);
|
||||
}
|
||||
|
||||
fn string(&mut self) {
|
||||
while self.peek() != "\"" && !self.is_at_end() {
|
||||
if self.peek() == "\n" {
|
||||
self.current_line = 1;
|
||||
}
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.is_at_end() {
|
||||
println!("Unterminated string");
|
||||
return;
|
||||
}
|
||||
|
||||
self.advance();
|
||||
|
||||
self.add_token(TokenType::Str);
|
||||
}
|
||||
|
||||
fn add_token(&mut self, tokentype: TokenType) {
|
||||
// println!("add token {:?}", tokentype);
|
||||
let token = Token::new(
|
||||
tokentype,
|
||||
self.chars[self.start_pos..self.current_pos].concat(),
|
||||
self.current_line,
|
||||
);
|
||||
self.tokens.push(token);
|
||||
}
|
||||
|
||||
fn match_token(&mut self, expected: &str) -> bool {
|
||||
if self.is_at_end() || self.peek() != expected {
|
||||
return false;
|
||||
}
|
||||
self.current_pos += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &str {
|
||||
let c = self.chars[self.current_pos];
|
||||
if self.current_pos < self.chars.len() {
|
||||
self.current_pos += 1;
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
fn is_at_end(&self) -> bool {
|
||||
self.current_pos >= self.chars.len()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_digit(char: &str) -> bool {
|
||||
char.len() > 0 && char.chars().next().unwrap().is_ascii_digit()
|
||||
}
|
||||
|
||||
fn is_alpha(char: &str) -> bool {
|
||||
if char.len() == 0 {
|
||||
false
|
||||
} else {
|
||||
let char = char.chars().next().unwrap();
|
||||
if char.is_alphabetic() || char == '_' {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/tokens.rs
Normal file
54
src/tokens.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use std::cell::LazyCell;
|
||||
use std::collections::HashMap;
|
||||
use TokenType::*;
|
||||
|
||||
pub const KEYWORDS: LazyCell<HashMap<&str, TokenType>> = LazyCell::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("markup", Markup);
|
||||
m.insert("styles", Styles);
|
||||
m.insert("group", Group);
|
||||
m.insert("px", Px);
|
||||
m
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub tokentype: TokenType,
|
||||
pub lexeme: String,
|
||||
pub line: usize,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub(crate) fn new(tokentype: TokenType, lexeme: String, line: usize) -> Self {
|
||||
Token {
|
||||
tokentype,
|
||||
lexeme,
|
||||
line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum TokenType {
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
DiamondArrowRight,
|
||||
DiamondArrowLeft,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
Comma,
|
||||
Dot,
|
||||
Colon,
|
||||
Slash,
|
||||
Identifier,
|
||||
Str,
|
||||
Number,
|
||||
Minus,
|
||||
Markup,
|
||||
Styles,
|
||||
Group,
|
||||
Eof,
|
||||
Px,
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue