Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
2b26d0d278
|
|||
|
236b50c813
|
|||
|
342b1ab107
|
|||
| 0732f68bcf | |||
|
0ac010ef8f
|
|||
|
c2208fbb15
|
|||
| 295f062797 | |||
|
9c260ef2d5
|
|||
|
0fde11a2bf
|
|||
|
b21d6cc73e
|
|||
| f19801d4e6 |
23
Changelog.md
23
Changelog.md
@@ -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`
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -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))]
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
2
rust_compiler/Cargo.lock
generated
2
rust_compiler/Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "slang"
|
name = "slang"
|
||||||
version = "0.2.0"
|
version = "0.2.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
return arg1 * 2;
|
||||||
|
};
|
||||||
|
loop {
|
||||||
let arg1 = 123;
|
let arg1 = 123;
|
||||||
let i = doSomething(arg1);
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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! {
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,9 +1627,10 @@ 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 Some(expr) = expr {
|
||||||
if let Expression::Negation(neg_expr) = &expr.node
|
if let Expression::Negation(neg_expr) = &expr.node
|
||||||
&& let Expression::Literal(spanned_lit) = &neg_expr.node
|
&& let Expression::Literal(spanned_lit) = &neg_expr.node
|
||||||
&& let Literal::Number(neg_num) = &spanned_lit.node
|
&& let Literal::Number(neg_num) = &spanned_lit.node
|
||||||
@@ -1583,7 +1648,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
|
|||||||
Expression::Variable(var_name) => {
|
Expression::Variable(var_name) => {
|
||||||
match scope.get_location_of(&var_name.node, Some(var_name.span)) {
|
match scope.get_location_of(&var_name.node, Some(var_name.span)) {
|
||||||
Ok(loc) => match loc {
|
Ok(loc) => match loc {
|
||||||
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
|
VariableLocation::Temporary(reg)
|
||||||
|
| VariableLocation::Persistant(reg) => {
|
||||||
self.write_output(format!(
|
self.write_output(format!(
|
||||||
"move r{} r{reg} {}",
|
"move r{} r{reg} {}",
|
||||||
VariableScope::RETURN_REGISTER,
|
VariableScope::RETURN_REGISTER,
|
||||||
@@ -1678,7 +1744,11 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
|
|||||||
)?;
|
)?;
|
||||||
if let Some(res) = res_opt {
|
if let Some(res) = res_opt {
|
||||||
let reg = self.resolve_register(&res.location)?;
|
let reg = self.resolve_register(&res.location)?;
|
||||||
self.write_output(format!("move r{} {}", VariableScope::RETURN_REGISTER, reg))?;
|
self.write_output(format!(
|
||||||
|
"move r{} {}",
|
||||||
|
VariableScope::RETURN_REGISTER,
|
||||||
|
reg
|
||||||
|
))?;
|
||||||
if let Some(temp) = res.temp_name {
|
if let Some(temp) = res.temp_name {
|
||||||
scope.free_temp(temp, None)?;
|
scope.free_temp(temp, None)?;
|
||||||
}
|
}
|
||||||
@@ -1691,6 +1761,16 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(label) = &self.current_return_label {
|
||||||
|
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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(¤t_token);
|
let ret_start_span = Self::token_to_span(¤t_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)?;
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, ">="),
|
||||||
|
|||||||
Reference in New Issue
Block a user