diff --git a/.github/logo.png b/.github/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..46e7e472cc5655f578d3cd2e873d230107999375 Binary files /dev/null and b/.github/logo.png differ diff --git a/README.md b/README.md index a8c86bb587ee9cc3e6675df01bc6b647fa07b107..e95f7dda52704002c53d136983f133003890a02a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![Blight Logo](/.github/logo.png) + # Blight Blight sure is a programming language. diff --git a/crates/ast/src/lib.rs b/crates/ast/src/lib.rs index 8ba4651df3847987415e038f685d975ab26229b0..c4841db4e84a9184793e9563f94d5e15b3dbaa66 100644 --- a/crates/ast/src/lib.rs +++ b/crates/ast/src/lib.rs @@ -3,6 +3,7 @@ use miette::Result; mod error; mod parse_block; mod parse_expression; +mod parse_function_call; mod parse_statement; mod parse_type; mod types; diff --git a/crates/ast/src/parse_expression.rs b/crates/ast/src/parse_expression.rs index 1b4467820c608c13daa9a062466b0e487fcf1e75..f71c1cd26b5070d3577eb7bcd607b2f5e4417005 100644 --- a/crates/ast/src/parse_expression.rs +++ b/crates/ast/src/parse_expression.rs @@ -4,6 +4,7 @@ use tokenizer::Token; use super::error::AstError; use super::parse_block::parse_block; +use super::parse_function_call::parse_function_call; use super::types::{Ast, Block, ParserContext}; pub fn parse_expression(tokens: &mut ParserContext) -> Result { @@ -11,6 +12,16 @@ pub fn parse_expression(tokens: &mut ParserContext) -> Result { Some(token) => match token { Token::Number(number) => Ast::Number(number), Token::String(string) => Ast::String(string.clone()), + Token::BuiltIn(name) => { + match tokens.peek() { + Some(Token::OpenParanthesis) => { + // FRICK we consumed the open paranthesis + tokens.back(); + parse_function_call(tokens)? + } + _ => Ast::BuiltIn(name.clone()), + } + } Token::True => Ast::Bool(true), Token::False => Ast::Bool(false), Token::OpenBracket => { @@ -57,43 +68,16 @@ pub fn parse_expression(tokens: &mut ParserContext) -> Result { else_value, }); } - Token::Identifier(name) => match tokens.peek() { - Some(Token::OpenParanthesis) => { - tokens.next(); - - let mut arguments = Vec::new(); - - loop { - match tokens.peek() { - Some(Token::ClosedParanthesis) => { - tokens.next(); - break; - } - _ => (), - } - arguments.push(parse_expression(tokens)?); - - match tokens.peek() { - Some(Token::Comma) => { - tokens.next(); - } - Some(Token::ClosedParanthesis) => (), - _ => { - return Err(AstError::new( - "Expected ) or , but found something else instead.", - ) - .into()) - } - } - } - - Ast::FunctionCall { - name: name.clone(), - arguments, + Token::Identifier(name) => { + match tokens.peek() { + Some(Token::OpenParanthesis) => { + // FRICK we consumed the open paranthesis + tokens.back(); + parse_function_call(tokens)? } + _ => Ast::Identifier(name.clone()), } - _ => Ast::Identifier(name.clone()), - }, + } _ => { println!("token: {:?}", token); return Err(AstError::new("Expected expression, but did not get it").into()); diff --git a/crates/ast/src/parse_function_call.rs b/crates/ast/src/parse_function_call.rs new file mode 100644 index 0000000000000000000000000000000000000000..57c177d8c77fb7e7aeb1b1d7cbd502c7651ca133 --- /dev/null +++ b/crates/ast/src/parse_function_call.rs @@ -0,0 +1,56 @@ +use miette::Result; + +use tokenizer::Token; + +use super::error::AstError; +use super::parse_expression::parse_expression; +use super::types::{Ast, ParserContext}; + +/// Parses a function call AS AN EXPRESSION +/// This does not consume the semicolon at the end of the function call +/// if it is a statement. +pub fn parse_function_call(tokens: &mut ParserContext) -> Result { + let identifier = match tokens.next() { + Some(Token::Identifier(identifier)) => Ast::Identifier(identifier), + Some(Token::BuiltIn(identifier)) => Ast::BuiltIn(identifier), + _ => return Err(AstError::new("Expected identifier").into()), + }; + + match tokens.peek() { + Some(Token::OpenParanthesis) => { + tokens.next(); + + let mut arguments = Vec::new(); + + loop { + match tokens.peek() { + Some(Token::ClosedParanthesis) => { + tokens.next(); + break; + } + _ => (), + } + arguments.push(parse_expression(tokens)?); + + match tokens.peek() { + Some(Token::Comma) => { + tokens.next(); + } + Some(Token::ClosedParanthesis) => (), + _ => { + return Err(AstError::new( + "Expected ) or , but found something else instead.", + ) + .into()) + } + } + } + + Ok(Ast::FunctionCall { + name: Box::new(identifier), + arguments, + }) + } + _ => Err(AstError::new("Unexpected identifier").into()), + } +} diff --git a/crates/ast/src/parse_statement.rs b/crates/ast/src/parse_statement.rs index 8cd4b63e89df1f9b19a88a30920aaac7aecba055..343aceff9eea0e9fc29c57e25fab2387e39f8b32 100644 --- a/crates/ast/src/parse_statement.rs +++ b/crates/ast/src/parse_statement.rs @@ -5,6 +5,7 @@ use tokenizer::Token; use super::error::AstError; use super::parse_block::parse_block; use super::parse_expression::parse_expression; +use super::parse_function_call::parse_function_call; use super::parse_type::parse_type; use super::types::{Argument, Ast, Block, ParserContext}; @@ -12,6 +13,16 @@ use super::types::{Argument, Ast, Block, ParserContext}; pub fn parse_statement(tokens: &mut ParserContext) -> Result { let token = tokens.next().unwrap(); match token { + Token::BuiltIn(name) => { + match tokens.peek() { + Some(Token::OpenParanthesis) => { + // Okay, so we messed up. We shouldn't have consumed that token. + tokens.back(); + parse_function_call(tokens) + } + _ => Ok(Ast::BuiltIn(name)), + } + } Token::OpenBracket => { // FRICK, we have to backtrack tokens.back(); @@ -44,7 +55,7 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result { Some(Token::Semicolon) => (), _ => { return Err( - AstError::new("Expected semicolon after variable declaration").into(), + AstError::new("Expected semicolon after constant declaration").into(), ) } }; @@ -213,45 +224,21 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result { else_value, }); } - Token::Identifier(name) => match tokens.peek() { + Token::Identifier(_) => match tokens.peek() { Some(Token::OpenParanthesis) => { - tokens.next(); - - let mut arguments = Vec::new(); - - loop { - match tokens.peek() { - Some(Token::ClosedParanthesis) => { - tokens.next(); - break; - } - _ => (), - } - arguments.push(parse_expression(tokens)?); - - match tokens.peek() { - Some(Token::Comma) => { - tokens.next(); - } - Some(Token::ClosedParanthesis) => (), - _ => { - return Err(AstError::new( - "Expected ) or , but found something else instead.", - ) - .into()) - } - } - } - + // We need to give the identifier back to the parser + tokens.back(); + let result = parse_function_call(tokens); match tokens.next() { Some(Token::Semicolon) => (), - _ => return Err(AstError::new("Expected semicolon after function call").into()), + _ => { + return Err(AstError::new( + "Expected semicolon after function call statement", + ) + .into()) + } }; - - Ok(Ast::FunctionCall { - name: name.clone(), - arguments, - }) + result } _ => Err(AstError::new("Unexpected identifier").into()), }, diff --git a/crates/ast/src/parse_type.rs b/crates/ast/src/parse_type.rs index 9b061cdcc6f8483921a1067e99f81d91252ffb88..6837edec59b4d8f649ccc28c65f33362ef948c49 100644 --- a/crates/ast/src/parse_type.rs +++ b/crates/ast/src/parse_type.rs @@ -13,6 +13,7 @@ pub fn parse_type(tokens: &mut ParserContext) -> Result { "bigint" => Ok(Type::Bigint), "bool" => Ok(Type::Bool), "string" => Ok(Type::String), + "any" => Ok(Type::Any), _ => return Err(AstError::new("Expected type").into()), }, _ => return Err(AstError::new("Expected type").into()), diff --git a/crates/ast/src/types.rs b/crates/ast/src/types.rs index 9551f067516b0faf8c8333e6aa61bf56ca379ed4..3500088aa85499be1b8eb25c25ed7749ff58c598 100644 --- a/crates/ast/src/types.rs +++ b/crates/ast/src/types.rs @@ -41,28 +41,29 @@ impl ParserContext { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Type { Int, Float, Bigint, Bool, String, + Any, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Block { pub value: Vec, pub implicit_return: bool, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Argument { pub name: String, pub return_type: Type, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Ast { ConstantDeclaration { name: String, @@ -86,7 +87,8 @@ pub enum Ast { block: Block, }, FunctionCall { - name: String, + /// Identifier or BuiltIn + name: Box, arguments: Vec, }, If { @@ -101,6 +103,7 @@ pub enum Ast { Bool(bool), String(String), Identifier(String), + BuiltIn(String), Comment(String), DocComment(String), } diff --git a/crates/js_ast/src/lib.rs b/crates/js_ast/src/lib.rs index 56e87fae25b384bb4d4ced8aadd487ff87013804..f0f738cde7b062470fb261f949c656f770af9b8a 100644 --- a/crates/js_ast/src/lib.rs +++ b/crates/js_ast/src/lib.rs @@ -109,10 +109,27 @@ fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression for argument in arguments { handles.push(get_expression_handle(argument, ctx)); } - ctx.push_internal_variable(JsExpression::FunctionCall { - name: ctx.get_identifier(name.clone()), - arguments: handles, - }) + match *name.clone() { + Ast::Identifier(name) => ctx.push_internal_variable(JsExpression::FunctionCall { + name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), + arguments: handles, + }), + Ast::BuiltIn(name) => match name.as_str() { + "@raw" => { + if handles.len() != 1 { + panic!("@raw expects exactly one argument"); + } + match handles.get(0).unwrap() { + JsExpression::String(value) => { + return JsExpression::Raw(value.clone()); + } + _ => panic!("@raw expects a string argument"), + } + } + _ => panic!("Unknown built-in function {}", name), + }, + _ => panic!("Function name must be an identifier"), + } } Ast::Operation { operator, @@ -286,13 +303,32 @@ fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { for argument in arguments { handles.push(get_expression_handle(argument, ctx)); } - ctx.push(JsStatement::Expression(JsExpression::FunctionCall { - name: match name.as_str() { - "print" => "console.log".to_string(), - _ => name.clone(), + match *name.clone() { + Ast::Identifier(name) => { + ctx.push(JsStatement::Expression(JsExpression::FunctionCall { + name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), + arguments: handles, + })); + } + Ast::BuiltIn(name) => match name.as_str() { + "@raw" => { + if handles.len() != 1 { + panic!("@raw expects exactly one argument"); + } + match handles.get(0).unwrap() { + JsExpression::String(value) => { + ctx.push(JsStatement::Expression(JsExpression::FunctionCall { + name: Box::new(JsExpression::Raw(value.clone())), + arguments: handles, + })); + } + _ => panic!("@raw expects a string argument"), + } + } + _ => panic!("Unknown built-in function {}", name), }, - arguments: handles, - })); + _ => panic!("Function name must be an identifier"), + } } Ast::Comment(_) | Ast::DocComment(_) => {} _ => { @@ -310,97 +346,3 @@ pub fn translate(_ast: Vec) -> Result> { Ok(ctx.pop_context()) } - -#[cfg(test)] -mod tests { - use crate::translate; - use crate::{JsExpression, JsStatement}; - use ast::{Ast, Type}; - use util::Operator; - - #[test] - fn test_translate() { - let ast = Ast::ConstantDeclaration { - name: "hi".to_string(), - return_type: Type::Int, - value: Box::new(Ast::Operation { - operator: Operator::Add, - left: Box::new(Ast::Operation { - operator: Operator::Add, - left: Box::new(Ast::Number(1.0)), - right: Box::new(Ast::Operation { - operator: Operator::Add, - left: Box::new(Ast::Number(2.0)), - right: Box::new(Ast::Number(3.0)), - }), - }), - right: Box::new(Ast::Operation { - operator: Operator::Add, - left: Box::new(Ast::Number(4.0)), - right: Box::new(Ast::Number(5.0)), - }), - }), - }; - assert_eq!( - translate(vec![ast]).unwrap(), - vec![ - JsStatement::ConstantDeclaration { - name: "$0".to_string(), - value: JsExpression::Number(1.0) - }, - JsStatement::ConstantDeclaration { - name: "$1".to_string(), - value: JsExpression::Number(2.0) - }, - JsStatement::ConstantDeclaration { - name: "$2".to_string(), - value: JsExpression::Number(3.0) - }, - JsStatement::ConstantDeclaration { - name: "$3".to_string(), - value: JsExpression::Operation { - operator: Operator::Add, - left: Box::new(JsExpression::Identifier("$1".to_string())), - right: Box::new(JsExpression::Identifier("$2".to_string())) - } - }, - JsStatement::ConstantDeclaration { - name: "$4".to_string(), - value: JsExpression::Operation { - operator: Operator::Add, - left: Box::new(JsExpression::Identifier("$0".to_string())), - right: Box::new(JsExpression::Identifier("$3".to_string())) - } - }, - JsStatement::ConstantDeclaration { - name: "$5".to_string(), - value: JsExpression::Number(4.0) - }, - JsStatement::ConstantDeclaration { - name: "$6".to_string(), - value: JsExpression::Number(5.0) - }, - JsStatement::ConstantDeclaration { - name: "$7".to_string(), - value: JsExpression::Operation { - operator: Operator::Add, - left: Box::new(JsExpression::Identifier("$5".to_string())), - right: Box::new(JsExpression::Identifier("$6".to_string())) - } - }, - JsStatement::ConstantDeclaration { - name: "$8".to_string(), - value: JsExpression::Operation { - operator: Operator::Add, - left: Box::new(JsExpression::Identifier("$4".to_string())), - right: Box::new(JsExpression::Identifier("$7".to_string())) - } - }, - JsStatement::ConstantDeclaration { - name: "hi".to_string(), - value: JsExpression::Identifier("$8".to_string()) - } - ] - ) - } -} diff --git a/crates/js_ast/src/types.rs b/crates/js_ast/src/types.rs index 022641d491214b181b64f9af75bb54ae247661b3..18a2788da595867d00cc1249f0cbd40f5487f3d0 100644 --- a/crates/js_ast/src/types.rs +++ b/crates/js_ast/src/types.rs @@ -10,6 +10,7 @@ pub enum JsExpression { String(String), Bool(bool), Identifier(String), + Raw(String), Array(Vec), Object(HashMap), Operation { @@ -22,7 +23,7 @@ pub enum JsExpression { value: Vec, }, FunctionCall { - name: String, + name: Box, arguments: Vec, }, } diff --git a/crates/js_printer/src/print_expression.rs b/crates/js_printer/src/print_expression.rs index be771126970a835788f3a7f92e65e83e4f84143b..ff87066c667b0ed8496173cb4fbe09422702e859 100644 --- a/crates/js_printer/src/print_expression.rs +++ b/crates/js_printer/src/print_expression.rs @@ -4,6 +4,7 @@ use crate::print_statement::print_statement; pub fn print_expression(statement: &JsExpression) -> String { match statement { + JsExpression::Raw(value) => format!("{}", value), JsExpression::Number(value) => format!("{}", value), JsExpression::BigInt(value) => format!("{}n", value), JsExpression::String(value) => { @@ -61,7 +62,11 @@ pub fn print_expression(statement: &JsExpression) -> String { ), JsExpression::FunctionCall { name, arguments } => format!( "{}({})", - name, + match *name.clone() { + JsExpression::Identifier(value) => value.clone(), + JsExpression::Raw(value) => value.clone(), + _ => panic!("Function call name must be an identifier"), + }, arguments .iter() .map(|x| print_expression(x)) @@ -238,21 +243,21 @@ mod tests { fn function_call() { assert_eq!( print_expression(&JsExpression::FunctionCall { - name: "hello".to_string(), + name: Box::new(JsExpression::Identifier("hello".to_string())), arguments: vec![], }), "hello()" ); assert_eq!( print_expression(&JsExpression::FunctionCall { - name: "hello".to_string(), + name: Box::new(JsExpression::Identifier("hello".to_string())), arguments: vec![JsExpression::Number(1.0)], }), "hello(1)" ); assert_eq!( print_expression(&JsExpression::FunctionCall { - name: "hello".to_string(), + name: Box::new(JsExpression::Identifier("hello".to_string())), arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)], }), "hello(1,2)" diff --git a/crates/tokenizer/src/lib.rs b/crates/tokenizer/src/lib.rs index 5dc2c9f62e6a9beb435edeb98dfe5d911f7eddfd..7e2ae39951da2165efdf34c6e38391072362aaab 100644 --- a/crates/tokenizer/src/lib.rs +++ b/crates/tokenizer/src/lib.rs @@ -16,6 +16,7 @@ pub enum Token { Identifier(String), Number(f64), String(String), + BuiltIn(String), // Comments Comment(String), DocComment(String), @@ -49,7 +50,7 @@ pub fn tokenize(content: String) -> Result> { loop { match chars.next() { Some((_, c)) => match c { - 'a'..='z' | 'A'..='Z' | '_' => { + 'a'..='z' | 'A'..='Z' | '_' | '@' => { let mut current_token = String::new(); current_token.push(c); loop { @@ -77,7 +78,13 @@ pub fn tokenize(content: String) -> Result> { "return" => tokens.push(Token::Return), "true" => tokens.push(Token::True), "false" => tokens.push(Token::False), - _ => tokens.push(Token::Identifier(current_token)), + _ => { + if current_token.starts_with('@') { + tokens.push(Token::BuiltIn(current_token)); + } else { + tokens.push(Token::Identifier(current_token)) + } + } } } '"' => { diff --git a/examples/basics/control.blight b/examples/basics/control.blight index a6d7f06cd84c9b6e611d6cf9e42e988093f765e3..1428905a5fddb419fb987256783c401c956355d2 100644 --- a/examples/basics/control.blight +++ b/examples/basics/control.blight @@ -1,3 +1,5 @@ +let print: any = @raw("console.log"); + let x: bool = true; if x { diff --git a/examples/basics/expressions.blight b/examples/basics/expressions.blight index 553407cb046bdb2f0bcbe3bfc9a7db0b83366594..fbc8e4c4c024dff1abf958045fd7d5a3c189bd7c 100644 --- a/examples/basics/expressions.blight +++ b/examples/basics/expressions.blight @@ -7,4 +7,5 @@ let x: int = 5 + { y }; +let print: any = @raw("console.log"); print(x); // 10 diff --git a/examples/basics/fib.blight b/examples/basics/fib.blight index 95762dfe0f74f8409b8371ff4a3d6c4e92d625a1..898f001d4f163a3b4142a38a64a86fd2393b9181 100644 --- a/examples/basics/fib.blight +++ b/examples/basics/fib.blight @@ -8,4 +8,5 @@ pub fn fib(n: int) -> int { } } +let print: any = @raw("console.log"); print(fib(10)); diff --git a/examples/basics/functions.blight b/examples/basics/functions.blight index dc14aaef31aa61086b22d34a0946cf4c253e4e23..58cd31caa9d8c9c49aadd8609bc9ccc62bc36eb2 100644 --- a/examples/basics/functions.blight +++ b/examples/basics/functions.blight @@ -4,4 +4,5 @@ fn bingus(x: int, y: int) -> int { let result: int = bingus(10, 25); -print(result); // built-in function +let print: any = @raw("console.log"); +print(result); diff --git a/examples/basics/operations.blight b/examples/basics/operations.blight index f35a323df01bec88a06c2e9198ab22283c5be8a0..85ee1283b38b61b6840f20d8dd6f8c206d8cf7c9 100644 --- a/examples/basics/operations.blight +++ b/examples/basics/operations.blight @@ -1 +1,4 @@ let a: int = 10 - 2 / 4 * 15; + +let print: any = @raw("console.log"); +print(a); diff --git a/examples/basics/shadowing.blight b/examples/basics/shadowing.blight index 7e2b4c6228d78014922200d4b84b41bc477b1b7d..2a8514581e8a102a5f67b44c22f825aca3fd9252 100644 --- a/examples/basics/shadowing.blight +++ b/examples/basics/shadowing.blight @@ -1,3 +1,5 @@ +let print: any = @raw("console.log"); + let x: int = 0; let x: int = 1; print(x); // 1 diff --git a/examples/basics/variables.blight b/examples/basics/variables.blight index 3fd14633a137273fc6ee0ff72f516c67114dbeac..3bfe757ddba318fa7e40620b6a4bbbd6c3f2cf45 100644 --- a/examples/basics/variables.blight +++ b/examples/basics/variables.blight @@ -5,3 +5,6 @@ var z: bigint = 0; let a: int = 0; let b: float = 0; let c: bigint = 0; + +let print: any = @raw("console.log"); +print(x); diff --git a/examples/basics/weird.blight b/examples/basics/weird.blight index 9a78b1625bca53e3c2fe224a5c3efe7fe07e2fac..a10c1ec7a2f55dbbe68e90ab93503ceefd4c91aa 100644 --- a/examples/basics/weird.blight +++ b/examples/basics/weird.blight @@ -9,5 +9,6 @@ fn bingus(y: bool) -> int { x } +let print: any = @raw("console.log"); print(bingus(true)); print(bingus(false)); diff --git a/examples/interop/blight_in_js/main.js b/examples/interop/blight_in_js/main.js index 38a8ce7256c2388c8d9b669a82c8c6156bbe2ee4..18c652b5967116f7639c152c7fbebd1760631755 100644 --- a/examples/interop/blight_in_js/main.js +++ b/examples/interop/blight_in_js/main.js @@ -1,4 +1,4 @@ import { add, TOP_LEVEL } from "./add.js"; console.log(add(1, 2)); -console.log(TOP_LEVEL) +console.log(TOP_LEVEL);