11 Commits
0.2.0 ... 0.2.3

21 changed files with 863 additions and 250 deletions

View File

@@ -1,5 +1,28 @@
# Changelog # Changelog
[0.2.3]
- Fixed stack underflow with function invocations
- They are still "heavy", but they should work as expected now
- Fixed issue where syscall functions were not allowed as infix operators
[0.2.2]
- Fixed some formatting issues when converting Markdown to Text Mesh Pro for
Stationpedia
- Added support for ternary expressions
- `let i = someValue ? 4 : 5;`
- `i = someValue ? 4 : 5;`
- This greedily evaluates both sides, so side effects like calling functions
is not recommended i.e.
- `i = someValue : doSomething() : doSomethingElse();`
- Both sides will be evaluated before calling the `select` instruction
[0.2.1]
- Added support for `loadSlot` and `setSlot`
- Fixed bug where syscalls like `max(1, 2)` were not allowed in assignment expressions
[0.2.0] [0.2.0]
- Completely re-wrote the tokenizer to use `logos` - Completely re-wrote the tokenizer to use `logos`

View File

@@ -2,7 +2,7 @@
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Slang</Name> <Name>Slang</Name>
<Author>JoeDiertay</Author> <Author>JoeDiertay</Author>
<Version>0.2.0</Version> <Version>0.2.3</Version>
<Description> <Description>
[h1]Slang: High-Level Programming for Stationeers[/h1] [h1]Slang: High-Level Programming for Stationeers[/h1]

View File

@@ -12,6 +12,7 @@ public static class SlangPatches
{ {
private static ProgrammableChipMotherboard? _currentlyEditingMotherboard; private static ProgrammableChipMotherboard? _currentlyEditingMotherboard;
private static AsciiString? _motherboardCachedCode; private static AsciiString? _motherboardCachedCode;
private static Guid? _currentlyEditingGuid;
[HarmonyPatch( [HarmonyPatch(
typeof(ProgrammableChipMotherboard), typeof(ProgrammableChipMotherboard),
@@ -32,11 +33,13 @@ public static class SlangPatches
return; return;
} }
var thisRef = Guid.NewGuid(); var thisRef = _currentlyEditingGuid ?? Guid.NewGuid();
// Ensure we cache this compiled code for later retreival. // Ensure we cache this compiled code for later retreival.
GlobalCode.SetSource(thisRef, result); GlobalCode.SetSource(thisRef, result);
_currentlyEditingGuid = null;
// Append REF to the bottom // Append REF to the bottom
compiled += $"\n{GlobalCode.SLANG_REF}{thisRef}"; compiled += $"\n{GlobalCode.SLANG_REF}{thisRef}";
result = compiled; result = compiled;
@@ -77,6 +80,7 @@ public static class SlangPatches
return; return;
} }
_currentlyEditingGuid = sourceRef;
var slangSource = GlobalCode.GetSource(sourceRef); var slangSource = GlobalCode.GetSource(sourceRef);
if (string.IsNullOrEmpty(slangSource)) if (string.IsNullOrEmpty(slangSource))
@@ -223,6 +227,7 @@ public static class SlangPatches
_currentlyEditingMotherboard = null; _currentlyEditingMotherboard = null;
_motherboardCachedCode = null; _motherboardCachedCode = null;
_currentlyEditingGuid = null;
} }
[HarmonyPatch(typeof(Stationpedia), nameof(Stationpedia.Regenerate))] [HarmonyPatch(typeof(Stationpedia), nameof(Stationpedia.Regenerate))]

View File

@@ -26,12 +26,18 @@ public static class TextMeshProFormatter
RegexOptions.Singleline RegexOptions.Singleline
); );
// 3. Handle Headers (## Header)
// Convert ## Header to large bold text
text = Regex.Replace( text = Regex.Replace(
text, text,
@"^##(\s+)?(.+)$", @"^\s*##\s+(.+)$",
"<size=120%><b>$1</b></size>", "<size=110%><color=#ffffff><b>$1</b></color></size>",
RegexOptions.Multiline
);
// 3. Handle # Headers SECOND (General)
text = Regex.Replace(
text,
@"^\s*#\s+(.+)$",
"<size=120%><color=#ffffff><b>$1</b></color></size>",
RegexOptions.Multiline RegexOptions.Multiline
); );

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AssemblyName>StationeersSlang</AssemblyName> <AssemblyName>StationeersSlang</AssemblyName>
<Description>Slang Compiler Bridge</Description> <Description>Slang Compiler Bridge</Description>
<Version>0.2.0</Version> <Version>0.2.3</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -909,7 +909,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "slang" name = "slang"
version = "0.2.0" version = "0.2.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "slang" name = "slang"
version = "0.2.0" version = "0.2.3"
edition = "2021" edition = "2021"
[workspace] [workspace]

View File

@@ -1,9 +1,10 @@
use crate::compile; use crate::compile;
use anyhow::Result;
use indoc::indoc; use indoc::indoc;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn simple_binary_expression() -> anyhow::Result<()> { fn simple_binary_expression() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
@@ -26,7 +27,7 @@ fn simple_binary_expression() -> anyhow::Result<()> {
} }
#[test] #[test]
fn nested_binary_expressions() -> anyhow::Result<()> { fn nested_binary_expressions() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
@@ -51,6 +52,8 @@ fn nested_binary_expressions() -> anyhow::Result<()> {
add r1 r10 r9 add r1 r10 r9
mul r2 r1 r8 mul r2 r1 r8
move r15 r2 move r15 r2
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -71,7 +74,7 @@ fn nested_binary_expressions() -> anyhow::Result<()> {
} }
#[test] #[test]
fn stress_test_constant_folding() -> anyhow::Result<()> { fn stress_test_constant_folding() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
@@ -94,7 +97,7 @@ fn stress_test_constant_folding() -> anyhow::Result<()> {
} }
#[test] #[test]
fn test_constant_folding_with_variables_mixed_in() -> anyhow::Result<()> { fn test_constant_folding_with_variables_mixed_in() -> Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
r#" r#"
@@ -120,3 +123,55 @@ fn test_constant_folding_with_variables_mixed_in() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_ternary_expression() -> Result<()> {
let compiled = compile! {
debug
r#"
let i = 1 > 2 ? 15 : 20;
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
sgt r1 1 2
select r2 r1 15 20
move r8 r2 #i
"
}
);
Ok(())
}
#[test]
fn test_ternary_expression_assignment() -> Result<()> {
let compiled = compile! {
debug
r#"
let i = 0;
i = 1 > 2 ? 15 : 20;
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #i
sgt r1 1 2
select r2 r1 15 20
move r8 r2 #i
"
}
);
Ok(())
}

View File

