Tholp's bespoke website generator

Initial Commit, !include and basic tooling

Tholp1 fdd1aaef

+4
.gitignore
···
+
/target
+
*.out
+
.vscode
+
Cargo.lock
+7
Cargo.toml
···
+
[package]
+
name = "skidmark"
+
version = "0.1.0"
+
edition = "2021"
+
+
[dependencies]
+
markdown = "0.3.0"
+3
a.sk
···
+
Skidmark progress: functions are prefixed with '!'
+
theres currently only one so far and its !include("b.sk")
+
+4
b.sk
···
+
used to include files inside other files, isnt that cool?
+
+
Functions prefixed with '&' are "Ephemeral" and will only be expanded if it comes from within the same file thats being proccessed
+
&include("c.sk")
+1
c.sk
···
+
See?
+1
src/blocktypes/mod.rs
···
+
//pub mod include;
+10
src/error.rs
···
+
pub fn exit_error(msg : String)
+
{
+
println!("[Error]" + msg);
+
exit(1);
+
}
+
+
pub fn warn(msg: String)
+
{
+
+
}
+17
src/macros/include.rs
···
+
use std::{env::Args, fs};
+
+
use crate::{
+
stringtools::{split_keep_delimiters, strings_to_tokens},
+
types::{InputFile, Token},
+
};
+
+
pub fn macro_include(_file: &InputFile, args: &Vec<String>) -> Vec<Token> {
+
print!("\nargs: {:?}\n", args);
+
let mut output = fs::read_to_string(args[0].clone()).expect("File unreadable or missing");
+
if output.ends_with("\n") {
+
output.pop();
+
} //remove trailing newline
+
+
let split_output = split_keep_delimiters(output);
+
return strings_to_tokens(split_output, args[0].clone());
+
}
+10
src/macros/mod.rs
···
+
pub mod include;
+
use super::types::Macro;
+
+
use include::macro_include;
+
+
pub static MACRO_LIST: [Macro<'_>; 1] = [Macro {
+
symbol: "include",
+
expand: macro_include,
+
//always_ephemeral: false,
+
}];
+95
src/main.rs
···
+
mod blocktypes;
+
mod macros;
+
mod stringtools;
+
mod types;
+
+
use macros::{
+
include::{self, macro_include},
+
MACRO_LIST,
+
};
+
use std::{
+
env,
+
fs::{self, File},
+
io::Write,
+
process::{exit, Output},
+
};
+
use stringtools::{collect_arguments, split_keep_delimiters, strings_to_tokens};
+
use types::{InputFile, Macro, Token};
+
+
static DELIMITERS: [char; 7] = [' ', '\n', '\t', '(', ')', '{', '}'];
+
+
fn main() {
+
let mut files: Vec<types::InputFile> = Vec::new();
+
let mut args: Vec<String> = env::args().collect();
+
args.remove(0);
+
+
for file in args.iter() {
+
let mut new_file = types::InputFile::new();
+
new_file.filename_in = file.to_string();
+
new_file.filename_out = file.to_string() + ".out";
+
files.push(new_file);
+
}
+
println!("{:?}", args);
+
for f in &mut files {
+
process_file(f);
+
}
+
}
+
+
fn process_file(file: &mut InputFile) {
+
let contents = fs::read_to_string(&file.filename_in).expect("File unreadable or missing");
+
//println!("{}\n {}", f.filename_out, contents);
+
+
file.tokens = strings_to_tokens(split_keep_delimiters(contents), file.filename_in.clone());
+
+
let mut index = 0;
+
+
while index < file.tokens.len() {
+
//look for macros or blocks
+
//println!(">\"{}\"<", file.tokens[index].contents);
+
+
if file.tokens[index].contents.starts_with(['!', '&']) {
+
let mut matched = false;
+
+
for m in &MACRO_LIST {
+
if &file.tokens[index].contents.trim()[1..] == m.symbol {
+
matched = true;
+
println!("Found a macro ({})", m.symbol);
+
let mut ephemeral = false;
+
if file.tokens[index].contents.starts_with('&')
+
&& file.tokens[index].origin_file != file.filename_in
+
{
+
println!("Skipping Ephermal macro from included file.");
+
ephemeral = true;
+
}
+
+
let (args, tokcount) = collect_arguments(&file.tokens[index..]);
+
let expansion: Vec<Token>;
+
if ephemeral {
+
expansion = Vec::new();
+
} else {
+
expansion = (m.expand)(&file, &args);
+
}
+
file.tokens.remove(index);
+
file.tokens.splice(index..(index + tokcount - 1), expansion);
+
}
+
}
+
+
// for b in &BLOCK_LIST {}
+
+
if !matched {
+
println!(
+
"Token written as a function but no such function exists \"{}\"",
+
file.tokens[index].contents.trim()
+
);
+
}
+
}
+
+
index += 1;
+
}
+
//println!("{:?}", file.tokens);
+
let mut full_output: String = "".to_string();
+
for t in &file.tokens {
+
full_output += &t.contents;
+
}
+
fs::write(&file.filename_out, full_output).expect("Couldn't write to file");
+
}
+124
src/stringtools.rs
···
+
use core::fmt;
+
use std::{fmt::Arguments, ops::Index, process::exit};
+
+
use super::DELIMITERS;
+
use crate::types::Token;
+
+
pub fn collect_arguments(tokens: &[Token]) -> (Vec<String>, usize) {
+
//let mut output = Vec::new();
+
let mut split_tokens = Vec::new();
+
for tok in tokens {
+
for s in split_keep_delimiters(tok.contents.clone()) {
+
split_tokens.push(s);
+
}
+
}
+
+
let mut quoted: bool = false;
+
let mut entered: bool = false;
+
let mut arg = "".to_string();
+
let mut args: Vec<String> = Vec::new();
+
+
let mut in_token_count = 0;
+
+
for tok in split_tokens {
+
in_token_count += 1;
+
if tok.starts_with([' ', '\t']) && !quoted {
+
continue;
+
}
+
+
if !entered && tok.starts_with('(') {
+
entered = true;
+
continue;
+
}
+
+
if !entered {
+
continue;
+
}
+
+
if !quoted && tok.starts_with(')') {
+
break;
+
}
+
+
let mut i = 0;
+
while i < tok.len() {
+
let c = tok.chars().nth(i).unwrap();
+
i += 1;
+
+
if c == '\"' {
+
quoted = !quoted;
+
continue;
+
}
+
+
arg.push(c);
+
}
+
+
if !quoted {
+
args.push(arg.clone());
+
arg.clear();
+
}
+
}
+
+
return (args, in_token_count);
+
}
+
+
// Theres no std function to have the delimiters be their own element in the out vector
+
// clean it up a bit here
+
pub fn split_keep_delimiters(instr: String) -> Vec<String> {
+
let split: Vec<&str> = instr.split_inclusive(DELIMITERS).collect();
+
let mut output = Vec::new();
+
+
for s in split {
+
if s.ends_with(DELIMITERS) {
+
let (token, ending) = s.split_at(s.len() - 1);
+
if token.len() > 0 {
+
output.push(token.to_string());
+
}
+
output.push(ending.to_string());
+
} else {
+
output.push(s.to_string());
+
}
+
}
+
return output;
+
}
+
+
pub fn strings_to_tokens(instrings: Vec<String>, origin_file: String) -> Vec<Token> {
+
let mut tokens = Vec::new();
+
let mut linecount: u32 = 1;
+
+
for str in instrings {
+
let currentline = linecount;
+
for char in str.chars() {
+
if char == '\n' {
+
linecount += 1;
+
}
+
}
+
let token: Token = Token::new(str, origin_file.clone(), currentline);
+
tokens.push(token);
+
}
+
+
return tokens;
+
}
+
pub fn next_nonwhitespace_token(tokens: &Vec<Token>, index: usize) -> (bool, usize) {
+
while index < tokens.len() {
+
if tokens[index].contents.starts_with([' ', '\t', '\n']) {
+
continue;
+
}
+
return (true, index);
+
}
+
return (false, 0);
+
}
+
+
pub trait IsDelimiter {
+
fn is_delimiter(&self) -> bool;
+
}
+
+
impl IsDelimiter for char {
+
fn is_delimiter(&self) -> bool {
+
for d in DELIMITERS {
+
if *self == d {
+
return true;
+
}
+
}
+
return false;
+
}
+
}
+61
src/types.rs
···
+
use std::sync::Mutex;
+
+
pub struct Token {
+
pub contents: String,
+
pub origin_file: String,
+
pub line_number: u32,
+
}
+
+
pub enum BlockEdgeType {
+
FileStart,
+
FileEnd,
+
Start,
+
End,
+
}
+
+
// A 'Block' is what im calling the enclosed scope of a macro
+
pub struct BlockEdge {
+
pub edge_type: BlockEdgeType,
+
pub tokens_to_next_edge: u64,
+
}
+
+
pub struct InputFile {
+
pub filename_in: String,
+
pub filename_out: String,
+
pub tokens: Vec<Token>,
+
pub block_edges: Vec<BlockEdge>,
+
}
+
+
type MacroExpansion = fn(&InputFile, &Vec<String>) -> Vec<Token>;
+
pub struct Macro<'a> {
+
pub symbol: &'a str,
+
pub expand: MacroExpansion,
+
//pub always_ephemeral: bool, // This wont be included from other files
+
}
+
+
impl InputFile {
+
pub fn new() -> InputFile {
+
InputFile {
+
filename_in: "".to_string(),
+
filename_out: "".to_string(),
+
tokens: Vec::new(),
+
block_edges: Vec::new(),
+
}
+
}
+
}
+
+
impl Token {
+
pub fn new(contents: String, origin_file: String, line_number: u32) -> Token {
+
Token {
+
contents: contents,
+
origin_file: origin_file,
+
line_number: line_number,
+
}
+
}
+
}
+
+
impl ToString for Token {
+
fn to_string(&self) -> String {
+
return self.contents.clone();
+
}
+
}