Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
88b6571659
|
|||
|
477c2b1aef
|
|||
| 941e81a3e5 | |||
|
b98817c8a0
|
|||
|
6d5c179eac
|
|||
|
b7fbc499b6
|
|||
| 30b564a153 | |||
|
415e69628d
|
14
Changelog.md
14
Changelog.md
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
[0.3.3]
|
||||
|
||||
- Fixed bug where negative temperature literals were converted to Kelvin
|
||||
first before applying the negative
|
||||
|
||||
[0.3.2]
|
||||
|
||||
- Fixed stack overflow due to incorrect optimization of 'leaf' functions
|
||||
|
||||
[0.3.1]
|
||||
|
||||
- Fixed possible `KeyNotFoundException` in C# code due to invalid
|
||||
dictionary access when an IC housing has an error
|
||||
|
||||
[0.3.0]
|
||||
|
||||
- Implemented a multi-pass optimizer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Slang</Name>
|
||||
<Author>JoeDiertay</Author>
|
||||
<Version>0.3.0</Version>
|
||||
<Version>0.3.3</Version>
|
||||
<Description>
|
||||
[h1]Slang: High-Level Programming for Stationeers[/h1]
|
||||
|
||||
|
||||
@@ -56,6 +56,11 @@ public static class GlobalCode
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sourceMaps[reference].ContainsKey(icErrorLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var foundRange = sourceMaps[reference][icErrorLine];
|
||||
|
||||
if (foundRange is null)
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Slang
|
||||
{
|
||||
public const string PluginGuid = "com.biddydev.slang";
|
||||
public const string PluginName = "Slang";
|
||||
public const string PluginVersion = "0.3.0";
|
||||
public const string PluginVersion = "0.3.3";
|
||||
|
||||
public static Mod MOD = new Mod(PluginName, PluginVersion);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>StationeersSlang</AssemblyName>
|
||||
<Description>Slang Compiler Bridge</Description>
|
||||
<Version>0.3.0</Version>
|
||||
<Version>0.3.2</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
2
rust_compiler/Cargo.lock
generated
2
rust_compiler/Cargo.lock
generated
@@ -930,7 +930,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "slang"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "slang"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
@@ -52,8 +52,8 @@ fn nested_binary_expressions() -> Result<()> {
|
||||
add r1 r10 r9
|
||||
mul r2 r1 r8
|
||||
move r15 r2
|
||||
j L1
|
||||
L1:
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
@@ -175,3 +175,52 @@ fn test_ternary_expression_assignment() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_literals() -> Result<()> {
|
||||
let compiled = compile!(
|
||||
debug
|
||||
r#"
|
||||
let item = -10c - 20c;
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 243.15
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mismatched_temperature_literals() -> Result<()> {
|
||||
let compiled = compile!(
|
||||
debug
|
||||
r#"
|
||||
let item = -10c - 100k;
|
||||
let item2 = item + 500c;
|
||||
"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 163.15
|
||||
add r1 r8 773.15
|
||||
move r9 r1
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ fn test_if_statement() -> anyhow::Result<()> {
|
||||
main:
|
||||
move r8 10
|
||||
sgt r1 r8 5
|
||||
beqz r1 L1
|
||||
beqz r1 __internal_L1
|
||||
move r8 20
|
||||
L1:
|
||||
__internal_L1:
|
||||
"
|
||||
}
|
||||
);
|
||||
@@ -54,12 +54,12 @@ fn test_if_else_statement() -> anyhow::Result<()> {
|
||||
main:
|
||||
move r8 0
|
||||
sgt r1 10 5
|
||||
beqz r1 L2
|
||||
beqz r1 __internal_L2
|
||||
move r8 1
|
||||
j L1
|
||||
L2:
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
move r8 2
|
||||
L1:
|
||||
__internal_L1:
|
||||
"
|
||||
}
|
||||
);
|
||||
@@ -91,18 +91,18 @@ fn test_if_else_if_statement() -> anyhow::Result<()> {
|
||||
main:
|
||||
move r8 0
|
||||
seq r1 r8 1
|
||||
beqz r1 L2
|
||||
beqz r1 __internal_L2
|
||||
move r8 10
|
||||
j L1
|
||||
L2:
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
seq r2 r8 2
|
||||
beqz r2 L4
|
||||
beqz r2 __internal_L4
|
||||
move r8 20
|
||||
j L3
|
||||
L4:
|
||||
j __internal_L3
|
||||
__internal_L4:
|
||||
move r8 30
|
||||
L3:
|
||||
L1:
|
||||
__internal_L3:
|
||||
__internal_L1:
|
||||
"
|
||||
}
|
||||
);
|
||||
@@ -145,10 +145,10 @@ fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
|
||||
move r14 7
|
||||
push 8
|
||||
seq r1 r8 1
|
||||
beqz r1 L1
|
||||
beqz r1 __internal_L1
|
||||
sub r0 sp 1
|
||||
put db r0 99
|
||||
L1:
|
||||
__internal_L1:
|
||||
sub sp sp 1
|
||||
"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ fn no_arguments() -> anyhow::Result<()> {
|
||||
j main
|
||||
doSomething:
|
||||
push ra
|
||||
L1:
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
@@ -59,14 +59,14 @@ fn let_var_args() -> anyhow::Result<()> {
|
||||
push ra
|
||||
mul r1 r8 2
|
||||
move r15 r1
|
||||
j L1
|
||||
L1:
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
j ra
|
||||
main:
|
||||
L2:
|
||||
__internal_L2:
|
||||
move r8 123
|
||||
push r8
|
||||
push r8
|
||||
@@ -77,8 +77,8 @@ fn let_var_args() -> anyhow::Result<()> {
|
||||
move r9 r15
|
||||
pow r1 r9 2
|
||||
move r9 r1
|
||||
j L2
|
||||
L3:
|
||||
j __internal_L2
|
||||
__internal_L3:
|
||||
"
|
||||
}
|
||||
);
|
||||
@@ -127,8 +127,8 @@ fn inline_literal_args() -> anyhow::Result<()> {
|
||||
pop r9
|
||||
push ra
|
||||
move r15 5
|
||||
j L1
|
||||
L1:
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
@@ -170,7 +170,7 @@ fn mixed_args() -> anyhow::Result<()> {
|
||||
pop r8
|
||||
pop r9
|
||||
push ra
|
||||
L1:
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
@@ -214,8 +214,8 @@ fn with_return_statement() -> anyhow::Result<()> {
|
||||
pop r8
|
||||
push ra
|
||||
move r15 456
|
||||
j L1
|
||||
L1:
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
@@ -251,7 +251,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
|
||||
doSomething:
|
||||
push ra
|
||||
move r15 -1
|
||||
L1:
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
|
||||
@@ -133,8 +133,8 @@ fn test_boolean_return() -> anyhow::Result<()> {
|
||||
getTrue:
|
||||
push ra
|
||||
move r15 1
|
||||
j L1
|
||||
L1:
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
|
||||
@@ -21,7 +21,7 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
|
||||
pop r13
|
||||
pop r14
|
||||
push ra
|
||||
L1:
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 3
|
||||
@@ -54,12 +54,12 @@ fn test_early_return() -> anyhow::Result<()> {
|
||||
doSomething:
|
||||
push ra
|
||||
seq r1 1 1
|
||||
beqz r1 L2
|
||||
j L1
|
||||
L2:
|
||||
beqz r1 __internal_L2
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
move r8 3
|
||||
j L1
|
||||
L1:
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
@@ -90,7 +90,7 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
|
||||
pop r8
|
||||
pop r9
|
||||
push ra
|
||||
L1:
|
||||
__internal_L1:
|
||||
sub r0 sp 1
|
||||
get ra db r0
|
||||
sub sp sp 1
|
||||
|
||||
@@ -14,7 +14,7 @@ fn test_infinite_loop() -> anyhow::Result<()> {
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end)
|
||||
// __internal_Labels: L1 (start), L2 (end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
@@ -22,11 +22,11 @@ fn test_infinite_loop() -> anyhow::Result<()> {
|
||||
j main
|
||||
main:
|
||||
move r8 0
|
||||
L1:
|
||||
__internal_L1:
|
||||
add r1 r8 1
|
||||
move r8 r1
|
||||
j L1
|
||||
L2:
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
@@ -49,7 +49,7 @@ fn test_loop_break() -> anyhow::Result<()> {
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end), L3 (if end - implicit else label)
|
||||
// __internal_Labels: L1 (start), L2 (end), L3 (if end - implicit else label)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
@@ -57,15 +57,15 @@ fn test_loop_break() -> anyhow::Result<()> {
|
||||
j main
|
||||
main:
|
||||
move r8 0
|
||||
L1:
|
||||
__internal_L1:
|
||||
add r1 r8 1
|
||||
move r8 r1
|
||||
sgt r2 r8 10
|
||||
beqz r2 L3
|
||||
j L2
|
||||
L3:
|
||||
j L1
|
||||
L2:
|
||||
beqz r2 __internal_L3
|
||||
j __internal_L2
|
||||
__internal_L3:
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
@@ -85,7 +85,7 @@ fn test_while_loop() -> anyhow::Result<()> {
|
||||
"
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end)
|
||||
// __internal_Labels: L1 (start), L2 (end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
@@ -93,13 +93,13 @@ fn test_while_loop() -> anyhow::Result<()> {
|
||||
j main
|
||||
main:
|
||||
move r8 0
|
||||
L1:
|
||||
__internal_L1:
|
||||
slt r1 r8 10
|
||||
beqz r1 L2
|
||||
beqz r1 __internal_L2
|
||||
add r2 r8 1
|
||||
move r8 r2
|
||||
j L1
|
||||
L2:
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
@@ -123,7 +123,7 @@ fn test_loop_continue() -> anyhow::Result<()> {
|
||||
"#
|
||||
};
|
||||
|
||||
// Labels: L1 (start), L2 (end), L3 (if end)
|
||||
// __internal_Labels: L1 (start), L2 (end), L3 (if end)
|
||||
assert_eq!(
|
||||
compiled,
|
||||
indoc! {
|
||||
@@ -131,16 +131,16 @@ fn test_loop_continue() -> anyhow::Result<()> {
|
||||
j main
|
||||
main:
|
||||
move r8 0
|
||||
L1:
|
||||
__internal_L1:
|
||||
add r1 r8 1
|
||||
move r8 r1
|
||||
slt r2 r8 5
|
||||
beqz r2 L3
|
||||
j L1
|
||||
L3:
|
||||
j L2
|
||||
j L1
|
||||
L2:
|
||||
beqz r2 __internal_L3
|
||||
j __internal_L1
|
||||
__internal_L3:
|
||||
j __internal_L2
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ use parser::{
|
||||
use rust_decimal::Decimal;
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
use thiserror::Error;
|
||||
use tokenizer::token::Number;
|
||||
use tokenizer::token::{Number, Unit};
|
||||
|
||||
fn extract_literal<'a>(
|
||||
literal: Literal<'a>,
|
||||
@@ -264,7 +264,7 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
fn next_label_name(&mut self) -> Cow<'a, str> {
|
||||
self.label_counter += 1;
|
||||
Cow::from(format!("L{}", self.label_counter))
|
||||
Cow::from(format!("__internal_L{}", self.label_counter))
|
||||
}
|
||||
|
||||
fn expression(
|
||||
@@ -811,7 +811,7 @@ impl<'a> Compiler<'a> {
|
||||
..
|
||||
})),
|
||||
..
|
||||
}) => Literal::Number(Number::Integer(crc_hash_signed(&str_to_hash))),
|
||||
}) => Literal::Number(Number::Integer(crc_hash_signed(&str_to_hash), Unit::None)),
|
||||
LiteralOr::Or(Spanned { span, .. }) => {
|
||||
return Err(Error::Unknown(
|
||||
"hash only supports string literals in this context.".into(),
|
||||
@@ -2022,6 +2022,7 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
let loc = VariableLocation::Constant(Literal::Number(Number::Integer(
|
||||
crc_hash_signed(&str_lit),
|
||||
Unit::None,
|
||||
)));
|
||||
|
||||
Ok(Some(CompileLocation {
|
||||
|
||||
@@ -14,6 +14,10 @@ pub fn find_leaf_functions(instructions: &[InstructionNode]) -> HashSet<String>
|
||||
for node in instructions {
|
||||
match &node.instruction {
|
||||
Instruction::LabelDef(label) => {
|
||||
if label.starts_with("__internal_L") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we were tracking a function, and it remained a leaf until now, save it.
|
||||
if let Some(name) = current_label.take()
|
||||
&& is_current_leaf
|
||||
|
||||
@@ -99,7 +99,7 @@ fn optimize_leaf_functions<'a>(
|
||||
// First scan: Identify instructions to remove and capture RA offsets
|
||||
for (i, node) in input.iter().enumerate() {
|
||||
match &node.instruction {
|
||||
Instruction::LabelDef(label) => {
|
||||
Instruction::LabelDef(label) if !label.starts_with("__internal_L") => {
|
||||
current_function = Some(label.to_string());
|
||||
function_start_indices.insert(label.to_string(), i);
|
||||
}
|
||||
@@ -174,7 +174,9 @@ fn optimize_leaf_functions<'a>(
|
||||
continue; // SKIP (Remove)
|
||||
}
|
||||
|
||||
if let Instruction::LabelDef(l) = &node.instruction {
|
||||
if let Instruction::LabelDef(l) = &node.instruction
|
||||
&& !l.starts_with("__internal_L")
|
||||
{
|
||||
processing_function = Some(l.to_string());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use tokenizer::Tokenizer;
|
||||
|
||||
use crate::Parser;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokenizer::Tokenizer;
|
||||
|
||||
#[test]
|
||||
fn test_block() -> anyhow::Result<()> {
|
||||
|
||||
@@ -54,10 +54,7 @@ fn test_const_declaration() -> Result<()> {
|
||||
let tokenizer = Tokenizer::from(input);
|
||||
let mut parser = Parser::new(tokenizer);
|
||||
|
||||
assert_eq!(
|
||||
"(const item = 293.15)",
|
||||
parser.parse()?.unwrap().to_string()
|
||||
);
|
||||
assert_eq!("(const item = 20c)", parser.parse()?.unwrap().to_string());
|
||||
|
||||
assert_eq!(
|
||||
"(const decimal = 200.15)",
|
||||
|
||||
@@ -102,48 +102,6 @@ impl<'a> Token<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone)]
|
||||
pub enum Temperature {
|
||||
Celsius(Number),
|
||||
Fahrenheit(Number),
|
||||
Kelvin(Number),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Temperature {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Temperature::Celsius(n) => write!(f, "{}°C", n),
|
||||
Temperature::Fahrenheit(n) => write!(f, "{}°F", n),
|
||||
Temperature::Kelvin(n) => write!(f, "{}°K", n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Temperature {
|
||||
pub fn to_kelvin(self) -> Number {
|
||||
match self {
|
||||
Temperature::Celsius(n) => {
|
||||
let n = match n {
|
||||
Number::Integer(i) => Decimal::new(i as i64, 0),
|
||||
Number::Decimal(d) => d,
|
||||
};
|
||||
Number::Decimal(n + Decimal::new(27315, 2))
|
||||
}
|
||||
Temperature::Fahrenheit(n) => {
|
||||
let n = match n {
|
||||
Number::Integer(i) => Decimal::new(i as i64, 0),
|
||||
Number::Decimal(d) => d,
|
||||
};
|
||||
|
||||
let a = n - Decimal::new(32, 0);
|
||||
let b = Decimal::new(5, 0) / Decimal::new(9, 0);
|
||||
Number::Decimal(a * b + Decimal::new(27315, 2))
|
||||
}
|
||||
Temperature::Kelvin(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! symbol {
|
||||
($var:ident) => {
|
||||
|_| Symbol::$var
|
||||
@@ -280,30 +238,27 @@ fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexE
|
||||
span.end -= lexer.extras.line_start_index;
|
||||
span.start -= lexer.extras.line_start_index;
|
||||
|
||||
let num = if clean_str.contains('.') {
|
||||
Number::Decimal(
|
||||
let unit = match suffix {
|
||||
Some('c') => Unit::Celsius,
|
||||
Some('f') => Unit::Fahrenheit,
|
||||
Some('k') => Unit::Kelvin,
|
||||
_ => Unit::None,
|
||||
};
|
||||
|
||||
if clean_str.contains('.') {
|
||||
Ok(Number::Decimal(
|
||||
clean_str
|
||||
.parse::<Decimal>()
|
||||
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||
)
|
||||
unit,
|
||||
))
|
||||
} else {
|
||||
Number::Integer(
|
||||
Ok(Number::Integer(
|
||||
clean_str
|
||||
.parse::<i128>()
|
||||
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(suffix) = suffix {
|
||||
Ok(match suffix {
|
||||
'c' => Temperature::Celsius(num),
|
||||
'f' => Temperature::Fahrenheit(num),
|
||||
'k' => Temperature::Kelvin(num),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.to_kelvin())
|
||||
} else {
|
||||
Ok(num)
|
||||
unit,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,25 +350,55 @@ impl<'a> std::fmt::Display for TokenType<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
pub enum Unit {
|
||||
None,
|
||||
Celsius,
|
||||
Fahrenheit,
|
||||
Kelvin,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
|
||||
pub enum Number {
|
||||
/// Represents an integer number
|
||||
Integer(i128),
|
||||
Integer(i128, Unit),
|
||||
/// Represents a decimal type number with a precision of 64 bits
|
||||
Decimal(Decimal),
|
||||
Decimal(Decimal, Unit),
|
||||
}
|
||||
|
||||
impl Number {
|
||||
pub fn unit(&self) -> Unit {
|
||||
match self {
|
||||
Number::Integer(_, u) => *u,
|
||||
Number::Decimal(_, u) => *u,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_unit(&self) -> bool {
|
||||
self.unit() != Unit::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Number {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::Integer(if value { 1 } else { 0 })
|
||||
Self::Integer(if value { 1 } else { 0 }, Unit::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Number> for Decimal {
|
||||
fn from(value: Number) -> Self {
|
||||
match value {
|
||||
Number::Decimal(d) => d,
|
||||
Number::Integer(i) => Decimal::from(i),
|
||||
let (val, unit) = match value {
|
||||
Number::Decimal(d, u) => (d, u),
|
||||
Number::Integer(i, u) => (Decimal::from(i), u),
|
||||
};
|
||||
|
||||
match unit {
|
||||
Unit::None | Unit::Kelvin => val,
|
||||
Unit::Celsius => val + Decimal::new(27315, 2),
|
||||
Unit::Fahrenheit => {
|
||||
(val - Decimal::new(32, 0)) * Decimal::new(5, 0) / Decimal::new(9, 0)
|
||||
+ Decimal::new(27315, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -423,22 +408,48 @@ impl std::ops::Neg for Number {
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
match self {
|
||||
Self::Integer(i) => Self::Integer(-i),
|
||||
Self::Decimal(d) => Self::Decimal(-d),
|
||||
Self::Integer(i, u) => Self::Integer(-i, u),
|
||||
Self::Decimal(d, u) => Self::Decimal(-d, u),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_target_unit(lhs_unit: Unit, rhs_unit: Unit) -> Option<Unit> {
|
||||
if lhs_unit == rhs_unit {
|
||||
return Some(lhs_unit);
|
||||
}
|
||||
if lhs_unit != Unit::None && rhs_unit == Unit::None {
|
||||
return Some(lhs_unit);
|
||||
}
|
||||
if lhs_unit == Unit::None && rhs_unit != Unit::None {
|
||||
return Some(rhs_unit);
|
||||
}
|
||||
// Mismatched units (C + F) -> Fallback to Kelvin/None
|
||||
None
|
||||
}
|
||||
|
||||
impl std::ops::Add for Number {
|
||||
type Output = Number;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Self::Integer(l), Self::Integer(r)) => Number::Integer(l + r),
|
||||
(Self::Decimal(l), Self::Decimal(r)) => Number::Decimal(l + r),
|
||||
(Self::Integer(l), Self::Decimal(r)) => Number::Decimal(Decimal::from(l) + r),
|
||||
(Self::Decimal(l), Self::Integer(r)) => Number::Decimal(l + Decimal::from(r)),
|
||||
// If we can determine a common target unit (e.g. C + C = C, or C + Scalar = C),
|
||||
// we preserve that unit. Otherwise, we convert to Kelvin (Decimal) and return Unit::None.
|
||||
if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
|
||||
return match (self, rhs) {
|
||||
(Self::Integer(l, _), Self::Integer(r, _)) => Number::Integer(l + r, target_unit),
|
||||
(Self::Decimal(l, _), Self::Decimal(r, _)) => Number::Decimal(l + r, target_unit),
|
||||
(Self::Integer(l, _), Self::Decimal(r, _)) => {
|
||||
Number::Decimal(Decimal::from(l) + r, target_unit)
|
||||
}
|
||||
(Self::Decimal(l, _), Self::Integer(r, _)) => {
|
||||
Number::Decimal(l + Decimal::from(r), target_unit)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let l: Decimal = self.into();
|
||||
let r: Decimal = rhs.into();
|
||||
Number::Decimal(l + r, Unit::None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,12 +457,22 @@ impl std::ops::Sub for Number {
|
||||
type Output = Number;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Self::Integer(l), Self::Integer(r)) => Self::Integer(l - r),
|
||||
(Self::Decimal(l), Self::Integer(r)) => Self::Decimal(l - Decimal::from(r)),
|
||||
(Self::Integer(l), Self::Decimal(r)) => Self::Decimal(Decimal::from(l) - r),
|
||||
(Self::Decimal(l), Self::Decimal(r)) => Self::Decimal(l - r),
|
||||
if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
|
||||
return match (self, rhs) {
|
||||
(Self::Integer(l, _), Self::Integer(r, _)) => Number::Integer(l - r, target_unit),
|
||||
(Self::Decimal(l, _), Self::Decimal(r, _)) => Number::Decimal(l - r, target_unit),
|
||||
(Self::Integer(l, _), Self::Decimal(r, _)) => {
|
||||
Number::Decimal(Decimal::from(l) - r, target_unit)
|
||||
}
|
||||
(Self::Decimal(l, _), Self::Integer(r, _)) => {
|
||||
Number::Decimal(l - Decimal::from(r), target_unit)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let l: Decimal = self.into();
|
||||
let r: Decimal = rhs.into();
|
||||
Number::Decimal(l - r, Unit::None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,12 +480,26 @@ impl std::ops::Mul for Number {
|
||||
type Output = Number;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Number::Integer(l), Number::Integer(r)) => Number::Integer(l * r),
|
||||
(Number::Integer(l), Number::Decimal(r)) => Number::Decimal(Decimal::from(l) * r),
|
||||
(Number::Decimal(l), Number::Integer(r)) => Number::Decimal(l * Decimal::from(r)),
|
||||
(Number::Decimal(l), Number::Decimal(r)) => Number::Decimal(l * r),
|
||||
if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
|
||||
return match (self, rhs) {
|
||||
(Number::Integer(l, _), Number::Integer(r, _)) => {
|
||||
Number::Integer(l * r, target_unit)
|
||||
}
|
||||
(Number::Integer(l, _), Number::Decimal(r, _)) => {
|
||||
Number::Decimal(Decimal::from(l) * r, target_unit)
|
||||
}
|
||||
(Number::Decimal(l, _), Number::Integer(r, _)) => {
|
||||
Number::Decimal(l * Decimal::from(r), target_unit)
|
||||
}
|
||||
(Number::Decimal(l, _), Number::Decimal(r, _)) => {
|
||||
Number::Decimal(l * r, target_unit)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let l: Decimal = self.into();
|
||||
let r: Decimal = rhs.into();
|
||||
Number::Decimal(l * r, Unit::None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +507,22 @@ impl std::ops::Div for Number {
|
||||
type Output = Number;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
Number::Decimal(Decimal::from(self) / Decimal::from(rhs))
|
||||
if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
|
||||
// Division always promotes to Decimal
|
||||
let l_val = match self {
|
||||
Self::Integer(i, _) => Decimal::from(i),
|
||||
Self::Decimal(d, _) => d,
|
||||
};
|
||||
let r_val = match rhs {
|
||||
Self::Integer(i, _) => Decimal::from(i),
|
||||
Self::Decimal(d, _) => d,
|
||||
};
|
||||
return Number::Decimal(l_val / r_val, target_unit);
|
||||
}
|
||||
|
||||
let l: Decimal = self.into();
|
||||
let r: Decimal = rhs.into();
|
||||
Number::Decimal(l / r, Unit::None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,15 +530,36 @@ impl std::ops::Rem for Number {
|
||||
type Output = Number;
|
||||
|
||||
fn rem(self, rhs: Self) -> Self::Output {
|
||||
Number::Decimal(Decimal::from(self) % Decimal::from(rhs))
|
||||
if let Some(target_unit) = determine_target_unit(self.unit(), rhs.unit()) {
|
||||
let l_val = match self {
|
||||
Self::Integer(i, _) => Decimal::from(i),
|
||||
Self::Decimal(d, _) => d,
|
||||
};
|
||||
let r_val = match rhs {
|
||||
Self::Integer(i, _) => Decimal::from(i),
|
||||
Self::Decimal(d, _) => d,
|
||||
};
|
||||
return Number::Decimal(l_val % r_val, target_unit);
|
||||
}
|
||||
|
||||
let l: Decimal = self.into();
|
||||
let r: Decimal = rhs.into();
|
||||
Number::Decimal(l % r, Unit::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Number {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Number::Integer(i) => write!(f, "{}", i),
|
||||
Number::Decimal(d) => write!(f, "{}", d),
|
||||
let (val, unit) = match self {
|
||||
Number::Integer(i, u) => (i.to_string(), u),
|
||||
Number::Decimal(d, u) => (d.to_string(), u),
|
||||
};
|
||||
|
||||
match unit {
|
||||
Unit::None => write!(f, "{}", val),
|
||||
Unit::Celsius => write!(f, "{}c", val),
|
||||
Unit::Fahrenheit => write!(f, "{}f", val),
|
||||
Unit::Kelvin => write!(f, "{}k", val),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -771,3 +842,4 @@ documented! {
|
||||
While,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ struct Args {
|
||||
/// The output file for the compiled program. If not set, output will go to stdout.
|
||||
#[arg(short, long)]
|
||||
output_file: Option<PathBuf>,
|
||||
/// Should Slang attempt to optimize the output?
|
||||
#[arg(short = 'z', long)]
|
||||
optimize: bool,
|
||||
}
|
||||
|
||||
fn run_logic<'a>() -> Result<(), Error<'a>> {
|
||||
@@ -107,7 +110,11 @@ fn run_logic<'a>() -> Result<(), Error<'a>> {
|
||||
}
|
||||
}
|
||||
|
||||
if args.optimize {
|
||||
optimizer::optimize(instructions).write(&mut writer)?;
|
||||
} else {
|
||||
instructions.write(&mut writer)?;
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user