@@ -17,6 +17,7 @@ fn no_arguments() -> anyhow::Result<()> {
j main j main
doSomething: doSomething:
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -34,14 +35,17 @@ fn no_arguments() -> anyhow::Result<()> {
#[test] #[test]
fn let_var_args() -> anyhow::Result<()> { fn let_var_args() -> anyhow::Result<()> {
// !IMPORTANT this needs to be stabilized as it currently incorrectly calculates sp offset at
// both ends of the cleanup lifecycle
let compiled = compile! { let compiled = compile! {
debug debug
" "
fn doSomething(arg1) {}; fn mul2(arg1) {
let arg1 = 123; return arg1 * 2;
let i = doSomething(arg1); };
loop {
let arg1 = 123;
let i = mul2(arg1);
i = i ** 2;
}
" "
}; };
@@ -50,23 +54,31 @@ fn let_var_args() -> anyhow::Result<()> {
indoc! { indoc! {
" "
j main j main
doSomething: mul2:
pop r8 #arg1 pop r8 #arg1
push ra push ra
mul r1 r8 2
move r15 r1
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
j ra j ra
main: main:
L2:
move r8 123 #arg1 move r8 123 #arg1
push r8 push r8
push r8 push r8
jal doSomething jal mul2
sub r0 sp 1 sub r0 sp 1
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #i move r9 r15 #i
sub sp sp 1 pow r1 r9 2
move r9 r1 #i
j L2
L3:
" "
} }
); );
@@ -97,7 +109,9 @@ fn inline_literal_args() -> anyhow::Result<()> {
let compiled = compile! { let compiled = compile! {
debug debug
" "
fn doSomething(arg1, arg2) {}; fn doSomething(arg1, arg2) {
return 5;
};
let thisVariableShouldStayInPlace = 123; let thisVariableShouldStayInPlace = 123;
let returnedValue = doSomething(12, 34); let returnedValue = doSomething(12, 34);
" "
@@ -112,6 +126,9 @@ fn inline_literal_args() -> anyhow::Result<()> {
pop r8 #arg2 pop r8 #arg2
pop r9 #arg1 pop r9 #arg1
push ra push ra
move r15 5 #returnValue
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -126,7 +143,6 @@ fn inline_literal_args() -> anyhow::Result<()> {
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #returnedValue move r9 r15 #returnedValue
sub sp sp 1
" "
} }
); );
@@ -154,6 +170,7 @@ fn mixed_args() -> anyhow::Result<()> {
pop r8 #arg2 pop r8 #arg2
pop r9 #arg1 pop r9 #arg1
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -168,7 +185,6 @@ fn mixed_args() -> anyhow::Result<()> {
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #returnValue move r9 r15 #returnValue
sub sp sp 1
" "
} }
); );
@@ -198,6 +214,8 @@ fn with_return_statement() -> anyhow::Result<()> {
pop r8 #arg1 pop r8 #arg1
push ra push ra
move r15 456 #returnValue move r15 456 #returnValue
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1
@@ -233,6 +251,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
doSomething: doSomething:
push ra push ra
move r15 -1 #returnValue move r15 -1 #returnValue
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1

View File

@@ -133,6 +133,8 @@ fn test_boolean_return() -> anyhow::Result<()> {
getTrue: getTrue:
push ra push ra
move r15 1 #returnValue move r15 1 #returnValue
j L1
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1

View File

@@ -21,6 +21,7 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
pop r13 #arg4 pop r13 #arg4
pop r14 #arg3 pop r14 #arg3
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 3 sub sp sp 3
@@ -31,6 +32,48 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_early_return() -> anyhow::Result<()> {
let compiled = compile!(debug r#"
// This is a test function declaration with no body
fn doSomething() {
if (1 == 1) {
return;
}
let i = 1 + 2;
return;
};
doSomething();
"#);
assert_eq!(
compiled,
indoc! {
"
j main
doSomething:
push ra
seq r1 1 1
beq r1 0 L2
j L1
L2:
move r8 3 #i
j L1
L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
j ra
main:
jal doSomething
move r1 r15 #__binary_temp_2
"
}
);
Ok(())
}
#[test] #[test]
fn test_function_declaration_with_register_params() -> anyhow::Result<()> { fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
let compiled = compile!(debug r#" let compiled = compile!(debug r#"
@@ -47,6 +90,7 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
pop r8 #arg2 pop r8 #arg2
pop r9 #arg1 pop r9 #arg1
push ra push ra
L1:
sub r0 sp 1 sub r0 sp 1
get ra db r0 get ra db r0
sub sp sp 1 sub sp sp 1

View File

@@ -243,6 +243,32 @@ fn test_max() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_max_from_game() -> Result<()> {
let compiled = compile! {
debug
r#"
let item = 0;
item = max(1 + 2, 2);
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #item
max r15 3 2
move r8 r15 #item
"
}
);
Ok(())
}
#[test] #[test]
fn test_min() -> Result<()> { fn test_min() -> Result<()> {
let compiled = compile! { let compiled = compile! {

View File

@@ -157,3 +157,54 @@ fn test_load_from_device() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_load_from_slot() -> anyhow::Result<()> {
let compiled = compile! {
debug
r#"
device airCon = "d0";
let setting = ls(airCon, 0, "Occupied");
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
ls r15 d0 0 Occupied
move r8 r15 #setting
"
}
);
Ok(())
}
#[test]
fn test_set_slot() -> anyhow::Result<()> {
let compiled = compile! {
debug
r#"
device airCon = "d0";
ss(airCon, 0, "Occupied", true);
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
ss d0 0 Occupied 1
"
}
);
Ok(())
}

View File

@@ -8,7 +8,7 @@ use parser::{
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression, AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression,
LoopExpression, MemberAccessExpression, Span, Spanned, WhileExpression, LoopExpression, MemberAccessExpression, Span, Spanned, TernaryExpression, WhileExpression,
}, },
}; };
use std::{ use std::{
@@ -145,6 +145,7 @@ pub struct CompilerConfig {
pub debug: bool, pub debug: bool,
} }
#[derive(Debug)]
struct CompilationResult<'a> { struct CompilationResult<'a> {
location: VariableLocation<'a>, location: VariableLocation<'a>,
/// If Some, this is the name of the temporary variable that holds the result. /// If Some, this is the name of the temporary variable that holds the result.
@@ -164,6 +165,7 @@ pub struct Compiler<'a, 'w, W: std::io::Write> {
temp_counter: usize, temp_counter: usize,
label_counter: usize, label_counter: usize,
loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label) loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label)
current_return_label: Option<Cow<'a, str>>,
pub errors: Vec<Error<'a>>, pub errors: Vec<Error<'a>>,
} }
@@ -185,6 +187,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
temp_counter: 0, temp_counter: 0,
label_counter: 0, label_counter: 0,
loop_stack: Vec::new(), loop_stack: Vec::new(),
current_return_label: None,
errors: Vec::new(), errors: Vec::new(),
} }
} }
@@ -313,6 +316,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
self.expression_assignment(assign_expr.node, scope)?; self.expression_assignment(assign_expr.node, scope)?;
Ok(None) Ok(None)
} }
Expression::Ternary(tern) => Ok(Some(self.expression_ternary(tern.node, scope)?)),
Expression::Invocation(expr_invoke) => { Expression::Invocation(expr_invoke) => {
self.expression_function_invocation(expr_invoke, scope)?; self.expression_function_invocation(expr_invoke, scope)?;
// Invocation returns result in r15 (RETURN_REGISTER). // Invocation returns result in r15 (RETURN_REGISTER).
@@ -740,6 +744,23 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
(var_loc, None) (var_loc, None)
} }
Expression::Ternary(ternary) => {
let res = self.expression_ternary(ternary.node, scope)?;
println!("{res:?}");
let var_loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
let res_register = self.resolve_register(&res.location)?;
self.emit_variable_assignment(name_str, &var_loc, res_register)?;
if let Some(name) = res.temp_name {
scope.free_temp(name, None)?;
}
(var_loc, None)
}
_ => { _ => {
return Err(Error::Unknown( return Err(Error::Unknown(
format!("`{name_str}` declaration of this type is not supported/implemented."), format!("`{name_str}` declaration of this type is not supported/implemented."),
@@ -865,6 +886,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
scope.free_temp(c, None)?; scope.free_temp(c, None)?;
} }
} }
_ => { _ => {
return Err(Error::Unknown( return Err(Error::Unknown(
"Invalid assignment target. Only variables and member access are supported." "Invalid assignment target. Only variables and member access are supported."
@@ -880,7 +902,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
fn expression_function_invocation( fn expression_function_invocation(
&mut self, &mut self,
invoke_expr: Spanned<InvocationExpression<'a>>, invoke_expr: Spanned<InvocationExpression<'a>>,
stack: &mut VariableScope<'a, '_>, parent_scope: &mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> { ) -> Result<(), Error<'a>> {
let InvocationExpression { name, arguments } = invoke_expr.node; let InvocationExpression { name, arguments } = invoke_expr.node;
@@ -906,9 +928,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
// Best to skip generation of this call to prevent bad IC10 // Best to skip generation of this call to prevent bad IC10
return Ok(()); return Ok(());
} }
let mut stack = VariableScope::scoped(parent_scope);
// backup all used registers to the stack // backup all used registers to the stack
let active_registers = stack.registers().cloned().collect::<Vec<_>>(); let active_registers = stack.registers();
for register in &active_registers { for register in &active_registers {
stack.add_variable( stack.add_variable(
Cow::from(format!("temp_{register}")), Cow::from(format!("temp_{register}")),
@@ -971,7 +994,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
} }
Expression::Binary(bin_expr) => { Expression::Binary(bin_expr) => {
// Compile the binary expression to a temp register // Compile the binary expression to a temp register
let result = self.expression_binary(bin_expr, stack)?; let result = self.expression_binary(bin_expr, &mut stack)?;
let reg_str = self.resolve_register(&result.location)?; let reg_str = self.resolve_register(&result.location)?;
self.write_output(format!("push {reg_str}"))?; self.write_output(format!("push {reg_str}"))?;
if let Some(name) = result.temp_name { if let Some(name) = result.temp_name {
@@ -980,7 +1003,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
} }
Expression::Logical(log_expr) => { Expression::Logical(log_expr) => {
// Compile the logical expression to a temp register // Compile the logical expression to a temp register
let result = self.expression_logical(log_expr, stack)?; let result = self.expression_logical(log_expr, &mut stack)?;
let reg_str = self.resolve_register(&result.location)?; let reg_str = self.resolve_register(&result.location)?;
self.write_output(format!("push {reg_str}"))?; self.write_output(format!("push {reg_str}"))?;
if let Some(name) = result.temp_name { if let Some(name) = result.temp_name {
@@ -999,7 +1022,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
end_line: 0, end_line: 0,
}, // Dummy span }, // Dummy span
}, },
stack, &mut stack,
)?; )?;
if let Some(result) = result_opt { if let Some(result) = result_opt {
@@ -1207,6 +1230,45 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
} }
} }
fn expression_ternary(
&mut self,
expr: TernaryExpression<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<CompilationResult<'a>, Error<'a>> {
let TernaryExpression {
condition,
true_value,
false_value,
} = expr;
let (cond, cond_clean) = self.compile_operand(*condition, scope)?;
let (true_val, true_clean) = self.compile_operand(*true_value, scope)?;
let (false_val, false_clean) = self.compile_operand(*false_value, scope)?;
let result_name = self.next_temp_name();
let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
let result_reg = self.resolve_register(&result_loc)?;
self.write_output(format!(
"select {} {} {} {}",
result_reg, cond, true_val, false_val
))?;
if let Some(clean) = cond_clean {
scope.free_temp(clean, None)?;
}
if let Some(clean) = true_clean {
scope.free_temp(clean, None)?;
}
if let Some(clean) = false_clean {
scope.free_temp(clean, None)?;
}
Ok(CompilationResult {
location: result_loc,
temp_name: Some(result_name),
})
}
/// Helper to resolve a location to a register string (e.g., "r0"). /// Helper to resolve a location to a register string (e.g., "r0").
/// Note: This does not handle Stack locations automatically, as they require /// Note: This does not handle Stack locations automatically, as they require
/// instruction emission to load. Use `compile_operand` for general handling. /// instruction emission to load. Use `compile_operand` for general handling.
@@ -1500,31 +1562,33 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
mut expr: BlockExpression<'a>, mut expr: BlockExpression<'a>,
parent_scope: &'v mut VariableScope<'a, '_>, parent_scope: &'v mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> { ) -> Result<(), Error<'a>> {
fn get_expression_priority<'a>(expr: &Spanned<Expression<'a>>) -> u32 {
match expr.node {
Expression::ConstDeclaration(_) => 0,
Expression::DeviceDeclaration(_) => 1,
Expression::Function(_) => 2,
_ => 3,
}
}
// First, sort the expressions to ensure functions are hoisted // First, sort the expressions to ensure functions are hoisted
expr.0.sort_by(|a, b| { expr.0.sort_by(|a, b| {
if matches!( let a_cost = get_expression_priority(a);
b.node, let b_cost = get_expression_priority(b);
Expression::Function(_) | Expression::ConstDeclaration(_)
) && matches!( a_cost.cmp(&b_cost)
a.node,
Expression::Function(_) | Expression::ConstDeclaration(_)
) {
std::cmp::Ordering::Equal
} else if matches!(
a.node,
Expression::Function(_) | Expression::ConstDeclaration(_)
) {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
}); });
let mut scope = VariableScope::scoped(parent_scope); let mut scope = VariableScope::scoped(parent_scope);
for expr in expr.0 { for expr in expr.0 {
if !self.declared_main if !self.declared_main
&& !matches!(expr.node, Expression::Function(_)) && !matches!(
expr.node,
Expression::Function(_)
| Expression::ConstDeclaration(_)
| Expression::DeviceDeclaration(_)
)
&& !parent_scope.has_parent() && !parent_scope.has_parent()
{ {
self.write_output("main:")?; self.write_output("main:")?;
@@ -1533,7 +1597,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
match expr.node { match expr.node {
Expression::Return(ret_expr) => { Expression::Return(ret_expr) => {
self.expression_return(*ret_expr, &mut scope)?; self.expression_return(ret_expr, &mut scope)?;
} }
_ => { _ => {
// Swallow errors within expressions so block can continue // Swallow errors within expressions so block can continue
@@ -1563,133 +1627,149 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
/// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER /// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER
fn expression_return( fn expression_return(
&mut self, &mut self,
expr: Spanned<Expression<'a>>, expr: Option<Box<Spanned<Expression<'a>>>>,
scope: &mut VariableScope<'a, '_>, scope: &mut VariableScope<'a, '_>,
) -> Result<VariableLocation<'a>, Error<'a>> { ) -> Result<VariableLocation<'a>, Error<'a>> {
if let Expression::Negation(neg_expr) = &expr.node if let Some(expr) = expr {
&& let Expression::Literal(spanned_lit) = &neg_expr.node if let Expression::Negation(neg_expr) = &expr.node
&& let Literal::Number(neg_num) = &spanned_lit.node && let Expression::Literal(spanned_lit) = &neg_expr.node
{ && let Literal::Number(neg_num) = &spanned_lit.node
let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER); {
self.emit_variable_assignment( let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER);
Cow::from("returnValue"), self.emit_variable_assignment(
&loc, Cow::from("returnValue"),
Cow::from(format!("-{neg_num}")), &loc,
)?; Cow::from(format!("-{neg_num}")),
return Ok(loc);
};
match expr.node {
Expression::Variable(var_name) => {
match scope.get_location_of(&var_name.node, Some(var_name.span)) {
Ok(loc) => match loc {
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
self.write_output(format!(
"move r{} r{reg} {}",
VariableScope::RETURN_REGISTER,
debug!(self, "#returnValue")
))?;
}
VariableLocation::Constant(lit) => {
let str = extract_literal(lit, false)?;
self.write_output(format!(
"move r{} {str} {}",
VariableScope::RETURN_REGISTER,
debug!(self, "#returnValue")
))?
}
VariableLocation::Stack(offset) => {
self.write_output(format!(
"sub r{} sp {offset}",
VariableScope::TEMP_STACK_REGISTER
))?;
self.write_output(format!(
"get r{} db r{}",
VariableScope::RETURN_REGISTER,
VariableScope::TEMP_STACK_REGISTER
))?;
}
VariableLocation::Device(_) => {
return Err(Error::Unknown(
"You can not return a device from a function.".into(),
Some(var_name.span),
));
}
},
Err(_) => {
self.errors.push(Error::UnknownIdentifier(
var_name.node.clone(),
var_name.span,
));
// Proceed with dummy
}
}
}
Expression::Literal(spanned_lit) => match spanned_lit.node {
Literal::Number(num) => {
self.emit_variable_assignment(
Cow::from("returnValue"),
&VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
Cow::from(num.to_string()),
)?;
}
Literal::Boolean(b) => {
let val = if b { "1" } else { "0" };
self.emit_variable_assignment(
Cow::from("returnValue"),
&VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
Cow::from(val.to_string()),
)?;
}
_ => {}
},
Expression::Binary(bin_expr) => {
let result = self.expression_binary(bin_expr, scope)?;
let result_reg = self.resolve_register(&result.location)?;
self.write_output(format!(
"move r{} {}",
VariableScope::RETURN_REGISTER,
result_reg
))?;
if let Some(name) = result.temp_name {
scope.free_temp(name, None)?;
}
}
Expression::Logical(log_expr) => {
let result = self.expression_logical(log_expr, scope)?;
let result_reg = self.resolve_register(&result.location)?;
self.write_output(format!(
"move r{} {}",
VariableScope::RETURN_REGISTER,
result_reg
))?;
if let Some(name) = result.temp_name {
scope.free_temp(name, None)?;
}
}
Expression::MemberAccess(access) => {
// Return result of member access
let res_opt = self.expression(
Spanned {
node: Expression::MemberAccess(access),
span: expr.span,
},
scope,
)?; )?;
if let Some(res) = res_opt { return Ok(loc);
let reg = self.resolve_register(&res.location)?; };
self.write_output(format!("move r{} {}", VariableScope::RETURN_REGISTER, reg))?;
if let Some(temp) = res.temp_name { match expr.node {
scope.free_temp(temp, None)?; Expression::Variable(var_name) => {
match scope.get_location_of(&var_name.node, Some(var_name.span)) {
Ok(loc) => match loc {
VariableLocation::Temporary(reg)
| VariableLocation::Persistant(reg) => {
self.write_output(format!(
"move r{} r{reg} {}",
VariableScope::RETURN_REGISTER,
debug!(self, "#returnValue")
))?;
}
VariableLocation::Constant(lit) => {
let str = extract_literal(lit, false)?;
self.write_output(format!(
"move r{} {str} {}",
VariableScope::RETURN_REGISTER,
debug!(self, "#returnValue")
))?
}
VariableLocation::Stack(offset) => {
self.write_output(format!(
"sub r{} sp {offset}",
VariableScope::TEMP_STACK_REGISTER
))?;
self.write_output(format!(
"get r{} db r{}",
VariableScope::RETURN_REGISTER,
VariableScope::TEMP_STACK_REGISTER
))?;
}
VariableLocation::Device(_) => {
return Err(Error::Unknown(
"You can not return a device from a function.".into(),
Some(var_name.span),
));
}
},
Err(_) => {
self.errors.push(Error::UnknownIdentifier(
var_name.node.clone(),
var_name.span,
));
// Proceed with dummy
}
} }
} }
Expression::Literal(spanned_lit) => match spanned_lit.node {
Literal::Number(num) => {
self.emit_variable_assignment(
Cow::from("returnValue"),
&VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
Cow::from(num.to_string()),
)?;
}
Literal::Boolean(b) => {
let val = if b { "1" } else { "0" };
self.emit_variable_assignment(
Cow::from("returnValue"),
&VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
Cow::from(val.to_string()),
)?;
}
_ => {}
},
Expression::Binary(bin_expr) => {
let result = self.expression_binary(bin_expr, scope)?;
let result_reg = self.resolve_register(&result.location)?;
self.write_output(format!(
"move r{} {}",
VariableScope::RETURN_REGISTER,
result_reg
))?;
if let Some(name) = result.temp_name {
scope.free_temp(name, None)?;
}
}
Expression::Logical(log_expr) => {
let result = self.expression_logical(log_expr, scope)?;
let result_reg = self.resolve_register(&result.location)?;
self.write_output(format!(
"move r{} {}",
VariableScope::RETURN_REGISTER,
result_reg
))?;
if let Some(name) = result.temp_name {
scope.free_temp(name, None)?;
}
}
Expression::MemberAccess(access) => {
// Return result of member access
let res_opt = self.expression(
Spanned {
node: Expression::MemberAccess(access),
span: expr.span,
},
scope,
)?;
if let Some(res) = res_opt {
let reg = self.resolve_register(&res.location)?;
self.write_output(format!(
"move r{} {}",
VariableScope::RETURN_REGISTER,
reg
))?;
if let Some(temp) = res.temp_name {
scope.free_temp(temp, None)?;
}
}
}
_ => {
return Err(Error::Unknown(
format!("Unsupported `return` statement: {:?}", expr),
None,
));
}
} }
_ => { }
return Err(Error::Unknown(
format!("Unsupported `return` statement: {:?}", expr), if let Some(label) = &self.current_return_label {
None, self.write_output(format!("j {}", label))?;
)); } else {
} return Err(Error::Unknown(
"Return statement used outside of function context.".into(),
None,
));
} }
Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER)) Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER))
@@ -1952,6 +2032,55 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
temp_name: None, temp_name: None,
})) }))
} }
System::LoadSlot(dev_name, slot_index, logic_type) => {
let (dev_hash, hash_cleanup) =
self.compile_literal_or_variable(dev_name.node, scope)?;
let (slot_index, slot_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(slot_index.node),
scope,
)?;
let (logic_type, logic_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(logic_type.node),
scope,
)?;
self.write_output(format!(
"ls r{} {} {} {}",
VariableScope::RETURN_REGISTER,
dev_hash,
slot_index,
logic_type
))?;
cleanup!(hash_cleanup, slot_cleanup, logic_cleanup);
Ok(Some(CompilationResult {
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
temp_name: None,
}))
}
System::SetSlot(dev_name, slot_index, logic_type, var) => {
let (dev_name, name_cleanup) =
self.compile_literal_or_variable(dev_name.node, scope)?;
let (slot_index, index_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(slot_index.node),
scope,
)?;
let (logic_type, type_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(logic_type.node),
scope,
)?;
let (var, var_cleanup) = self.compile_operand(*var, scope)?;
self.write_output(format!(
"ss {} {} {} {}",
dev_name, slot_index, logic_type, var
))?;
cleanup!(name_cleanup, index_cleanup, type_cleanup, var_cleanup);
Ok(None)
}
} }
} }
@@ -2236,8 +2365,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
} }
self.write_output("push ra")?; self.write_output("push ra")?;
let return_label = self.next_label_name();
let prev_return_label = self.current_return_label.replace(return_label.clone());
block_scope.add_variable( block_scope.add_variable(
Cow::from(format!("{}_ra", name.node)), return_label.clone(),
LocationRequest::Stack, LocationRequest::Stack,
Some(name.span), Some(name.span),
)?; )?;
@@ -2245,7 +2379,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
for expr in body.0 { for expr in body.0 {
match expr.node { match expr.node {
Expression::Return(ret_expr) => { Expression::Return(ret_expr) => {
self.expression_return(*ret_expr, &mut block_scope)?; self.expression_return(ret_expr, &mut block_scope)?;
} }
_ => { _ => {
// Swallow internal errors // Swallow internal errors
@@ -2264,11 +2398,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
} }
// Get the saved return address and save it back into `ra` // Get the saved return address and save it back into `ra`
let ra_res = let ra_res = block_scope.get_location_of(&return_label, Some(name.span));
block_scope.get_location_of(&Cow::from(format!("{}_ra", name.node)), Some(name.span));
let ra_stack_offset = match ra_res { let ra_stack_offset = match ra_res {
Ok(VariableLocation::Stack(offset)) => offset, Ok(VariableLocation::Stack(offset)) => {
block_scope.free_temp(return_label.clone(), None)?;
offset
}
_ => { _ => {
// If we can't find RA, we can't return properly. // If we can't find RA, we can't return properly.
// This usually implies a compiler bug or scope tracking error. // This usually implies a compiler bug or scope tracking error.
@@ -2279,6 +2415,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
} }
}; };
self.current_return_label = prev_return_label;
self.write_output(format!("{}:", return_label))?;
self.write_output(format!( self.write_output(format!(
"sub r{0} sp {ra_stack_offset}", "sub r{0} sp {ra_stack_offset}",
VariableScope::TEMP_STACK_REGISTER VariableScope::TEMP_STACK_REGISTER

View File

@@ -52,7 +52,7 @@ pub enum LocationRequest {
Stack, Stack,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum VariableLocation<'a> { pub enum VariableLocation<'a> {
/// Represents a temporary register (r1 - r7) /// Represents a temporary register (r1 - r7)
Temporary(u8), Temporary(u8),
@@ -66,7 +66,6 @@ pub enum VariableLocation<'a> {
Device(Cow<'a, str>), Device(Cow<'a, str>),
} }
// FIX: Added 'b lifetime for the parent reference
pub struct VariableScope<'a, 'b> { pub struct VariableScope<'a, 'b> {
temporary_vars: VecDeque<u8>, temporary_vars: VecDeque<u8>,
persistant_vars: VecDeque<u8>, persistant_vars: VecDeque<u8>,
@@ -75,7 +74,6 @@ pub struct VariableScope<'a, 'b> {
parent: Option<&'b VariableScope<'a, 'b>>, parent: Option<&'b VariableScope<'a, 'b>>,
} }
// FIX: Updated Default impl to include 'b
impl<'a, 'b> Default for VariableScope<'a, 'b> { impl<'a, 'b> Default for VariableScope<'a, 'b> {
fn default() -> Self { fn default() -> Self {
Self { Self {
@@ -88,7 +86,6 @@ impl<'a, 'b> Default for VariableScope<'a, 'b> {
} }
} }
// FIX: Updated impl block to include 'b
impl<'a, 'b> VariableScope<'a, 'b> { impl<'a, 'b> VariableScope<'a, 'b> {
#[allow(dead_code)] #[allow(dead_code)]
pub const TEMP_REGISTER_COUNT: u8 = 7; pub const TEMP_REGISTER_COUNT: u8 = 7;
@@ -97,22 +94,23 @@ impl<'a, 'b> VariableScope<'a, 'b> {
pub const RETURN_REGISTER: u8 = 15; pub const RETURN_REGISTER: u8 = 15;
pub const TEMP_STACK_REGISTER: u8 = 0; pub const TEMP_STACK_REGISTER: u8 = 0;
pub fn registers(&self) -> impl Iterator<Item = &u8> { pub fn registers(&self) -> Vec<u8> {
self.var_lookup_table let mut used = Vec::new();
.values()
.filter(|val| { for r in TEMP {
matches!( if !self.temporary_vars.contains(&r) {
val, used.push(r);
VariableLocation::Temporary(_) | VariableLocation::Persistant(_) }
) }
})
.map(|loc| match loc { for r in PERSIST {
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => reg, if !self.persistant_vars.contains(&r) {
_ => unreachable!(), used.push(r);
}) }
}
used
} }
// FIX: parent is now &'b VariableScope<'a, 'b>
pub fn scoped(parent: &'b VariableScope<'a, 'b>) -> Self { pub fn scoped(parent: &'b VariableScope<'a, 'b>) -> Self {
Self { Self {
parent: Option::Some(parent), parent: Option::Some(parent),

View File

@@ -9,9 +9,11 @@ macro_rules! with_syscalls {
"load", "load",
"loadBatched", "loadBatched",
"loadBatchedNamed", "loadBatchedNamed",
"loadSlot",
"set", "set",
"setBatched", "setBatched",
"setBatchedNamed", "setBatchedNamed",
"setSlot",
"acos", "acos",
"asin", "asin",
"atan", "atan",
@@ -32,9 +34,11 @@ macro_rules! with_syscalls {
"l", "l",
"lb", "lb",
"lbn", "lbn",
"ls",
"s", "s",
"sb", "sb",
"sbn" "sbn",
"ss"
); );
}; };
} }

View File

@@ -293,12 +293,12 @@ impl<'a> Parser<'a> {
// Handle Infix operators (Binary, Logical, Assignment) // Handle Infix operators (Binary, Logical, Assignment)
if self_matches_peek!( if self_matches_peek!(
self, self,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign) TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question)
) { ) {
return Ok(Some(self.infix(lhs)?)); return Ok(Some(self.infix(lhs)?));
} else if self_matches_current!( } else if self_matches_current!(
self, self,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign) TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question)
) { ) {
self.tokenizer.seek(SeekFrom::Current(-1))?; self.tokenizer.seek(SeekFrom::Current(-1))?;
return Ok(Some(self.infix(lhs)?)); return Ok(Some(self.infix(lhs)?));
@@ -645,6 +645,15 @@ impl<'a> Parser<'a> {
.node .node
.ok_or(Error::UnexpectedEOF)?, .ok_or(Error::UnexpectedEOF)?,
TokenType::Identifier(ref id) if SysCall::is_syscall(id) => {
let spanned_call = self.spanned(|p| p.syscall())?;
Spanned {
span: spanned_call.span,
node: Expression::Syscall(spanned_call),
}
}
TokenType::Identifier(_) TokenType::Identifier(_)
if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) =>
{ {
@@ -757,9 +766,11 @@ impl<'a> Parser<'a> {
Expression::Binary(_) Expression::Binary(_)
| Expression::Logical(_) | Expression::Logical(_)
| Expression::Invocation(_) | Expression::Invocation(_)
| Expression::Syscall(_)
| Expression::Priority(_) | Expression::Priority(_)
| Expression::Literal(_) | Expression::Literal(_)
| Expression::Variable(_) | Expression::Variable(_)
| Expression::Ternary(_)
| Expression::Negation(_) | Expression::Negation(_)
| Expression::MemberAccess(_) | Expression::MemberAccess(_)
| Expression::MethodCall(_) => {} | Expression::MethodCall(_) => {}
@@ -779,7 +790,7 @@ impl<'a> Parser<'a> {
// Include Assign in the operator loop // Include Assign in the operator loop
while token_matches!( while token_matches!(
temp_token, temp_token,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign) TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question | Symbol::Colon)
) { ) {
let operator = match temp_token.token_type { let operator = match temp_token.token_type {
TokenType::Symbol(s) => s, TokenType::Symbol(s) => s,
@@ -1010,7 +1021,52 @@ impl<'a> Parser<'a> {
} }
operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr)); operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr));
// --- PRECEDENCE LEVEL 8: Assignment (=) --- // -- PRECEDENCE LEVEL 8: Ternary (x ? 1 : 2)
for i in (0..operators.len()).rev() {
if matches!(operators[i], Symbol::Question) {
// Ensure next operator is a colon
if i + 1 >= operators.len() || !matches!(operators[i + 1], Symbol::Colon) {
return Err(Error::InvalidSyntax(
self.current_span(),
"Ternary operator '?' missing matching ':'".to_string(),
));
}
let false_branch = expressions.remove(i + 2);
let true_branch = expressions.remove(i + 1);
let condition = expressions.remove(i);
let span = Span {
start_line: condition.span.start_line,
end_line: false_branch.span.end_line,
start_col: condition.span.start_col,
end_col: false_branch.span.end_col,
};
let ternary_node = Spanned {
span,
node: TernaryExpression {
condition: Box::new(condition),
true_value: Box::new(true_branch),
false_value: Box::new(false_branch),
},
};
expressions.insert(
i,
Spanned {
node: Expression::Ternary(ternary_node),
span,
},
);
// Remove the `?` and the `:` from the operators list
operators.remove(i);
operators.remove(i);
}
}
// --- PRECEDENCE LEVEL 9: Assignment (=) ---
// Assignment is Right Associative: a = b = c => a = (b = c) // Assignment is Right Associative: a = b = c => a = (b = c)
// We iterate Right to Left // We iterate Right to Left
for (i, operator) in operators.iter().enumerate().rev() { for (i, operator) in operators.iter().enumerate().rev() {
@@ -1050,7 +1106,9 @@ impl<'a> Parser<'a> {
if token_matches!( if token_matches!(
temp_token, temp_token,
TokenType::Symbol(Symbol::Semicolon) | TokenType::Symbol(Symbol::RParen) TokenType::Symbol(Symbol::Semicolon)
| TokenType::Symbol(Symbol::RParen)
| TokenType::Symbol(Symbol::Comma)
) { ) {
self.tokenizer.seek(SeekFrom::Current(-1))?; self.tokenizer.seek(SeekFrom::Current(-1))?;
} }
@@ -1168,18 +1226,34 @@ impl<'a> Parser<'a> {
// Need to capture return span // Need to capture return span
let ret_start_span = Self::token_to_span(&current_token); let ret_start_span = Self::token_to_span(&current_token);
self.assign_next()?; self.assign_next()?;
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
let expr = if token_matches!(
self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?,
TokenType::Symbol(Symbol::Semicolon)
) {
// rewind 1 token so we can check for the semicolon at the bottom of this function.
self.tokenizer.seek(SeekFrom::Current(-1))?;
None
} else {
Some(self.expression()?.ok_or(Error::UnexpectedEOF)?)
};
let ret_span = Span { let ret_span = Span {
start_line: ret_start_span.start_line, start_line: ret_start_span.start_line,
start_col: ret_start_span.start_col, start_col: ret_start_span.start_col,
end_line: expression.span.end_line, end_line: expr
end_col: expression.span.end_col, .as_ref()
.map(|e| e.span.end_line)
.unwrap_or(ret_start_span.end_line),
end_col: expr
.as_ref()
.map(|e| e.span.end_col)
.unwrap_or(ret_start_span.end_col),
}; };
let return_expr = Spanned { let return_expr = Spanned {
span: ret_span, span: ret_span,
node: Expression::Return(boxed!(expression)), node: Expression::Return(expr.map(Box::new)),
}; };
expressions.push(return_expr); expressions.push(return_expr);
@@ -1518,18 +1592,23 @@ impl<'a> Parser<'a> {
} }
fn syscall(&mut self) -> Result<SysCall<'a>, Error<'a>> { fn syscall(&mut self) -> Result<SysCall<'a>, Error<'a>> {
fn check_length<'a>( let invocation = self.invocation()?;
span: Span,
arguments: &[Spanned<Expression<'a>>], let check_length = |len: usize| -> Result<(), Error> {
length: usize, if invocation.arguments.len() != len {
) -> Result<(), Error<'a>> {
if arguments.len() != length {
return Err(Error::InvalidSyntax( return Err(Error::InvalidSyntax(
span, self.current_span(),
format!("Expected {} arguments", length), format!("Expected {} arguments", len),
)); ));
} }
Ok(()) Ok(())
};
macro_rules! args {
($count:expr) => {{
check_length($count)?;
invocation.arguments.into_iter()
}};
} }
macro_rules! literal_or_variable { macro_rules! literal_or_variable {
@@ -1581,23 +1660,19 @@ impl<'a> Parser<'a> {
}; };
} }
let invocation = self.invocation()?;
match invocation.name.node.as_ref() { match invocation.name.node.as_ref() {
// System SysCalls // System SysCalls
"yield" => { "yield" => {
check_length(self.current_span(), &invocation.arguments, 0)?; check_length(0)?;
Ok(SysCall::System(sys_call::System::Yield)) Ok(SysCall::System(sys_call::System::Yield))
} }
"sleep" => { "sleep" => {
check_length(self.current_span(), &invocation.arguments, 1)?; let mut args = args!(1);
let mut arg = invocation.arguments.into_iter(); let expr = args.next().ok_or(Error::UnexpectedEOF)?;
let expr = arg.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::System(System::Sleep(boxed!(expr)))) Ok(SysCall::System(System::Sleep(boxed!(expr))))
} }
"hash" => { "hash" => {
check_length(self.current_span(), &invocation.arguments, 1)?; let mut args = args!(1);
let mut args = invocation.arguments.into_iter();
let lit_str = literal_or_variable!(args.next()); let lit_str = literal_or_variable!(args.next());
let Spanned { let Spanned {
@@ -1617,8 +1692,7 @@ impl<'a> Parser<'a> {
}))) })))
} }
"load" | "l" => { "load" | "l" => {
check_length(self.current_span(), &invocation.arguments, 2)?; let mut args = args!(2);
let mut args = invocation.arguments.into_iter();
let tmp = args.next(); let tmp = args.next();
let device = literal_or_variable!(tmp); let device = literal_or_variable!(tmp);
@@ -1662,8 +1736,7 @@ impl<'a> Parser<'a> {
))) )))
} }
"loadBatched" | "lb" => { "loadBatched" | "lb" => {
check_length(self.current_span(), &invocation.arguments, 3)?; let mut args = args!(3);
let mut args = invocation.arguments.into_iter();
let tmp = args.next(); let tmp = args.next();
let device_hash = literal_or_variable!(tmp); let device_hash = literal_or_variable!(tmp);
@@ -1680,8 +1753,7 @@ impl<'a> Parser<'a> {
))) )))
} }
"loadBatchedNamed" | "lbn" => { "loadBatchedNamed" | "lbn" => {
check_length(self.current_span(), &invocation.arguments, 4)?; let mut args = args!(4);
let mut args = invocation.arguments.into_iter();
let tmp = args.next(); let tmp = args.next();
let dev_hash = literal_or_variable!(tmp); let dev_hash = literal_or_variable!(tmp);
@@ -1699,8 +1771,7 @@ impl<'a> Parser<'a> {
))) )))
} }
"set" | "s" => { "set" | "s" => {
check_length(self.current_span(), &invocation.arguments, 3)?; let mut args = args!(3);
let mut args = invocation.arguments.into_iter();
let tmp = args.next(); let tmp = args.next();
let device = literal_or_variable!(tmp); let device = literal_or_variable!(tmp);
@@ -1720,8 +1791,7 @@ impl<'a> Parser<'a> {
))) )))
} }
"setBatched" | "sb" => { "setBatched" | "sb" => {
check_length(self.current_span(), &invocation.arguments, 3)?; let mut args = args!(3);
let mut args = invocation.arguments.into_iter();
let tmp = args.next(); let tmp = args.next();
let device_hash = literal_or_variable!(tmp); let device_hash = literal_or_variable!(tmp);
@@ -1739,8 +1809,7 @@ impl<'a> Parser<'a> {
))) )))
} }
"setBatchedNamed" | "sbn" => { "setBatchedNamed" | "sbn" => {
check_length(self.current_span(), &invocation.arguments, 4)?; let mut args = args!(4);
let mut args = invocation.arguments.into_iter();
let tmp = args.next(); let tmp = args.next();
let device_hash = literal_or_variable!(tmp); let device_hash = literal_or_variable!(tmp);
@@ -1760,30 +1829,110 @@ impl<'a> Parser<'a> {
expr, expr,
))) )))
} }
"loadSlot" | "ls" => {
let mut args = args!(3);
let next = args.next();
let dev_name = literal_or_variable!(next);
let next = args.next();
let slot_index = get_arg!(Literal, literal_or_variable!(next));
if !matches!(
slot_index,
Spanned {
node: Literal::Number(_),
..
},
) {
return Err(Error::InvalidSyntax(
slot_index.span,
"Expected a number".to_string(),
));
}
let next = args.next();
let slot_logic = get_arg!(Literal, literal_or_variable!(next));
if !matches!(
slot_logic,
Spanned {
node: Literal::String(_),
..
}
) {
return Err(Error::InvalidSyntax(
slot_logic.span,
"Expected a String".into(),
));
}
Ok(SysCall::System(System::LoadSlot(
dev_name, slot_index, slot_logic,
)))
}
"setSlot" | "ss" => {
let mut args = args!(4);
let next = args.next();
let dev_name = literal_or_variable!(next);
let next = args.next();
let slot_index = get_arg!(Literal, literal_or_variable!(next));
if !matches!(
slot_index,
Spanned {
node: Literal::Number(_),
..
}
) {
return Err(Error::InvalidSyntax(
slot_index.span,
"Expected a number".into(),
));
}
let next = args.next();
let slot_logic = get_arg!(Literal, literal_or_variable!(next));
if !matches!(
slot_logic,
Spanned {
node: Literal::String(_),
..
}
) {
return Err(Error::InvalidSyntax(
slot_logic.span,
"Expected a string".into(),
));
}
let next = args.next();
let expr = next.ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::System(System::SetSlot(
dev_name,
slot_index,
slot_logic,
Box::new(expr),
)))
}
// Math SysCalls // Math SysCalls
"acos" => { "acos" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let tmp = args.next().ok_or(Error::UnexpectedEOF)?; let tmp = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Acos(boxed!(tmp)))) Ok(SysCall::Math(Math::Acos(boxed!(tmp))))
} }
"asin" => { "asin" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let tmp = args.next().ok_or(Error::UnexpectedEOF)?; let tmp = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Asin(boxed!(tmp)))) Ok(SysCall::Math(Math::Asin(boxed!(tmp))))
} }
"atan" => { "atan" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let expr = args.next().ok_or(Error::UnexpectedEOF)?; let expr = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Atan(boxed!(expr)))) Ok(SysCall::Math(Math::Atan(boxed!(expr))))
} }
"atan2" => { "atan2" => {
check_length(self.current_span(), &invocation.arguments, 2)?; check_length(2)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg1 = args.next().ok_or(Error::UnexpectedEOF)?; let arg1 = args.next().ok_or(Error::UnexpectedEOF)?;
let arg2 = args.next().ok_or(Error::UnexpectedEOF)?; let arg2 = args.next().ok_or(Error::UnexpectedEOF)?;
@@ -1791,42 +1940,42 @@ impl<'a> Parser<'a> {
Ok(SysCall::Math(Math::Atan2(boxed!(arg1), boxed!(arg2)))) Ok(SysCall::Math(Math::Atan2(boxed!(arg1), boxed!(arg2))))
} }
"abs" => { "abs" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let expr = args.next().ok_or(Error::UnexpectedEOF)?; let expr = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Abs(boxed!(expr)))) Ok(SysCall::Math(Math::Abs(boxed!(expr))))
} }
"ceil" => { "ceil" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Ceil(boxed!(arg)))) Ok(SysCall::Math(Math::Ceil(boxed!(arg))))
} }
"cos" => { "cos" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Cos(boxed!(arg)))) Ok(SysCall::Math(Math::Cos(boxed!(arg))))
} }
"floor" => { "floor" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Floor(boxed!(arg)))) Ok(SysCall::Math(Math::Floor(boxed!(arg))))
} }
"log" => { "log" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Log(boxed!(arg)))) Ok(SysCall::Math(Math::Log(boxed!(arg))))
} }
"max" => { "max" => {
check_length(self.current_span(), &invocation.arguments, 2)?; check_length(2)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg1 = args.next().ok_or(Error::UnexpectedEOF)?; let arg1 = args.next().ok_or(Error::UnexpectedEOF)?;
let arg2 = args.next().ok_or(Error::UnexpectedEOF)?; let arg2 = args.next().ok_or(Error::UnexpectedEOF)?;
@@ -1834,7 +1983,7 @@ impl<'a> Parser<'a> {
Ok(SysCall::Math(Math::Max(boxed!(arg1), boxed!(arg2)))) Ok(SysCall::Math(Math::Max(boxed!(arg1), boxed!(arg2))))
} }
"min" => { "min" => {
check_length(self.current_span(), &invocation.arguments, 2)?; check_length(2)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg1 = args.next().ok_or(Error::UnexpectedEOF)?; let arg1 = args.next().ok_or(Error::UnexpectedEOF)?;
let arg2 = args.next().ok_or(Error::UnexpectedEOF)?; let arg2 = args.next().ok_or(Error::UnexpectedEOF)?;
@@ -1842,32 +1991,32 @@ impl<'a> Parser<'a> {
Ok(SysCall::Math(Math::Min(boxed!(arg1), boxed!(arg2)))) Ok(SysCall::Math(Math::Min(boxed!(arg1), boxed!(arg2))))
} }
"rand" => { "rand" => {
check_length(self.current_span(), &invocation.arguments, 0)?; check_length(0)?;
Ok(SysCall::Math(Math::Rand)) Ok(SysCall::Math(Math::Rand))
} }
"sin" => { "sin" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Sin(boxed!(arg)))) Ok(SysCall::Math(Math::Sin(boxed!(arg))))
} }
"sqrt" => { "sqrt" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Sqrt(boxed!(arg)))) Ok(SysCall::Math(Math::Sqrt(boxed!(arg))))
} }
"tan" => { "tan" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Tan(boxed!(arg)))) Ok(SysCall::Math(Math::Tan(boxed!(arg))))
} }
"trunc" => { "trunc" => {
check_length(self.current_span(), &invocation.arguments, 1)?; check_length(1)?;
let mut args = invocation.arguments.into_iter(); let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?; let arg = args.next().ok_or(Error::UnexpectedEOF)?;

View File

@@ -214,6 +214,30 @@ documented! {
Spanned<Literal<'a>>, Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>,
), ),
/// Loads slot LogicSlotType from device into a variable
///
/// ## IC10
/// `ls r0 d0 2 Occupied`
/// ## Slang
/// `let isOccupied = loadSlot(deviceHash, 2, "Occupied");`
/// `let isOccupied = ls(deviceHash, 2, "Occupied");`
LoadSlot(
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Spanned<Literal<'a>>
),
/// Stores a value of LogicType on a device by the index value
/// ## IC10
/// `ss d0 0 "Open" 1`
/// ## Slang
/// `setSlot(deviceHash, 0, "Open", true);`
/// `ss(deviceHash, 0, "Open", true);`
SetSlot(
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>>
)
} }
} }
@@ -235,6 +259,8 @@ impl<'a> std::fmt::Display for System<'a> {
System::SetOnDeviceBatchedNamed(a, b, c, d) => { System::SetOnDeviceBatchedNamed(a, b, c, d) => {
write!(f, "setOnDeviceBatchedNamed({}, {}, {}, {})", a, b, c, d) write!(f, "setOnDeviceBatchedNamed({}, {}, {}, {})", a, b, c, d)
} }
System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c),
System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
} }
} }
} }

