diff options
| -rw-r--r-- | examples/modules/bot.sloth | 7 | ||||
| -rw-r--r-- | examples/modules/mod.sloth | 15 | ||||
| -rw-r--r-- | examples/webserver.sloth | 108 | ||||
| -rw-r--r-- | src/ast.rs | 0 | ||||
| -rw-r--r-- | src/ast/display.rs | 45 | ||||
| -rw-r--r-- | src/ast/evaluate.rs | 5 | ||||
| -rw-r--r-- | src/ast/mod.rs | 55 | ||||
| -rw-r--r-- | src/lexer.rs | 10 | ||||
| -rw-r--r-- | src/main.rs | 18 | ||||
| -rw-r--r-- | tour/literals.sloth | 48 | ||||
| -rw-r--r-- | tour/modules.sloth | 49 | ||||
| -rw-r--r-- | tour/types.sloth | 16 |
12 files changed, 356 insertions, 20 deletions
diff --git a/examples/modules/bot.sloth b/examples/modules/bot.sloth new file mode 100644 index 0000000..1355ddb --- /dev/null +++ b/examples/modules/bot.sloth @@ -0,0 +1,7 @@ +use slowcord::DiscordBot; + +pub type Bot; + +impl DiscordBot for Bot { + # TODO: +} diff --git a/examples/modules/mod.sloth b/examples/modules/mod.sloth new file mode 100644 index 0000000..75be7e1 --- /dev/null +++ b/examples/modules/mod.sloth @@ -0,0 +1,15 @@ +# Root module - Run this to start program + +pub use extern "logging"; +pub use extern "dotenv"; +pub use extern "slowcord"; +pub use extern "sqlite"; + +use logging::LogLevel; +use bot::Bot; + +logging::set_loglevel(LogLevel::WARN); + +val token = dotenv::get("TOKEN"); +val bot = Bot::new(); +bot.start(token); diff --git a/examples/webserver.sloth b/examples/webserver.sloth new file mode 100644 index 0000000..abbead3 --- /dev/null +++ b/examples/webserver.sloth @@ -0,0 +1,108 @@ +# Include the external dependency itself as a module named "slow_api" +use extern "slowapi" as slow_api; + +# Use some things from the "slow_api" module +use std::serde::Serializable; +use std::serde::format::Json; + +use slow_api::{SlowAPI, Method}; + +# Construct a slow API server +val server = SlowApi(); + +type Person derives Serializable = { + name: String, + age: Option<String> +}; + +fn hello_route( + name: Argument<String>, + age: Argument<Option<String>>, +) -> Json<Person> { + Person { name, age } +} + +# Start the server +server + .route(Method::GET, "/hello", hello_route) + .start("0.0.0.0:8000"); +# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### +type Poggies; + +trait Constructor<..T> { + fn new(..T) -> Self; +} + +impl Constructor<> for Poggies { + fn new() -> Self { + # + } +} + +impl<T: Constructor<>> Default for T { + fn default() -> Self { + Self::new() + } +} + +### +type Person = { + name: String, + age: i32, +}; + +type Person derives Serialize, Deserialize = { + name: String, + age: i32, +}; + +@route::get("/teacup") # vvvvvv - Requires T to implement Serialize +fn teacup_route() -> Response<Person> { + Response(418, Person { + name: "Cody Q", + age: 17, + }) +} diff --git a/src/ast.rs b/src/ast.rs deleted file mode 100644 index e69de29..0000000 --- a/src/ast.rs +++ /dev/null diff --git a/src/ast/display.rs b/src/ast/display.rs new file mode 100644 index 0000000..25b4d23 --- /dev/null +++ b/src/ast/display.rs @@ -0,0 +1,45 @@ +use std::fmt::Display; + +use super::{Expression, Statement, Value}; + +impl<'a> Display for Statement<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{{")?; + let value = match self { + Statement::Val { + identifier, + initializer, + } => format!("val {} {}", identifier.lexeme, initializer), + Statement::Var { + identifier, + initializer, + } => format!("var {} {}", identifier.lexeme, initializer), + Statement::Expression { expr } => expr.to_string(), + }; + write!(f, "{value}")?; + write!(f, "}}")?; + + Ok(()) + } +} + +impl<'a> Display for Expression<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(")?; + let value = match self { + Expression::Literal(value) => value.0.to_string(), + Expression::Unary { expr, .. } => format!("+ {}", expr), + Expression::Binary { lhs, rhs, .. } => format!("+ {lhs} {rhs}"), + }; + write!(f, "{value}")?; + write!(f, ")")?; + + Ok(()) + } +} + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/src/ast/evaluate.rs b/src/ast/evaluate.rs new file mode 100644 index 0000000..4d47994 --- /dev/null +++ b/src/ast/evaluate.rs @@ -0,0 +1,5 @@ +impl<'a> Expression<'a> { + fn evaluate() -> Value { + Value(5) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs new file mode 100644 index 0000000..73e73b3 --- /dev/null +++ b/src/ast/mod.rs @@ -0,0 +1,55 @@ +#![allow(dead_code)] + +pub mod display; + +use crate::lexer::Token; + +#[derive(Clone)] +pub enum Statement<'a> { + Val { + identifier: &'a Token<'a>, + initializer: &'a Expression<'a>, + }, + Var { + identifier: &'a Token<'a>, + initializer: &'a Expression<'a>, + }, + Expression { + expr: &'a Expression<'a>, + }, +} + +#[derive(Clone)] +pub enum Expression<'a> { + // Basic + Literal(Value), + Unary { + operation: Operation, + expr: &'a Expression<'a>, + }, + Binary { + operation: Operation, + lhs: &'a Expression<'a>, + rhs: &'a Expression<'a>, + }, + // Grouping +} + +#[derive(Clone)] +pub enum Operation { + Add, + Subtract, +} + +#[derive(Clone)] +pub struct Value(pub i32); + +#[test] +fn test() { + let right = Expression::Literal(Value(7)); + let _ = Expression::Binary { + operation: Operation::Add, + lhs: &Expression::Literal(Value(5)), + rhs: &right, + }; +} diff --git a/src/lexer.rs b/src/lexer.rs index 74a006c..27669cb 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -3,6 +3,7 @@ #[derive(Debug, Eq, PartialEq)] pub enum TokenType { // Utility + DocComment(String), Comment(String), // Short @@ -156,6 +157,15 @@ impl<'a> Iterator for Lexer<'a> { let tt = match character { // Whitespace & Comments + '#' if self.advance_if_eq(Some('#')) => { + let mut value = String::new(); + while self.peek() != Some('\n') { + value.push(self.advance().unwrap()); + } + + TokenType::DocComment(value) + } + '#' => { let mut value = String::new(); while self.peek() != Some('\n') { diff --git a/src/main.rs b/src/main.rs index 8f3c001..f064d39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,13 +10,16 @@ pub mod ast; pub mod lexer; +use ast::{Expression, Operation, Value}; use lexer::Lexer; +use crate::ast::Statement; + const SOURCE: &str = r#" val variable = 5; -if variable >= 7 { +if variable <= 7 { print "Hello World"; } @@ -27,4 +30,17 @@ fn main() { for token in lexer { print!("{} ", token.lexeme); } + + println!("-------"); + + let a = Expression::Literal(Value(7)); + let b = Expression::Binary { + operation: Operation::Add, + lhs: &Expression::Literal(Value(5)), + rhs: &a, + }; + + let stmt = Statement::Expression { expr: &b }; + + println!("{stmt}"); } diff --git a/tour/literals.sloth b/tour/literals.sloth index 4aa4533..e2be0d0 100644 --- a/tour/literals.sloth +++ b/tour/literals.sloth @@ -31,14 +31,46 @@ val maps: Map<String, i32> = { "bar": 97, }; -# `value?` Can be used to bubble up an Option or Result -# `value!` Can be used to panic on None or Error - -maps["foo"] # Option<i32> -maps["foo"]! # 48 -maps["foo"]? # 48 - Caller of function is responsible for None case -maps.keys() # ["foo", "bar"] -maps.values() # [48, 97] +# Types can be 'any' and be assigned to anything +var anything: any = "I'm a string right now"; +anything = 53; +anything = "I was a number- but now I'm a string again"; + +# You can use the `is` keyword to check if a type is something +if anything is String { + # Now I can call functions that take a String + anything.split('-'); +} + +# TODO: HMMMMMMM- +if anything is Some(it) {} + +# You can use the `in` keyword to check if something is in a collection +if "hello" in ["hello", "hola"] { + # +} + +# ... or a range +if 5 in 2..17 { + # +} + +# ... or anything that implements Contains<T> +if 'p' in "Apple" {} # impl Contains<char> for String +if "ppl" in "Apple" {} # impl Contains<String> for String +if /[0-9]/ in "24" {} # impl Contains<Regex> for String + +# `value!` Can be used to bubble up an Option or Result +# `value!!` Can be used to panic on None or Error +# `value?` Can be used to optionally chain +# `value ?: 0` Can be used to provide a default + +maps["foo"] # Option<i32> +maps["foo"]!! # 48 - Panics in None case +maps["foo"]! # 48 - Caller of function is responsible for None case +maps["foo"]?.signum() ?: 0 # 1 - Provide a default for None case +maps.keys() # ["foo", "bar"] +maps.values() # [48, 97] # Spreading val lhs = [1, 2, 3]; diff --git a/tour/modules.sloth b/tour/modules.sloth index 1523753..b91261e 100644 --- a/tour/modules.sloth +++ b/tour/modules.sloth @@ -24,18 +24,49 @@ use extern "git://github.com/CatDevz/AdventOfCode.git" as spookylib; use extern "ftp://codyq.dev/libs/spookylib.sloth" as spookylib; use extern "https://codyq.dev/libs/spookylib.sloth" as spookylib; -# In order to use modules or members of modules without quantifying the entire -# path you must include them using a `use` statement. Star imports do not exist -# because they fucking suck. +# In sloth files will automatically become modules. Files named `mod.sloth` will +# become the module for the directory. If you have a project with multiple files +# the root module will be `mod.sloth`. Anything exposed in this can just strait up +# be used. # +# If no `mod.sloth` exists, for example when running single file scripts, it will +# be trated as if there is one but its empty. + +# /mod.sloth +pub fn fib(n: i32) -> i32 { + match n { + 0 | 1 => n, + _ => fib(n - 1) + fib(n - 2), + } +} + +# /foo.sloth +use fib; # TODO: + +fib(5); # Works because everything inside of mod.sloth is fair game + +# This means if you want an extern library to be available in every module you just +# need to add a `pub(pkg) use extern "lib"` + +# In order to use modules or members of modules without quantifying the entire +# path you must include them using a `use` statement. +use foo::bar; + +# Sloth will use your root module (the one used as your entrypoint) for imports. +# In order to import from modules relative to your own you must prefix the use +# with a `::` +use ::from::relative; + +# If you would live to traverse up the module tree you can use the `super` +# psudo-module. +use + +# TODO: # Sloth will automatically turn files relative to your own and directories # relative to your own with a `mod.sloth` into modules. In order to traverse -# up the module tree you can use the `super` and `pkg` psudo-modules. -# -# The super psudo-module will go up a single module in the module tree whereas -# the pkg psudo-module will go to the root module in the module tree. -use pkg::a; -use pkg::b; +# up the module tree you can use the `super` psudo-module. +use super::a; +use super::b; use std::rand::random; use std::uuid; diff --git a/tour/types.sloth b/tour/types.sloth index c7ef2f5..0fdae33 100644 --- a/tour/types.sloth +++ b/tour/types.sloth @@ -32,12 +32,24 @@ type Option<T> = type Class = Archer | Mage | Tank; type Operation = - | Add = (Operation, Operation) - | Literal = i32; + | Add = (Operation, Operation) + | Lit = i32; + +# Untagged Union Type +# +# Unlike tagged unions if all variants of a untagged union implement a specific +# trait you will be able to call those trait methods without getting the variant +# first. +type Untagged = i32 + String; # Type Alias type OptionPos = Option<Position>; +# You can define untagged union types and tuple types inline without a name, this +# may be useful for one-off types only used by a single function. +val example: String + i32 = 52; +val example: (i32, i32) = (5, 5); + # Functions can be associated with types using 'impl' blocks. type Color = (f64, f64, f64); |
