refactor & fixed assembler path handling

This commit is contained in:
2025-06-21 04:05:22 +01:00
parent 42c26d4184
commit 528ceddade
17 changed files with 447 additions and 184 deletions
Generated
+21
View File
@@ -236,6 +236,8 @@ name = "assembler"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"common", "common",
"num_cpus",
"threadpool",
] ]
[[package]] [[package]]
@@ -1951,6 +1953,16 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]] [[package]]
name = "num_enum" name = "num_enum"
version = "0.7.3" version = "0.7.3"
@@ -3041,6 +3053,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]] [[package]]
name = "tiff" name = "tiff"
version = "0.9.1" version = "0.9.1"
+2
View File
@@ -14,3 +14,5 @@ path = "src/lib.rs"
[dependencies] [dependencies]
common = { path = "../common" } common = { path = "../common" }
num_cpus = "1.17.0"
threadpool = "1.8.1"
+131
View File
@@ -0,0 +1,131 @@
//! Macros used throughout the assembler
use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting
///
/// # Examples
/// ```
/// // With formatting:
/// let nodes = dsa!(hash, "mov r1, {}", 42)?;
///
/// // Without formatting:
/// let nodes = dsa!(hash, "mov r1, 42")?;
/// ```
#[macro_export]
macro_rules! dsa {
// Version with formatting arguments
($hash:expr, $input:expr, $($args:expr),+) => {{
let input = format!($input, $($args),+);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
// Version without formatting
($hash:expr, $input:expr) => {{
let input = String::from($input);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
}
/// Creates a new Node with the given symbol, opcode, and tokens
#[macro_export]
macro_rules! node {
($symbol: expr, $opcode: expr, args: $tokens: expr) => {
$crate::assembler::model::Node::new($symbol.clone(), $opcode.clone(), $tokens.clone())
};
($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
$crate::assembler::model::Node::new(
$symbol.clone(),
$opcode.clone(),
vec![$(node!(@convert_token $tokens)),+]
)
};
(@convert_token $token: literal) => {
$crate::assembler::model::Token::Immediate($token)
};
(@convert_token $token: expr) => {
$token.clone()
};
}
/// Extracts a specific token type from a token
#[macro_export]
macro_rules! expect_token {
($token:expr, Symbol) => {
match $token {
$crate::assembler::model::Token::Symbol(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Symbol,
)),
}
};
($token:expr, Register) => {
match $token {
$crate::assembler::model::Token::Register(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Register,
)),
}
};
($token:expr, Immediate) => {
match $token {
$crate::assembler::model::Token::Immediate(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Immediate,
)),
}
};
($token:expr, StringLit) => {
match $token {
$crate::assembler::model::Token::StringLit(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::StringLit,
)),
}
};
($token:expr, Opcode) => {
match $token {
$crate::assembler::model::Token::Opcode(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Opcode,
)),
}
};
}
/// Checks if a token matches any of the specified types
#[macro_export]
macro_rules! expect_type {
($token:expr, $($variant:ident),+) => {{
let token = $token;
match &token {
$(
$crate::assembler::model::Token::$variant(_) => Ok(token.clone()),
)+
other => {
let expected_type = expect_type!(@get_first_type $($variant),+);
Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone().clone(),
expected_type,
))
}
}
}};
(@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol };
(@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register };
(@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate };
(@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit };
(@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode };
}
+98 -118
View File
@@ -1,21 +1,23 @@
#![allow(dead_code, unused)]
use std::{ use std::{
collections::HashSet, collections::HashSet,
fmt, fs, fmt, fs,
hash::{DefaultHasher, Hash, Hasher}, hash::{DefaultHasher, Hash, Hasher},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::mpsc,
}; };
use common::prelude::Instruction; use common::prelude::Instruction;
use crate::{ // TODO: Use an actual logging or tracing library for pretty (scoped) output.
assembler::{ fn log(message: &str) {
expand::expand_pseudo_ops, println!("\x1b[32mINFO:\x1b[0m {message}");
model::{Node, Opcode, Symbol, Token, TokenType}, }
parser::{Parser, Program},
resolver::{create_sections, resolve_dependencies, resolve_symbols}, // Module declarations
}, #[macro_use]
codegen, log, node, pub mod macros;
};
pub mod codegen; pub mod codegen;
pub mod expand; pub mod expand;
@@ -24,25 +26,91 @@ pub mod model;
pub mod parser; pub mod parser;
pub mod resolver; pub mod resolver;
pub fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> { // Re-exports
let mut modules = HashSet::<u64>::new(); pub use self::{
let mut program = Program::new(); codegen::codegen,
expand::expand_pseudo_ops,
lexer::lexer,
model::{Module, Node, Opcode, Symbol, Token, TokenType},
parser::{Parser, Program},
resolver::{create_sections, resolve_dependencies, resolve_symbols},
};
let hash = quick_hash(src); use crate::util::logging::{Entry, Logger};
modules.insert(hash);
prepare_dependency(src, &mut modules, &mut program)?; pub struct CompilerEngine {
let mut nodes = program.nodes; modules: HashSet<u64>,
program: Program,
logger: Option<Logger>,
receiver: Option<mpsc::Receiver<Entry>>,
result: Option<Result<Vec<Instruction>, AssembleError>>,
}
create_sections(&mut nodes)?; impl CompilerEngine {
resolve_symbols(&mut nodes)?; pub fn new() -> CompilerEngine {
let (tx, rx) = mpsc::channel::<Entry>();
let instructions = codegen(nodes)?; CompilerEngine {
for inst in instructions.iter() { program: Program::new(),
println!("{inst}"); modules: HashSet::new(),
logger: Some(Logger::new(tx)),
receiver: Some(rx),
result: None,
}
} }
Ok(instructions) pub fn get_log() -> Option<Entry> {
None
}
pub fn get_logs() -> Vec<Entry> {
vec![]
}
pub fn is_ready(&self) -> bool {
self.result.is_some()
}
pub fn result(&self) -> Option<Result<Vec<Instruction>, AssembleError>> {
self.result.clone()
}
pub fn assemble(&mut self, src: &Path) -> Result<(), AssembleError> {
let hash = quick_hash(src);
if self.modules.contains(&hash) {
return Ok(());
}
prepare_dependency(src, &mut self.modules, &mut self.program)?;
self.result = Some(self.build());
Ok(())
}
fn load_module(&mut self, path: &Path) -> Result<(), AssembleError> {
Ok(())
}
fn build(&self) -> Result<Vec<Instruction>, AssembleError> {
let mut nodes = self.program.nodes.clone();
create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?;
let instructions = codegen(nodes)?;
for inst in instructions.iter() {
println!("{inst}");
}
Ok(instructions)
}
}
impl Default for CompilerEngine {
fn default() -> Self {
Self::new()
}
} }
fn prepare_dependency( fn prepare_dependency(
@@ -71,9 +139,13 @@ fn prepare_dependency(
let parsed = Parser::parse_nodes(tokens)?; let parsed = Parser::parse_nodes(tokens)?;
log(&format!("{:20} {:20}", "Resolving Deps", filename)); log(&format!("{:20} {:20}", "Resolving Deps", filename));
let nodes = resolve_dependencies(parsed)?; // Get the parent directory of the source file to use as the base directory
let base_dir = path
.parent()
.ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?;
let nodes = resolve_dependencies(parsed, base_dir)?;
let deps = Parser::get_dependencies(&nodes)?; let deps = Parser::get_dependencies(&nodes, path)?;
log(&format!( log(&format!(
"{:20} {:20}", "{:20} {:20}",
@@ -122,7 +194,7 @@ pub fn disassemble(_: Vec<Instruction>) -> String {
todo!() todo!()
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum AssembleError { pub enum AssembleError {
Generic, Generic,
UnexpectedEof, UnexpectedEof,
@@ -154,95 +226,3 @@ fn quick_hash(value: &Path) -> u64 {
value.canonicalize().unwrap().to_str().hash(&mut hasher); value.canonicalize().unwrap().to_str().hash(&mut hasher);
hasher.finish() hasher.finish()
} }
#[macro_export]
macro_rules! dsa {
// Version with formatting arguments
($hash:expr, $input:expr, $($args:expr),+) => {{
let input = format!($input, $($args),+);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
// Version without formatting
($hash:expr, $input:expr) => {{
let input = String::from($input);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
}
#[macro_export]
macro_rules! expect_token {
($token:expr, Symbol) => {
match $token {
$crate::assembler::model::Token::Symbol(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Symbol,
)),
}
};
($token:expr, Register) => {
match $token {
$crate::assembler::model::Token::Register(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Register,
)),
}
};
($token:expr, Immediate) => {
match $token {
$crate::assembler::model::Token::Immediate(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Immediate,
)),
}
};
($token:expr, StringLit) => {
match $token {
$crate::assembler::model::Token::StringLit(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::StringLit,
)),
}
};
($token:expr, Opcode) => {
match $token {
$crate::assembler::model::Token::Opcode(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Opcode,
)),
}
};
}
#[macro_export]
macro_rules! expect_type {
($token:expr, $($variant:ident),+) => {{
let token = $token;
match &token {
$(
$crate::assembler::model::Token::$variant(_) => Ok(token.clone()),
)+
other => {
let expected_type = expect_type!(@get_first_type $($variant),+);
Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone().clone(),
expected_type,
))
}
}
}};
(@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol };
(@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register };
(@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate };
(@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit };
(@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode };
}
-23
View File
@@ -11,29 +11,6 @@ pub struct Node {
pub tokens: Vec<Token>, pub tokens: Vec<Token>,
} }
#[macro_export]
macro_rules! node {
($symbol: expr, $opcode: expr, args: $tokens: expr) => {
Node::new($symbol.clone(), $opcode.clone(), $tokens.clone())
};
($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
Node::new(
$symbol.clone(),
$opcode.clone(),
vec![$(node!(@convert_token $tokens)),+]
)
};
(@convert_token $token: literal) => {
Token::Immediate($token)
};
(@convert_token $token: expr) => {
$token.clone()
};
}
impl Node { impl Node {
pub fn new(symbol: Option<Symbol>, opcode: Opcode, tokens: Vec<Token>) -> Node { pub fn new(symbol: Option<Symbol>, opcode: Opcode, tokens: Vec<Token>) -> Node {
Node { Node {
+22 -6
View File
@@ -1,4 +1,4 @@
use std::path::PathBuf; use std::path::{Path, PathBuf};
use crate::{assembler::AssembleError, expect_token, expect_type, node}; use crate::{assembler::AssembleError, expect_token, expect_type, node};
@@ -10,6 +10,7 @@ pub struct Parser {
nodes: Vec<Node>, nodes: Vec<Node>,
} }
#[derive(Debug)]
pub struct Program { pub struct Program {
pub nodes: Vec<Node>, pub nodes: Vec<Node>,
} }
@@ -44,8 +45,6 @@ impl Parser {
nodes: vec![], nodes: vec![],
}; };
println!("{:#?}", tokens);
while !self_.tokens.is_empty() { while !self_.tokens.is_empty() {
let ins = self_.parse_instruction()?; let ins = self_.parse_instruction()?;
self_.nodes.push(ins); self_.nodes.push(ins);
@@ -54,12 +53,29 @@ impl Parser {
Ok(self_.nodes.clone()) Ok(self_.nodes.clone())
} }
pub fn get_dependencies(nodes: &Vec<Node>) -> Result<Vec<PathBuf>, AssembleError> { pub fn get_dependencies(
nodes: &Vec<Node>,
source_path: &Path,
) -> Result<Vec<PathBuf>, AssembleError> {
let mut dependencies = Vec::new(); let mut dependencies = Vec::new();
// Get the parent directory of the source file to use as the base directory
let base_dir = source_path
.parent()
.ok_or_else(|| AssembleError::InvalidFile(source_path.to_path_buf()))?;
for node in nodes { for node in nodes {
if let Opcode::Include = node.opcode() { if let Opcode::Include = node.opcode() {
let path = expect_token!(node.args().get(1).unwrap(), StringLit)?; let path_str = expect_token!(node.args().get(1).unwrap(), StringLit)?;
dependencies.push(PathBuf::from(path)); let path = PathBuf::from(path_str);
// If the path is not absolute, make it relative to the base directory
let full_path = if path.is_absolute() {
path
} else {
base_dir.join(path)
};
dependencies.push(full_path);
} }
} }
Ok(dependencies) Ok(dependencies)
+20 -8
View File
@@ -1,9 +1,16 @@
use std::{collections::HashMap, path::PathBuf}; use std::{
collections::HashMap,
fs::canonicalize,
path::{Path, PathBuf},
};
use common::prelude::Register; use common::prelude::Register;
use crate::assembler::model::{Module, Node, Opcode, Symbol, Token};
use crate::assembler::quick_hash; use crate::assembler::quick_hash;
use crate::assembler::{
log,
model::{Module, Node, Opcode, Symbol, Token},
};
use crate::{assembler::AssembleError, node}; use crate::{assembler::AssembleError, node};
pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> { pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> {
@@ -63,7 +70,10 @@ fn generate_symbol_table(nodes: &[Node]) -> Result<HashMap<Symbol, u32>, Assembl
Ok(table) Ok(table)
} }
pub fn resolve_dependencies(mut nodes: Vec<Node>) -> Result<Vec<Node>, AssembleError> { pub fn resolve_dependencies(
mut nodes: Vec<Node>,
base_dir: &Path,
) -> Result<Vec<Node>, AssembleError> {
// First we get a list of imports. // First we get a list of imports.
let mut dependencies = Vec::new(); let mut dependencies = Vec::new();
for node in &nodes { for node in &nodes {
@@ -79,11 +89,13 @@ pub fn resolve_dependencies(mut nodes: Vec<Node>) -> Result<Vec<Node>, AssembleE
} else { } else {
unreachable!() unreachable!()
}; };
let hash = quick_hash(
&PathBuf::from(path) let full_path = base_dir.join(path);
.canonicalize() let canonical_path = full_path
.expect("ERROR: Invalid import path."), .canonicalize()
); .map_err(|_| AssembleError::InvalidFile(full_path.to_path_buf()))?;
let hash = quick_hash(&canonical_path);
dependencies.push((name, hash)); dependencies.push((name, hash));
} }
+1 -8
View File
@@ -1,15 +1,8 @@
use assembler::codegen::codegen;
pub mod assembler; pub mod assembler;
pub mod tooling; pub mod tooling;
mod util; mod util;
pub mod prelude { pub mod prelude {
pub use crate::assembler::assemble; pub use crate::assembler::CompilerEngine;
pub use crate::assembler::disassemble; pub use crate::assembler::disassemble;
} }
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
+38 -11
View File
@@ -1,7 +1,8 @@
use assembler::prelude::*;
use std::{fs, io::Write, path::PathBuf}; use std::{fs, io::Write, path::PathBuf};
fn main() { fn main() {
// parse args: // Parse command line arguments
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
if args.len() == 2 && args[1] == "init" { if args.len() == 2 && args[1] == "init" {
@@ -10,23 +11,49 @@ fn main() {
} }
if args.len() != 5 || args[1] != "-i" || args[3] != "-o" { if args.len() != 5 || args[1] != "-i" || args[3] != "-o" {
eprintln!("Usage: binary_name -i input_path -o output_path"); eprintln!("Usage: {} -i input_path -o output_path", args[0]);
std::process::exit(1); std::process::exit(1);
} }
let input_path = &args[2]; let input_path = &args[2];
let output_path = &args[4]; let output_path = &args[4];
let src = PathBuf::from(input_path); let src = PathBuf::from(input_path);
let mut output_file = fs::File::create(output_path).unwrap();
match assembler::assembler::assemble(&src) { // Create the output file
Ok(res) => { let mut output_file = match fs::File::create(output_path) {
res.iter().map(|i| i.encode()).for_each(|i| { Ok(file) => file,
output_file.write_all(&i.to_le_bytes()).unwrap();
});
}
Err(e) => { Err(e) => {
eprintln!("{e}"); eprintln!("Failed to create output file: {}", e);
std::process::exit(1);
}
};
// Initialize the compiler engine
let mut engine = CompilerEngine::new();
// Assemble the source file
if let Err(e) = engine.assemble(&src) {
eprintln!("Assembly error: {}", e);
std::process::exit(1);
}
// Build and write the output
match engine.result() {
Some(Ok(instructions)) => {
for instruction in instructions {
if let Err(e) = output_file.write_all(&instruction.encode().to_le_bytes())
{
eprintln!("Failed to write to output file: {}", e);
std::process::exit(1);
}
}
}
Some(Err(e)) => {
eprintln!("Build error: {}", e);
std::process::exit(1);
}
None => {
eprintln!("Build error: No result available");
std::process::exit(1); std::process::exit(1);
} }
} }
+94
View File
@@ -0,0 +1,94 @@
use std::{fmt, sync::mpsc::Sender};
#[allow(dead_code)]
#[derive(Debug)]
pub struct Logger {
pub sender: Sender<Entry>,
}
impl Logger {
pub fn new(sender: Sender<Entry>) -> Self {
Self { sender }
}
pub fn debug<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Debug,
message: message.to_string(),
})
.unwrap();
}
pub fn info<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Info,
message: message.to_string(),
})
.unwrap();
}
pub fn warn<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Warn,
message: message.to_string(),
})
.unwrap();
}
pub fn error<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Error,
message: message.to_string(),
})
.unwrap();
}
pub fn fatal<T: fmt::Display>(&self, message: T) {
self.sender
.send(Entry {
etype: EntryType::Fatal,
message: message.to_string(),
})
.unwrap();
}
}
pub struct Entry {
etype: EntryType,
pub message: String,
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum EntryType {
Debug,
Info,
Warn,
Error,
Fatal,
}
impl fmt::Display for EntryType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:<5}",
match self {
EntryType::Debug => "DEBUG",
EntryType::Info => "INFO",
EntryType::Warn => "WARN",
EntryType::Error => "ERROR",
EntryType::Fatal => "FATAL",
}
)
}
}
impl fmt::Display for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.etype, self.message)
}
}
+2
View File
@@ -1,3 +1,5 @@
pub mod logging;
use std::io::Write; use std::io::Write;
pub fn input(prompt: &str) -> String { pub fn input(prompt: &str) -> String {
+15 -3
View File
@@ -346,12 +346,24 @@ impl Editor {
// builds the current file // builds the current file
if ui.button("Build").clicked() && !self.unsaved { if ui.button("Build").clicked() && !self.unsaved {
if let Some(path) = &self.path { if let Some(path) = &self.path {
let instructions = match assemble(path) { let mut assembler = CompilerEngine::new();
Ok(instructions) => instructions, if let Err(error) = assembler.assemble(path) {
Err(error) => { self.error = Some(format!("Failed to assemble: {error:?}"));
return;
}
let instructions = match assembler.result() {
Some(Ok(instructions)) => instructions,
Some(Err(error)) => {
self.error = Some(format!("Failed to assemble: {error:?}")); self.error = Some(format!("Failed to assemble: {error:?}"));
return; return;
} }
None => {
self.error = Some(
"Failed to assemble: No result available".to_string(),
);
return;
}
}; };
self.output = instructions self.output = instructions
+2 -6
View File
@@ -1,11 +1,7 @@
include print "./print.dsa" include print "./lib/print.dsa"
dw stack: 0x10000 dw stack: 0x10000
db string: "An idiot admires complexity, a genius admires simplicity, db string: "Hello world frfrfr"
a physicist tries to make it simple, for an idiot anything the more complicated it is,
the more he will admire it, if you make something so clusterfucked he can't understand it he's
gonna think you're a god cause you made it so complicated nobody can understand it.
That's how they write journals in Academics, they try to make it so complicated people think you're a genius"
init: init:
// set up a stack. // set up a stack.
Binary file not shown.