View File

@@ -160,3 +160,37 @@ fn test_negative_literal_const() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_ternary_expression() -> Result<()> {
let expr = parser!(r#"let i = x ? 1 : 2;"#).parse()?.unwrap();
assert_eq!("(let i = (x ? 1 : 2))", expr.to_string());
Ok(())
}
#[test]
fn test_complex_binary_with_ternary() -> Result<()> {
let expr = parser!("let i = (x ? 1 : 3) * 2;").parse()?.unwrap();
assert_eq!("(let i = ((x ? 1 : 3) * 2))", expr.to_string());
Ok(())
}
#[test]
fn test_operator_prescedence_with_ternary() -> Result<()> {
let expr = parser!("let x = x ? 1 : 3 * 2;").parse()?.unwrap();
assert_eq!("(let x = (x ? 1 : (3 * 2)))", expr.to_string());
Ok(())
}
#[test]
fn test_nested_ternary_right_associativity() -> Result<()> {
let expr = parser!("let i = a ? b : c ? d : e;").parse()?.unwrap();
assert_eq!("(let i = (a ? b : (c ? d : e)))", expr.to_string());
Ok(())
}

View File

@@ -277,6 +277,23 @@ pub struct WhileExpression<'a> {
pub body: BlockExpression<'a>, pub body: BlockExpression<'a>,
} }
#[derive(Debug, PartialEq, Eq)]
pub struct TernaryExpression<'a> {
pub condition: Box<Spanned<Expression<'a>>>,
pub true_value: Box<Spanned<Expression<'a>>>,
pub false_value: Box<Spanned<Expression<'a>>>,
}
impl<'a> std::fmt::Display for TernaryExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"({} ? {} : {})",
self.condition, self.true_value, self.false_value
)
}
}
impl<'a> std::fmt::Display for WhileExpression<'a> { impl<'a> std::fmt::Display for WhileExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(while {} {})", self.condition, self.body) write!(f, "(while {} {})", self.condition, self.body)
@@ -364,8 +381,9 @@ pub enum Expression<'a> {
MethodCall(Spanned<MethodCallExpression<'a>>), MethodCall(Spanned<MethodCallExpression<'a>>),
Negation(Box<Spanned<Expression<'a>>>), Negation(Box<Spanned<Expression<'a>>>),
Priority(Box<Spanned<Expression<'a>>>), Priority(Box<Spanned<Expression<'a>>>),
Return(Box<Spanned<Expression<'a>>>), Return(Option<Box<Spanned<Expression<'a>>>>),
Syscall(Spanned<SysCall<'a>>), Syscall(Spanned<SysCall<'a>>),
Ternary(Spanned<TernaryExpression<'a>>),
Variable(Spanned<Cow<'a, str>>), Variable(Spanned<Cow<'a, str>>),
While(Spanned<WhileExpression<'a>>), While(Spanned<WhileExpression<'a>>),
} }
@@ -391,8 +409,17 @@ impl<'a> std::fmt::Display for Expression<'a> {
Expression::MethodCall(e) => write!(f, "{}", e), Expression::MethodCall(e) => write!(f, "{}", e),
Expression::Negation(e) => write!(f, "(-{})", e), Expression::Negation(e) => write!(f, "(-{})", e),
Expression::Priority(e) => write!(f, "({})", e), Expression::Priority(e) => write!(f, "({})", e),
Expression::Return(e) => write!(f, "(return {})", e), Expression::Return(e) => write!(
f,
"(return {})",
if let Some(e) = e {
e.to_string()
} else {
"".to_string()
}
),
Expression::Syscall(e) => write!(f, "{}", e), Expression::Syscall(e) => write!(f, "{}", e),
Expression::Ternary(e) => write!(f, "{}", e),
Expression::Variable(id) => write!(f, "{}", id), Expression::Variable(id) => write!(f, "{}", id),
Expression::While(e) => write!(f, "{}", e), Expression::While(e) => write!(f, "{}", e),
} }

View File

@@ -39,7 +39,7 @@ impl From<LexError> for Diagnostic {
..Default::default() ..Default::default()
} }
} }
_ => todo!(), _ => Diagnostic::default(),
} }
} }
} }
@@ -225,6 +225,7 @@ pub enum TokenType<'a> {
#[token(".", symbol!(Dot))] #[token(".", symbol!(Dot))]
#[token("^", symbol!(Caret))] #[token("^", symbol!(Caret))]
#[token("%", symbol!(Percent))] #[token("%", symbol!(Percent))]
#[token("?", symbol!(Question))]
#[token("==", symbol!(Equal))] #[token("==", symbol!(Equal))]
#[token("!=", symbol!(NotEqual))] #[token("!=", symbol!(NotEqual))]
#[token("&&", symbol!(LogicalAnd))] #[token("&&", symbol!(LogicalAnd))]
@@ -535,6 +536,8 @@ pub enum Symbol {
Caret, Caret,
/// Represents the `%` symbol /// Represents the `%` symbol
Percent, Percent,
/// Represents the `?` symbol
Question,
// Double Character Symbols // Double Character Symbols
/// Represents the `==` symbol /// Represents the `==` symbol
@@ -601,6 +604,7 @@ impl std::fmt::Display for Symbol {
Self::Asterisk => write!(f, "*"), Self::Asterisk => write!(f, "*"),
Self::Slash => write!(f, "/"), Self::Slash => write!(f, "/"),
Self::LessThan => write!(f, "<"), Self::LessThan => write!(f, "<"),
Self::Question => write!(f, "?"),
Self::LessThanOrEqual => write!(f, "<="), Self::LessThanOrEqual => write!(f, "<="),
Self::GreaterThan => write!(f, ">"), Self::GreaterThan => write!(f, ">"),
Self::GreaterThanOrEqual => write!(f, ">="), Self::GreaterThanOrEqual => write!(f, ">="),