20 Commits
0.1.1 ... 0.2.0

Author SHA1 Message Date
46500a456a Add support for colorized comments 2025-12-09 17:38:35 -07:00
f60c9b32a8 minor version bump 2025-12-09 16:50:23 -07:00
0cddb3e8c8 The Cows are all working. Moo. 2025-12-09 16:31:24 -07:00
c3986ab4d9 VariableManager lifetime errors 2025-12-09 16:05:40 -07:00
f54214acb9 Most of all the errors are gone 2025-12-09 13:59:54 -07:00
d9a7a31306 Lifetimes are declared, now I gotta fix the lifetime issues 2025-12-09 13:51:54 -07:00
d40b759442 TEST -- use Cow instead of String for tokens 2025-12-09 13:17:35 -07:00
080b5320f7 Removed off-by-one calculations in the C# mod 2025-12-09 12:24:29 -07:00
a50a45f0b4 More cleanup 2025-12-09 12:12:28 -07:00
c531f673a5 Remove quickerror in favor of thiserror 2025-12-09 11:32:14 -07:00
23c2ba4134 Fix visual bugs with new span logic 2025-12-09 02:21:56 -07:00
7b7c1f7d29 Logos plugged into Parser 2025-12-09 02:15:43 -07:00
72cf9ea042 wip 2025-12-09 01:43:12 -07:00
fac36c756b Lexer impl done 2025-12-08 23:19:23 -07:00
115a57128c Before error type refactor 2025-12-08 22:50:20 -07:00
6afeec6da2 First pass getting a logos tokenizer up and running 2025-12-08 21:06:42 -07:00
b6123219f8 Merge pull request #21 from dbidwell94/cysharp-removal
Updates for Stationeers Beta branch
2025-12-08 15:26:22 -07:00
f38c15da9c Remove references to Unitask and Cysharp 2025-12-07 23:00:45 -07:00
fdd1a311d5 Merge pull request #20 from dbidwell94/release
Release
2025-12-07 21:46:05 -07:00
fb7ca0d4fd Update About.xml again 2025-12-07 21:45:07 -07:00
22 changed files with 1212 additions and 1652 deletions

11
Changelog.md Normal file
View File

@@ -0,0 +1,11 @@
# Changelog
[0.2.0]
- Completely re-wrote the tokenizer to use `logos`
- Changed AST and Token data structures to use `Cow` instead of `String`
- Updated error reporting to use `thiserror` instead of `quickerror`
[0.1.2]
- Removed references to `Unitask`

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>StationeersSlang</Name>
<Name>Slang</Name>
<Author>JoeDiertay</Author>
<Version>0.1.1</Version>
<Version>0.2.0</Version>
<Description>
[h1]Slang: High-Level Programming for Stationeers[/h1]
Stop writing assembly. Start writing code.
Slang (Stationeers Language) brings modern programming to Stationeers. It allows you to write scripts using a familiar C-style syntax (variables, functions, if/else, loops) directly in the in-game editor. When you hit confirm, Slang compiles your code into optimized IC10 MIPS assembly instantly.
Slang (Stationeers Language) brings modern programming to Stationeers. It allows you to write scripts using a familiar C-style syntax (variables, functions, if/else, loops) directly in the in-game editor. When you hit confirm, Slang compiles your code into IC10 instantly.
[b]NOTE: This project is in BETA. Expect updates and changes![/b]
@@ -91,7 +91,7 @@ A: Yes! Slang does not modify any existing IC10 code, it is only a compiler. As
<OrderBefore WorkshopHandle="3592775931" />
<InGameDescription><![CDATA[
<size=30><color=#ffff00>Slang - High Level Language Compiler</color></size>
A modern programming experience for Stationeers. Write C-style code that compiles to MIPS assembly instantly.
A modern programming experience for Stationeers. Write C-style code that compiles to IC10 instantly.
<color=#ffa500><b>Features</b></color>
- <b>In-Game Compilation:</b> Write high-level logic directly in the chip editor.

View File

@@ -55,7 +55,7 @@ public static unsafe class SlangExtensions
var color = GetColorForKind(token.token_kind);
int colIndex = token.column - 1;
int colIndex = token.column;
if (colIndex < 0)
colIndex = 0;
@@ -100,10 +100,10 @@ public static unsafe class SlangExtensions
Severity = item.severity,
Range = new Slang.Range
{
EndCol = Math.Max(item.range.end_col - 2, 0),
EndLine = item.range.end_line - 1,
StartCol = Math.Max(item.range.start_col - 2, 0),
StartLine = item.range.end_line - 1,
EndCol = Math.Max(item.range.end_col, 0),
EndLine = item.range.end_line,
StartCol = Math.Max(item.range.start_col, 0),
StartLine = item.range.start_line,
},
}
);
@@ -134,6 +134,9 @@ public static unsafe class SlangExtensions
case 7: // (punctuation)
return SlangFormatter.ColorDefault;
case 8: // Comments
return SlangFormatter.ColorComment;
case 10: // (syscalls)
return SlangFormatter.ColorFunction;

View File

@@ -4,7 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
using StationeersIC10Editor;
public class SlangFormatter : ICodeFormatter
@@ -98,29 +98,32 @@ public class SlangFormatter : ICodeFormatter
inputSrc = this.RawText;
}
HandleLsp(inputSrc, token).Forget();
_ = HandleLsp(inputSrc, token);
}
private async UniTaskVoid HandleLsp(string inputSrc, CancellationToken cancellationToken)
private async Task HandleLsp(string inputSrc, CancellationToken cancellationToken)
{
try
{
await UniTask.SwitchToThreadPool();
if (cancellationToken.IsCancellationRequested)
return;
await Task.Delay(200, cancellationToken: cancellationToken);
if (cancellationToken.IsCancellationRequested)
return;
await System.Threading.Tasks.Task.Delay(200, cancellationToken: cancellationToken);
if (cancellationToken.IsCancellationRequested)
return;
var dict = Marshal
.DiagnoseSource(inputSrc)
.GroupBy(d => d.Range.StartLine)
.ToDictionary(g => g.Key);
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken);
// Running this potentially CPU intensive work on a background thread.
var dict = await Task.Run(
() =>
{
return Marshal
.DiagnoseSource(inputSrc)
.GroupBy(d => d.Range.StartLine)
.ToDictionary(g => g.Key);
},
cancellationToken
);
ApplyDiagnostics(dict);
}
@@ -136,7 +139,6 @@ public class SlangFormatter : ICodeFormatter
{
HashSet<uint> linesToRefresh;
// CRITICAL FIX FOR LINE SHIFTS:
// If the line count has changed (lines added/deleted), indices have shifted.
// We must refresh ALL lines to ensure any line that shifted into a new position
// gets scrubbed of its old visual state.

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<AssemblyName>StationeersSlang</AssemblyName>
<Description>Slang Compiler Bridge</Description>
<Version>0.1.1</Version>
<Version>0.2.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
@@ -39,10 +39,6 @@
<HintPath>./ref/Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UniTask">
<HintPath>./ref/UniTask.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="IC10Editor.dll">
<HintPath>./ref/IC10Editor.dll</HintPath>

109
rust_compiler/Cargo.lock generated
View File

@@ -28,6 +28,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.21"
@@ -114,6 +123,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -257,8 +272,8 @@ dependencies = [
"lsp-types",
"parser",
"pretty_assertions",
"quick-error",
"rust_decimal",
"thiserror",
"tokenizer",
]
@@ -327,6 +342,12 @@ dependencies = [
"bitflags",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "funty"
version = "2.0.0"
@@ -434,6 +455,40 @@ version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "logos"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a790d11254054e5dc83902dba85d253ff06ceb0cfafb12be8773435cb9dfb4f4"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-codegen"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60337c43a38313b58871f8d5d76872b8e17aa9d51fad494b5e76092c0ce05f5"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-automata",
"regex-syntax",
"rustc_version",
"syn 2.0.111",
]
[[package]]
name = "logos-derive"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d151b2ae667f69e10b8738f5cac0c746faa22b2e15ea7e83b55476afec3767dc"
dependencies = [
"logos-codegen",
]
[[package]]
name = "lsp-types"
version = "0.97.0"
@@ -516,7 +571,7 @@ dependencies = [
"helpers",
"lsp-types",
"pretty_assertions",
"quick-error",
"thiserror",
"tokenizer",
]
@@ -593,12 +648,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.42"
@@ -644,6 +693,23 @@ dependencies = [
"getrandom",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rend"
version = "0.4.2"
@@ -843,7 +909,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "slang"
version = "0.1.1"
version = "0.2.0"
dependencies = [
"anyhow",
"clap",
@@ -851,9 +917,9 @@ dependencies = [
"helpers",
"lsp-types",
"parser",
"quick-error",
"rust_decimal",
"safer-ffi",
"thiserror",
"tokenizer",
]
@@ -926,6 +992,26 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
name = "tinyvec"
version = "1.10.0"
@@ -947,9 +1033,10 @@ version = "0.1.0"
dependencies = [
"anyhow",
"helpers",
"logos",
"lsp-types",
"quick-error",
"rust_decimal",
"thiserror",
]
[[package]]

View File

@@ -1,13 +1,13 @@
[package]
name = "slang"
version = "0.1.1"
version = "0.2.0"
edition = "2021"
[workspace]
members = ["libs/*"]
[workspace.dependencies]
quick-error = "2"
thiserror = "2"
rust_decimal = "1"
safer-ffi = { version = "0.1" } # Safely share structs in memory between C# and Rust
lsp-types = { version = "0.97" } # Allows for LSP style reporting to the frontend
@@ -36,13 +36,13 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
clap = { version = "^4.5", features = ["derive"] }
lsp-types = { workspace = true }
quick-error = { workspace = true }
thiserror = { workspace = true }
rust_decimal = { workspace = true }
tokenizer = { path = "libs/tokenizer" }
parser = { path = "libs/parser" }
compiler = { path = "libs/compiler" }
helpers = { path = "libs/helpers" }
safer-ffi = { workspace = true }
anyhow = { version = "^1.0", features = ["backtrace"] }
[dev-dependencies]
anyhow = { version = "^1.0", features = ["backtrace"] }

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
quick-error = { workspace = true }
thiserror = { workspace = true }
parser = { path = "../parser" }
tokenizer = { path = "../tokenizer" }
helpers = { path = "../helpers" }

View File

@@ -22,7 +22,7 @@ macro_rules! compile {
(result $source:expr) => {{
let mut writer = std::io::BufWriter::new(Vec::new());
let compiler = crate::Compiler::new(
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
parser::Parser::new(tokenizer::Tokenizer::from($source)),
&mut writer,
Some(crate::CompilerConfig { debug: true }),
);
@@ -32,7 +32,7 @@ macro_rules! compile {
(debug $source:expr) => {{
let mut writer = std::io::BufWriter::new(Vec::new());
let compiler = crate::Compiler::new(
parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))),
parser::Parser::new(tokenizer::Tokenizer::from($source)),
&mut writer,
Some(crate::CompilerConfig { debug: true }),
);

View File

@@ -11,11 +11,12 @@ use parser::{
LoopExpression, MemberAccessExpression, Span, Spanned, WhileExpression,
},
};
use quick_error::quick_error;
use std::{
borrow::Cow,
collections::HashMap,
io::{BufWriter, Write},
};
use thiserror::Error;
use tokenizer::token::Number;
macro_rules! debug {
@@ -36,7 +37,10 @@ macro_rules! debug {
};
}
fn extract_literal(literal: Literal, allow_strings: bool) -> Result<String, Error> {
fn extract_literal<'a>(
literal: Literal<'a>,
allow_strings: bool,
) -> Result<Cow<'a, str>, Error<'a>> {
if !allow_strings && matches!(literal, Literal::String(_)) {
return Err(Error::Unknown(
"Literal strings are not allowed in this context".to_string(),
@@ -45,59 +49,56 @@ fn extract_literal(literal: Literal, allow_strings: bool) -> Result<String, Erro
}
Ok(match literal {
Literal::String(s) => s,
Literal::Number(n) => n.to_string(),
Literal::Boolean(b) => if b { "1" } else { "0" }.into(),
Literal::Number(n) => Cow::from(n.to_string()),
Literal::Boolean(b) => Cow::from(if b { "1" } else { "0" }),
})
}
quick_error! {
#[derive(Debug)]
pub enum Error {
ParseError(error: parser::Error) {
from()
}
IoError(error: String) {
display("IO Error: {}", error)
}
ScopeError(error: variable_manager::Error) {
from()
}
DuplicateIdentifier(func_name: String, span: Span) {
display("`{func_name}` has already been defined")
}
UnknownIdentifier(ident: String, span: Span) {
display("`{ident}` is not found in the current scope.")
}
InvalidDevice(device: String, span: Span) {
display("`{device}` is not valid")
}
AgrumentMismatch(func_name: String, span: Span) {
display("Incorrect number of arguments passed into `{func_name}`")
}
ConstAssignment(ident: String, span: Span) {
display("Attempted to re-assign a value to const variable `{ident}`")
}
DeviceAssignment(ident: String, span: Span) {
display("Attempted to re-assign a value to a device const `{ident}`")
}
Unknown(reason: String, span: Option<Span>) {
display("{reason}")
}
}
#[derive(Error, Debug)]
pub enum Error<'a> {
#[error("{0}")]
Parse(parser::Error<'a>),
#[error("{0}")]
Scope(variable_manager::Error<'a>),
#[error("IO Error: {0}")]
IO(String),
#[error("`{0}` has already been defined.")]
DuplicateIdentifier(Cow<'a, str>, Span),
#[error("`{0}` is not found in the current scope.")]
UnknownIdentifier(Cow<'a, str>, Span),
#[error("`{0}` is not valid.")]
InvalidDevice(Cow<'a, str>, Span),
#[error("Incorrent number of arguments passed into `{0}`")]
AgrumentMismatch(Cow<'a, str>, Span),
#[error("Attempted to re-assign a value to const variable `{0}`")]
ConstAssignment(Cow<'a, str>, Span),
#[error("Attempted to re-assign a value to a device const `{0}`")]
DeviceAssignment(Cow<'a, str>, Span),
#[error("{0}")]
Unknown(String, Option<Span>),
}
impl From<Error> for lsp_types::Diagnostic {
impl<'a> From<Error<'a>> for lsp_types::Diagnostic {
fn from(value: Error) -> Self {
use Error::*;
use lsp_types::*;
match value {
ParseError(e) => e.into(),
IoError(e) => Diagnostic {
Parse(e) => e.into(),
IO(e) => Diagnostic {
message: e.to_string(),
severity: Some(DiagnosticSeverity::ERROR),
..Default::default()
},
ScopeError(e) => e.into(),
Scope(e) => e.into(),
DuplicateIdentifier(_, span)
| UnknownIdentifier(_, span)
| InvalidDevice(_, span)
@@ -119,10 +120,22 @@ impl From<Error> for lsp_types::Diagnostic {
}
}
impl<'a> From<parser::Error<'a>> for Error<'a> {
fn from(value: parser::Error<'a>) -> Self {
Self::Parse(value)
}
}
impl<'a> From<variable_manager::Error<'a>> for Error<'a> {
fn from(value: variable_manager::Error<'a>) -> Self {
Self::Scope(value)
}
}
// Map io::Error to Error manually since we can't clone io::Error
impl From<std::io::Error> for Error {
impl<'a> From<std::io::Error> for Error<'a> {
fn from(err: std::io::Error) -> Self {
Error::IoError(err.to_string())
Error::IO(err.to_string())
}
}
@@ -132,32 +145,32 @@ pub struct CompilerConfig {
pub debug: bool,
}
struct CompilationResult {
location: VariableLocation,
struct CompilationResult<'a> {
location: VariableLocation<'a>,
/// If Some, this is the name of the temporary variable that holds the result.
/// It must be freed by the caller when done.
temp_name: Option<String>,
temp_name: Option<Cow<'a, str>>,
}
pub struct Compiler<'a, W: std::io::Write> {
pub struct Compiler<'a, 'w, W: std::io::Write> {
pub parser: ASTParser<'a>,
function_locations: HashMap<String, usize>,
function_metadata: HashMap<String, Vec<String>>,
devices: HashMap<String, String>,
output: &'a mut BufWriter<W>,
function_locations: HashMap<Cow<'a, str>, usize>,
function_metadata: HashMap<Cow<'a, str>, Vec<Cow<'a, str>>>,
devices: HashMap<Cow<'a, str>, Cow<'a, str>>,
output: &'w mut BufWriter<W>,
current_line: usize,
declared_main: bool,
config: CompilerConfig,
temp_counter: usize,
label_counter: usize,
loop_stack: Vec<(String, String)>, // Stores (start_label, end_label)
pub errors: Vec<Error>,
loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label)
pub errors: Vec<Error<'a>>,
}
impl<'a, W: std::io::Write> Compiler<'a, W> {
impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
pub fn new(
parser: ASTParser<'a>,
writer: &'a mut BufWriter<W>,
writer: &'w mut BufWriter<W>,
config: Option<CompilerConfig>,
) -> Self {
Self {
@@ -176,12 +189,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
}
pub fn compile(mut self) -> Vec<Error> {
pub fn compile(mut self) -> Vec<Error<'a>> {
let expr = self.parser.parse_all();
// Copy errors from parser
for e in std::mem::take(&mut self.parser.errors) {
self.errors.push(Error::ParseError(e));
self.errors.push(Error::Parse(e));
}
// We treat parse_all result as potentially partial
@@ -190,7 +203,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(None) => return self.errors,
Err(e) => {
// Should be covered by parser.errors, but just in case
self.errors.push(Error::ParseError(e));
self.errors.push(Error::Parse(e));
return self.errors;
}
};
@@ -214,15 +227,17 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
return self.errors;
}
let mut scope = VariableScope::default();
// We ignore the result of the root expression (usually a block)
if let Err(e) = self.expression(spanned_root, &mut VariableScope::default()) {
if let Err(e) = self.expression(spanned_root, &mut scope) {
self.errors.push(e);
}
self.errors
}
fn write_output(&mut self, output: impl Into<String>) -> Result<(), Error> {
fn write_output(&mut self, output: impl Into<String>) -> Result<(), Error<'a>> {
self.output.write_all(output.into().as_bytes())?;
self.output.write_all(b"\n")?;
self.current_line += 1;
@@ -230,21 +245,21 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(())
}
fn next_temp_name(&mut self) -> String {
fn next_temp_name(&mut self) -> Cow<'a, str> {
self.temp_counter += 1;
format!("__binary_temp_{}", self.temp_counter)
Cow::from(format!("__binary_temp_{}", self.temp_counter))
}
fn next_label_name(&mut self) -> String {
fn next_label_name(&mut self) -> Cow<'a, str> {
self.label_counter += 1;
format!("L{}", self.label_counter)
Cow::from(format!("L{}", self.label_counter))
}
fn expression<'v>(
fn expression(
&mut self,
expr: Spanned<Expression>,
scope: &mut VariableScope<'v>,
) -> Result<Option<CompilationResult>, Error> {
expr: Spanned<Expression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<Option<CompilationResult<'a>>, Error<'a>> {
match expr.node {
Expression::Function(expr_func) => {
self.expression_function(expr_func, scope)?;
@@ -303,11 +318,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// Invocation returns result in r15 (RETURN_REGISTER).
// If used as an expression, we must move it to a temp to avoid overwrite.
let temp_name = self.next_temp_name();
let temp_loc = scope.add_variable(&temp_name, LocationRequest::Temp, None)?;
let temp_loc =
scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?;
self.emit_variable_assignment(
&temp_name,
temp_name.clone(),
&temp_loc,
format!("r{}", VariableScope::RETURN_REGISTER),
Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)),
)?;
Ok(Some(CompilationResult {
location: temp_loc,
@@ -325,8 +341,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Expression::Literal(spanned_lit) => match spanned_lit.node {
Literal::Number(num) => {
let temp_name = self.next_temp_name();
let loc = scope.add_variable(&temp_name, LocationRequest::Temp, None)?;
self.emit_variable_assignment(&temp_name, &loc, num.to_string())?;
let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?;
self.emit_variable_assignment(
temp_name.clone(),
&loc,
Cow::from(num.to_string()),
)?;
Ok(Some(CompilationResult {
location: loc,
temp_name: Some(temp_name),
@@ -335,8 +355,8 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Literal::Boolean(b) => {
let val = if b { "1" } else { "0" };
let temp_name = self.next_temp_name();
let loc = scope.add_variable(&temp_name, LocationRequest::Temp, None)?;
self.emit_variable_assignment(&temp_name, &loc, val)?;
let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?;
self.emit_variable_assignment(temp_name.clone(), &loc, Cow::from(val))?;
Ok(Some(CompilationResult {
location: loc,
temp_name: Some(temp_name),
@@ -377,7 +397,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// 2. Allocate a temp register for the result
let result_name = self.next_temp_name();
let loc = scope.add_variable(&result_name, LocationRequest::Temp, None)?;
let loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
let reg = self.resolve_register(&loc)?;
// 3. Emit load instruction: l rX device member
@@ -409,7 +429,8 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// Compile negation as 0 - inner
let (inner_str, cleanup) = self.compile_operand(*inner_expr, scope)?;
let result_name = self.next_temp_name();
let result_loc = scope.add_variable(&result_name, LocationRequest::Temp, None)?;
let result_loc =
scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
let result_reg = self.resolve_register(&result_loc)?;
self.write_output(format!("sub {result_reg} 0 {inner_str}"))?;
@@ -435,11 +456,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
/// Resolves an expression to a device identifier string for use in instructions like `s` or `l`.
/// Returns (device_string, optional_cleanup_temp_name).
fn resolve_device<'v>(
fn resolve_device(
&mut self,
expr: Spanned<Expression>,
scope: &mut VariableScope<'v>,
) -> Result<(String, Option<String>), Error> {
expr: Spanned<Expression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(Cow<'a, str>, Option<Cow<'a, str>>), Error<'a>> {
// If it's a direct variable reference, check if it's a known device alias first
if let Expression::Variable(ref name) = expr.node
&& let Some(device_id) = self.devices.get(&name.node)
@@ -453,10 +474,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
fn emit_variable_assignment(
&mut self,
var_name: &str,
location: &VariableLocation,
source_value: impl Into<String>,
) -> Result<(), Error> {
var_name: Cow<'a, str>,
location: &VariableLocation<'a>,
source_value: Cow<'a, str>,
) -> Result<(), Error<'a>> {
let debug_tag = if self.config.debug {
format!(" #{var_name}")
} else {
@@ -465,10 +486,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
match location {
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
self.write_output(format!("move r{reg} {}{debug_tag}", source_value.into()))?;
self.write_output(format!("move r{reg} {}{debug_tag}", source_value))?;
}
VariableLocation::Stack(_) => {
self.write_output(format!("push {}{debug_tag}", source_value.into()))?;
self.write_output(format!("push {}{debug_tag}", source_value))?;
}
VariableLocation::Constant(_) => {
return Err(Error::Unknown(
@@ -491,12 +512,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(())
}
fn expression_declaration<'v>(
fn expression_declaration(
&mut self,
var_name: Spanned<String>,
expr: Spanned<Expression>,
scope: &mut VariableScope<'v>,
) -> Result<Option<CompilationResult>, Error> {
var_name: Spanned<Cow<'a, str>>,
expr: Spanned<Expression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<Option<CompilationResult<'a>>, Error<'a>> {
let name_str = var_name.node;
let name_span = var_name.span;
@@ -505,8 +526,13 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
&& let Expression::Literal(spanned_lit) = &box_expr.node
&& let Literal::Number(neg_num) = &spanned_lit.node
{
let loc = scope.add_variable(&name_str, LocationRequest::Persist, Some(name_span))?;
self.emit_variable_assignment(&name_str, &loc, format!("-{neg_num}"))?;
let loc =
scope.add_variable(name_str.clone(), LocationRequest::Persist, Some(name_span))?;
self.emit_variable_assignment(
name_str.clone(),
&loc,
Cow::from(format!("-{neg_num}")),
)?;
return Ok(Some(CompilationResult {
location: loc,
temp_name: None,
@@ -522,7 +548,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Some(name_span),
)?;
self.emit_variable_assignment(&name_str, &var_location, num)?;
self.emit_variable_assignment(
name_str.clone(),
&var_location,
Cow::from(num.to_string()),
)?;
(var_location, None)
}
Literal::Boolean(b) => {
@@ -533,7 +563,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Some(name_span),
)?;
self.emit_variable_assignment(&name_str, &var_location, val)?;
self.emit_variable_assignment(name_str, &var_location, Cow::from(val))?;
(var_location, None)
}
_ => return Ok(None),
@@ -541,12 +571,15 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Expression::Invocation(invoke_expr) => {
self.expression_function_invocation(invoke_expr, scope)?;
let loc =
scope.add_variable(&name_str, LocationRequest::Persist, Some(name_span))?;
let loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
self.emit_variable_assignment(
&name_str,
name_str,
&loc,
format!("r{}", VariableScope::RETURN_REGISTER),
Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)),
)?;
(loc, None)
}
@@ -566,12 +599,15 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
));
};
let loc =
scope.add_variable(&name_str, LocationRequest::Persist, Some(name_span))?;
let loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
self.emit_variable_assignment(
&name_str,
name_str,
&loc,
format!("r{}", VariableScope::RETURN_REGISTER),
Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)),
)?;
(loc, None)
@@ -579,20 +615,23 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// Support assigning binary expressions to variables directly
Expression::Binary(bin_expr) => {
let result = self.expression_binary(bin_expr, scope)?;
let var_loc =
scope.add_variable(&name_str, LocationRequest::Persist, Some(name_span))?;
let var_loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
if let CompilationResult {
location: VariableLocation::Constant(Literal::Number(num)),
..
} = result
{
self.emit_variable_assignment(&name_str, &var_loc, num)?;
self.emit_variable_assignment(name_str, &var_loc, Cow::from(num.to_string()))?;
(var_loc, None)
} else {
// Move result from temp to new persistent variable
let result_reg = self.resolve_register(&result.location)?;
self.emit_variable_assignment(&name_str, &var_loc, result_reg)?;
self.emit_variable_assignment(name_str, &var_loc, result_reg)?;
// Free the temp result
if let Some(name) = result.temp_name {
@@ -603,12 +642,15 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
Expression::Logical(log_expr) => {
let result = self.expression_logical(log_expr, scope)?;
let var_loc =
scope.add_variable(&name_str, LocationRequest::Persist, Some(name_span))?;
let var_loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
// Move result from temp to new persistent variable
let result_reg = self.resolve_register(&result.location)?;
self.emit_variable_assignment(&name_str, &var_loc, result_reg)?;
self.emit_variable_assignment(name_str, &var_loc, result_reg)?;
// Free the temp result
if let Some(name) = result.temp_name {
@@ -628,8 +670,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
};
let var_loc =
scope.add_variable(&name_str, LocationRequest::Persist, Some(name_span))?;
let var_loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
// Handle loading from stack if necessary
let src_str = match src_loc {
@@ -649,7 +694,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
VariableLocation::Constant(_) | VariableLocation::Device(_) => unreachable!(),
};
self.emit_variable_assignment(&name_str, &var_loc, src_str)?;
self.emit_variable_assignment(name_str, &var_loc, Cow::from(src_str))?;
(var_loc, None)
}
Expression::Priority(inner) => {
@@ -680,11 +725,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
));
};
let var_loc =
scope.add_variable(&name_str, LocationRequest::Persist, Some(name_span))?;
let var_loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
let result_reg = self.resolve_register(&comp_res.location)?;
self.emit_variable_assignment(&name_str, &var_loc, result_reg)?;
self.emit_variable_assignment(name_str, &var_loc, result_reg)?;
if let Some(temp) = comp_res.temp_name {
scope.free_temp(temp, None)?;
@@ -706,11 +754,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}))
}
fn expression_const_declaration<'v>(
fn expression_const_declaration(
&mut self,
expr: ConstDeclarationExpression,
scope: &mut VariableScope<'v>,
) -> Result<CompilationResult, Error> {
expr: ConstDeclarationExpression<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<CompilationResult<'a>, Error<'a>> {
let ConstDeclarationExpression {
name: const_name,
value: const_value,
@@ -741,11 +789,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
})
}
fn expression_assignment<'v>(
fn expression_assignment(
&mut self,
expr: AssignmentExpression,
scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
expr: AssignmentExpression<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> {
let AssignmentExpression {
assignee,
expression,
@@ -831,9 +879,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
fn expression_function_invocation(
&mut self,
invoke_expr: Spanned<InvocationExpression>,
stack: &mut VariableScope,
) -> Result<(), Error> {
invoke_expr: Spanned<InvocationExpression<'a>>,
stack: &mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> {
let InvocationExpression { name, arguments } = invoke_expr.node;
if !self.function_locations.contains_key(&name.node) {
@@ -848,12 +896,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
let Some(args) = self.function_metadata.get(&name.node) else {
// Should be covered by check above
return Err(Error::UnknownIdentifier(name.node.clone(), name.span));
return Err(Error::UnknownIdentifier(name.node, name.span));
};
if args.len() != arguments.len() {
self.errors
.push(Error::AgrumentMismatch(name.node.clone(), name.span));
.push(Error::AgrumentMismatch(name.node, name.span));
// Proceed anyway? The assembly will likely crash or act weird.
// Best to skip generation of this call to prevent bad IC10
return Ok(());
@@ -862,7 +910,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// backup all used registers to the stack
let active_registers = stack.registers().cloned().collect::<Vec<_>>();
for register in &active_registers {
stack.add_variable(format!("temp_{register}"), LocationRequest::Stack, None)?;
stack.add_variable(
Cow::from(format!("temp_{register}")),
LocationRequest::Stack,
None,
)?;
self.write_output(format!("push r{register}"))?;
}
for arg in arguments {
@@ -879,15 +931,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
_ => {}
},
Expression::Variable(var_name) => {
let loc =
match stack.get_location_of(var_name.node.clone(), Some(var_name.span)) {
Ok(l) => l,
Err(_) => {
self.errors
.push(Error::UnknownIdentifier(var_name.node, var_name.span));
VariableLocation::Temporary(0)
}
};
let loc = match stack.get_location_of(&var_name.node, Some(var_name.span)) {
Ok(l) => l,
Err(_) => {
self.errors
.push(Error::UnknownIdentifier(var_name.node, var_name.span));
VariableLocation::Temporary(0)
}
};
match loc {
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => {
@@ -978,8 +1029,8 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
for register in active_registers {
let VariableLocation::Stack(stack_offset) = stack
.get_location_of(format!("temp_{register}"), None)
.map_err(Error::ScopeError)?
.get_location_of(&Cow::from(format!("temp_{register}")), None)
.map_err(Error::Scope)?
else {
// This shouldn't happen if we just added it
return Err(Error::Unknown(
@@ -1004,7 +1055,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(())
}
fn expression_device(&mut self, expr: DeviceDeclarationExpression) -> Result<(), Error> {
fn expression_device(
&mut self,
expr: DeviceDeclarationExpression<'a>,
) -> Result<(), Error<'a>> {
if self.devices.contains_key(&expr.name.node) {
self.errors.push(Error::DuplicateIdentifier(
expr.name.node.clone(),
@@ -1020,11 +1074,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(())
}
fn expression_if<'v>(
fn expression_if(
&mut self,
expr: IfExpression,
scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
expr: IfExpression<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> {
let end_label = self.next_label_name();
let else_label = if expr.else_branch.is_some() {
self.next_label_name()
@@ -1067,11 +1121,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(())
}
fn expression_loop<'v>(
fn expression_loop(
&mut self,
expr: LoopExpression,
scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
expr: LoopExpression<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> {
let start_label = self.next_label_name();
let end_label = self.next_label_name();
@@ -1093,11 +1147,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(())
}
fn expression_while<'v>(
fn expression_while(
&mut self,
expr: WhileExpression,
scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
expr: WhileExpression<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> {
let start_label = self.next_label_name();
let end_label = self.next_label_name();
@@ -1129,7 +1183,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(())
}
fn expression_break(&mut self) -> Result<(), Error> {
fn expression_break(&mut self) -> Result<(), Error<'a>> {
if let Some((_, end_label)) = self.loop_stack.last() {
self.write_output(format!("j {end_label}"))?;
Ok(())
@@ -1141,7 +1195,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
}
fn expression_continue(&mut self) -> Result<(), Error> {
fn expression_continue(&mut self) -> Result<(), Error<'a>> {
if let Some((start_label, _)) = self.loop_stack.last() {
self.write_output(format!("j {start_label}"))?;
Ok(())
@@ -1156,9 +1210,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
/// Helper to resolve a location to a register string (e.g., "r0").
/// Note: This does not handle Stack locations automatically, as they require
/// instruction emission to load. Use `compile_operand` for general handling.
fn resolve_register(&self, loc: &VariableLocation) -> Result<String, Error> {
fn resolve_register(&self, loc: &VariableLocation) -> Result<Cow<'a, str>, Error<'a>> {
match loc {
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => Ok(format!("r{r}")),
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => {
Ok(Cow::from(format!("r{r}")))
}
VariableLocation::Constant(_) => Err(Error::Unknown(
"Cannot resolve a constant value to register".into(),
None,
@@ -1180,19 +1236,19 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
/// so the caller can free it.
fn compile_operand(
&mut self,
expr: Spanned<Expression>,
scope: &mut VariableScope,
) -> Result<(String, Option<String>), Error> {
expr: Spanned<Expression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(Cow<'a, str>, Option<Cow<'a, str>>), Error<'a>> {
// Optimization for literals
if let Expression::Literal(spanned_lit) = &expr.node {
if let Literal::Number(n) = spanned_lit.node {
return Ok((n.to_string(), None));
return Ok((Cow::from(n.to_string()), None));
}
if let Literal::Boolean(b) = spanned_lit.node {
return Ok((if b { "1".to_string() } else { "0".to_string() }, None));
return Ok((Cow::from(if b { "1" } else { "0" }), None));
}
if let Literal::String(ref s) = spanned_lit.node {
return Ok((s.to_string(), None));
return Ok((s.clone(), None));
}
}
@@ -1202,7 +1258,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
&& let Expression::Literal(spanned_lit) = &inner.node
&& let Literal::Number(n) = spanned_lit.node
{
return Ok((format!("-{}", n), None));
return Ok((Cow::from(format!("-{}", n)), None));
}
let result_opt = self.expression(expr, scope)?;
@@ -1211,23 +1267,24 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Some(r) => r,
None => {
// Expression failed or returned void. Recover with dummy.
return Ok(("r0".to_string(), None));
return Ok((Cow::from("r0"), None));
}
};
match result.location {
VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => {
Ok((format!("r{r}"), result.temp_name))
Ok((Cow::from(format!("r{r}")), result.temp_name))
}
VariableLocation::Constant(lit) => match lit {
Literal::Number(n) => Ok((n.to_string(), None)),
Literal::Boolean(b) => Ok((if b { "1" } else { "0" }.to_string(), None)),
Literal::Number(n) => Ok((Cow::from(n.to_string()), None)),
Literal::Boolean(b) => Ok((Cow::from(if b { "1" } else { "0" }), None)),
Literal::String(s) => Ok((s, None)),
},
VariableLocation::Stack(offset) => {
// If it's on the stack, we must load it into a temp to use it as an operand
let temp_name = self.next_temp_name();
let temp_loc = scope.add_variable(&temp_name, LocationRequest::Temp, None)?;
let temp_loc =
scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?;
let temp_reg = self.resolve_register(&temp_loc)?;
self.write_output(format!(
@@ -1250,9 +1307,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
fn compile_literal_or_variable(
&mut self,
val: LiteralOrVariable,
scope: &mut VariableScope,
) -> Result<(String, Option<String>), Error> {
val: LiteralOrVariable<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(Cow<'a, str>, Option<Cow<'a, str>>), Error<'a>> {
let dummy_span = Span {
start_line: 0,
start_col: 0,
@@ -1276,12 +1333,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
)
}
fn expression_binary<'v>(
fn expression_binary(
&mut self,
expr: Spanned<BinaryExpression>,
scope: &mut VariableScope<'v>,
) -> Result<CompilationResult, Error> {
fn fold_binary_expression(expr: &BinaryExpression) -> Option<Number> {
expr: Spanned<BinaryExpression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<CompilationResult<'a>, Error<'a>> {
fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option<Number> {
let (lhs, rhs) = match &expr {
BinaryExpression::Add(l, r)
| BinaryExpression::Subtract(l, r)
@@ -1301,7 +1358,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
}
fn fold_expression(expr: &Expression) -> Option<Number> {
fn fold_expression<'a>(expr: &Expression<'a>) -> Option<Number> {
match expr {
// 1. Base Case: It's already a number
Expression::Literal(lit) => match lit.node {
@@ -1349,7 +1406,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// Allocate result register
let result_name = self.next_temp_name();
let result_loc = scope.add_variable(&result_name, LocationRequest::Temp, None)?;
let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
let result_reg = self.resolve_register(&result_loc)?;
// Emit instruction: op result lhs rhs
@@ -1369,17 +1426,18 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
})
}
fn expression_logical<'v>(
fn expression_logical(
&mut self,
expr: Spanned<LogicalExpression>,
scope: &mut VariableScope<'v>,
) -> Result<CompilationResult, Error> {
expr: Spanned<LogicalExpression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<CompilationResult<'a>, Error<'a>> {
match expr.node {
LogicalExpression::Not(inner) => {
let (inner_str, cleanup) = self.compile_operand(*inner, scope)?;
let result_name = self.next_temp_name();
let result_loc = scope.add_variable(&result_name, LocationRequest::Temp, None)?;
let result_loc =
scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
let result_reg = self.resolve_register(&result_loc)?;
// seq rX rY 0 => if rY == 0 set rX = 1 else rX = 0
@@ -1414,7 +1472,8 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// Allocate result register
let result_name = self.next_temp_name();
let result_loc = scope.add_variable(&result_name, LocationRequest::Temp, None)?;
let result_loc =
scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
let result_reg = self.resolve_register(&result_loc)?;
// Emit instruction: op result lhs rhs
@@ -1438,9 +1497,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
fn expression_block<'v>(
&mut self,
mut expr: BlockExpression,
parent_scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
mut expr: BlockExpression<'a>,
parent_scope: &'v mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> {
// First, sort the expressions to ensure functions are hoisted
expr.0.sort_by(|a, b| {
if matches!(
@@ -1502,17 +1561,21 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
/// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER
fn expression_return<'v>(
fn expression_return(
&mut self,
expr: Spanned<Expression>,
scope: &mut VariableScope<'v>,
) -> Result<VariableLocation, Error> {
expr: Spanned<Expression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<VariableLocation<'a>, Error<'a>> {
if let Expression::Negation(neg_expr) = &expr.node
&& let Expression::Literal(spanned_lit) = &neg_expr.node
&& let Literal::Number(neg_num) = &spanned_lit.node
{
let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER);
self.emit_variable_assignment("returnValue", &loc, format!("-{neg_num}"))?;
self.emit_variable_assignment(
Cow::from("returnValue"),
&loc,
Cow::from(format!("-{neg_num}")),
)?;
return Ok(loc);
};
@@ -1565,17 +1628,17 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Expression::Literal(spanned_lit) => match spanned_lit.node {
Literal::Number(num) => {
self.emit_variable_assignment(
"returnValue",
Cow::from("returnValue"),
&VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
num,
Cow::from(num.to_string()),
)?;
}
Literal::Boolean(b) => {
let val = if b { "1" } else { "0" };
self.emit_variable_assignment(
"returnValue",
Cow::from("returnValue"),
&VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
val,
Cow::from(val.to_string()),
)?;
}
_ => {}
@@ -1634,12 +1697,12 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
// syscalls that return values will be stored in the VariableScope::RETURN_REGISTER
// register
fn expression_syscall_system<'v>(
fn expression_syscall_system(
&mut self,
expr: System,
expr: System<'a>,
span: Span,
scope: &mut VariableScope<'v>,
) -> Result<Option<CompilationResult>, Error> {
scope: &mut VariableScope<'a, '_>,
) -> Result<Option<CompilationResult<'a>>, Error<'a>> {
macro_rules! cleanup {
($($to_clean:expr),*) => {
$(
@@ -1710,7 +1773,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
.devices
.get(&device_name)
.cloned()
.unwrap_or("d0".to_string());
.unwrap_or(Cow::from("d0"));
let Spanned {
node: Literal::String(logic_type),
@@ -1802,7 +1865,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
.devices
.get(&device_name)
.cloned()
.unwrap_or("d0".to_string());
.unwrap_or(Cow::from("d0"));
let Spanned {
node: Literal::String(logic_type),
@@ -1892,11 +1955,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
}
fn expression_syscall_math<'v>(
fn expression_syscall_math(
&mut self,
expr: Math,
scope: &mut VariableScope<'v>,
) -> Result<Option<CompilationResult>, Error> {
expr: Math<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<Option<CompilationResult<'a>>, Error<'a>> {
macro_rules! cleanup {
($($to_clean:expr),*) => {
$(
@@ -2088,11 +2151,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
/// Compile a function declaration.
/// Calees are responsible for backing up any registers they wish to use.
fn expression_function<'v>(
fn expression_function(
&mut self,
expr: Spanned<FunctionExpression>,
scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
expr: Spanned<FunctionExpression<'a>>,
scope: &mut VariableScope<'a, '_>,
) -> Result<(), Error<'a>> {
let FunctionExpression {
name,
arguments,
@@ -2174,7 +2237,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
self.write_output("push ra")?;
block_scope.add_variable(
format!("{}_ra", name.node),
Cow::from(format!("{}_ra", name.node)),
LocationRequest::Stack,
Some(name.span),
)?;
@@ -2201,7 +2264,8 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
}
// Get the saved return address and save it back into `ra`
let ra_res = block_scope.get_location_of(format!("{}_ra", name.node), Some(name.span));
let ra_res =
block_scope.get_location_of(&Cow::from(format!("{}_ra", name.node)), Some(name.span));
let ra_stack_offset = match ra_res {
Ok(VariableLocation::Stack(offset)) => offset,

View File

@@ -5,28 +5,28 @@
use lsp_types::{Diagnostic, DiagnosticSeverity};
use parser::tree_node::{Literal, Span};
use quick_error::quick_error;
use std::collections::{HashMap, VecDeque};
use std::{
borrow::Cow,
collections::{HashMap, VecDeque},
};
use thiserror::Error;
const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14];
quick_error! {
#[derive(Debug)]
pub enum Error {
DuplicateVariable(var: String, span: Option<Span>) {
display("{var} already exists.")
}
UnknownVariable(var: String, span: Option<Span>) {
display("{var} does not exist.")
}
Unknown(reason: String, span: Option<Span>) {
display("{reason}")
}
}
#[derive(Error, Debug)]
pub enum Error<'a> {
#[error("{0} already exists.")]
DuplicateVariable(Cow<'a, str>, Option<Span>),
#[error("{0} does not exist.")]
UnknownVariable(Cow<'a, str>, Option<Span>),
#[error("{0}")]
Unknown(Cow<'a, str>, Option<Span>),
}
impl From<Error> for lsp_types::Diagnostic {
impl<'a> From<Error<'a>> for lsp_types::Diagnostic {
fn from(value: Error) -> Self {
match value {
Error::DuplicateVariable(_, span)
@@ -53,7 +53,7 @@ pub enum LocationRequest {
}
#[derive(Clone)]
pub enum VariableLocation {
pub enum VariableLocation<'a> {
/// Represents a temporary register (r1 - r7)
Temporary(u8),
/// Represents a persistant register (r8 - r14)
@@ -61,20 +61,22 @@ pub enum VariableLocation {
/// Represents a a stack offset (current stack - offset = variable loc)
Stack(u16),
/// Represents a constant value and should be directly substituted as such.
Constant(Literal),
Constant(Literal<'a>),
/// Represents a device pin. This will contain the exact `d0-d5` string
Device(String),
Device(Cow<'a, str>),
}
pub struct VariableScope<'a> {
// FIX: Added 'b lifetime for the parent reference
pub struct VariableScope<'a, 'b> {
temporary_vars: VecDeque<u8>,
persistant_vars: VecDeque<u8>,
var_lookup_table: HashMap<String, VariableLocation>,
var_lookup_table: HashMap<Cow<'a, str>, VariableLocation<'a>>,
stack_offset: u16,
parent: Option<&'a VariableScope<'a>>,
parent: Option<&'b VariableScope<'a, 'b>>,
}
impl<'a> Default for VariableScope<'a> {
// FIX: Updated Default impl to include 'b
impl<'a, 'b> Default for VariableScope<'a, 'b> {
fn default() -> Self {
Self {
parent: None,
@@ -86,7 +88,8 @@ impl<'a> Default for VariableScope<'a> {
}
}
impl<'a> VariableScope<'a> {
// FIX: Updated impl block to include 'b
impl<'a, 'b> VariableScope<'a, 'b> {
#[allow(dead_code)]
pub const TEMP_REGISTER_COUNT: u8 = 7;
pub const PERSIST_REGISTER_COUNT: u8 = 7;
@@ -109,7 +112,8 @@ impl<'a> VariableScope<'a> {
})
}
pub fn scoped(parent: &'a VariableScope<'a>) -> Self {
// FIX: parent is now &'b VariableScope<'a, 'b>
pub fn scoped(parent: &'b VariableScope<'a, 'b>) -> Self {
Self {
parent: Option::Some(parent),
temporary_vars: parent.temporary_vars.clone(),
@@ -126,12 +130,11 @@ impl<'a> VariableScope<'a> {
/// to the stack.
pub fn add_variable(
&mut self,
var_name: impl Into<String>,
var_name: Cow<'a, str>,
location: LocationRequest,
span: Option<Span>,
) -> Result<VariableLocation, Error> {
let var_name = var_name.into();
if self.var_lookup_table.contains_key(var_name.as_str()) {
) -> Result<VariableLocation<'a>, Error<'a>> {
if self.var_lookup_table.contains_key(&var_name) {
return Err(Error::DuplicateVariable(var_name, span));
}
let var_location = match location {
@@ -166,11 +169,10 @@ impl<'a> VariableScope<'a> {
pub fn define_const(
&mut self,
var_name: impl Into<String>,
value: Literal,
var_name: Cow<'a, str>,
value: Literal<'a>,
span: Option<Span>,
) -> Result<VariableLocation, Error> {
let var_name = var_name.into();
) -> Result<VariableLocation<'a>, Error<'a>> {
if self.var_lookup_table.contains_key(&var_name) {
return Err(Error::DuplicateVariable(var_name, span));
}
@@ -183,13 +185,11 @@ impl<'a> VariableScope<'a> {
pub fn get_location_of(
&self,
var_name: impl Into<String>,
var_name: &Cow<'a, str>,
span: Option<Span>,
) -> Result<VariableLocation, Error> {
let var_name = var_name.into();
) -> Result<VariableLocation<'a>, Error<'a>> {
// 1. Check this scope
if let Some(var) = self.var_lookup_table.get(var_name.as_str()) {
if let Some(var) = self.var_lookup_table.get(var_name) {
if let VariableLocation::Stack(inserted_at_offset) = var {
// Return offset relative to CURRENT sp
return Ok(VariableLocation::Stack(
@@ -210,7 +210,7 @@ impl<'a> VariableScope<'a> {
return Ok(loc);
}
Err(Error::UnknownVariable(var_name, span))
Err(Error::UnknownVariable(var_name.clone(), span))
}
pub fn has_parent(&self) -> bool {
@@ -220,11 +220,10 @@ impl<'a> VariableScope<'a> {
#[allow(dead_code)]
pub fn free_temp(
&mut self,
var_name: impl Into<String>,
var_name: Cow<'a, str>,
span: Option<Span>,
) -> Result<(), Error> {
let var_name = var_name.into();
let Some(location) = self.var_lookup_table.remove(var_name.as_str()) else {
) -> Result<(), Error<'a>> {
let Some(location) = self.var_lookup_table.remove(&var_name) else {
return Err(Error::UnknownVariable(var_name, span));
};
@@ -234,7 +233,7 @@ impl<'a> VariableScope<'a> {
}
VariableLocation::Persistant(_) => {
return Err(Error::UnknownVariable(
String::from("Attempted to free a `let` variable."),
Cow::from("Attempted to free a `let` variable."),
span,
));
}

View File

@@ -0,0 +1,41 @@
// Pressure numbers are in KPa
device self = "db";
device emergencyRelief = "d0";
device greenhouseSensor = "d1";
device recycleValve = "d2";
const MAX_INTERIOR_PRESSURE = 80;
const MAX_INTERIOR_TEMP = 28c;
const MIN_INTERIOR_PRESSURE = 75;
const MIN_INTERIOR_TEMP = 25c;
const daylightSensor = 1076425094;
const growLight = hash("StructureGrowLight");
const wallLight = hash("StructureLightLong");
const lightRound = hash("StructureLightRound");
let shouldPurge = false;
loop {
let interiorPress = greenhouseSensor.Pressure;
let interiorTemp = greenhouseSensor.Temperature;
shouldPurge = (
interiorPress > MAX_INTERIOR_PRESSURE ||
interiorTemp > MAX_INTERIOR_TEMP
) || shouldPurge;
emergencyRelief.On = shouldPurge;
recycleValve.On = !shouldPurge;
if (shouldPurge && (interiorPress < MIN_INTERIOR_PRESSURE && interiorTemp < MIN_INTERIOR_TEMP)) {
shouldPurge = false;
}
let solarAngle = lb(daylightSensor, "SolarAngle", "Average");
let isDaylight = solarAngle < 90;
sb(growLight, "On", isDaylight);
sb(wallLight, "On", !isDaylight);
sb(lightRound, "On", !isDaylight);
}

View File

@@ -3,15 +3,10 @@ macro_rules! documented {
// -------------------------------------------------------------------------
// Internal Helper: Filter doc comments
// -------------------------------------------------------------------------
// Case 1: Doc comment. Return Some("string").
// We match the specific structure of a doc attribute.
(@doc_filter #[doc = $doc:expr]) => {
Some($doc)
};
// Case 2: Other attributes (derives, etc.). Return None.
// We catch any other token sequence inside the brackets.
(@doc_filter #[$($attr:tt)*]) => {
None
};
@@ -30,23 +25,59 @@ macro_rules! documented {
};
// -------------------------------------------------------------------------
// Main Macro Entry Point
// Entry Point 1: Enum with a single Lifetime (e.g. enum Foo<'a>)
// -------------------------------------------------------------------------
(
$(#[$enum_attr:meta])* $vis:vis enum $name:ident < $lt:lifetime > {
$($body:tt)*
}
) => {
documented!(@generate
meta: [$(#[$enum_attr])*],
vis: [$vis],
name: [$name],
generics: [<$lt>],
body: [$($body)*]
);
};
// -------------------------------------------------------------------------
// Entry Point 2: Regular Enum (No Generics)
// -------------------------------------------------------------------------
(
$(#[$enum_attr:meta])* $vis:vis enum $name:ident {
$($body:tt)*
}
) => {
documented!(@generate
meta: [$(#[$enum_attr])*],
vis: [$vis],
name: [$name],
generics: [],
body: [$($body)*]
);
};
// -------------------------------------------------------------------------
// Code Generator (Shared Logic)
// -------------------------------------------------------------------------
(@generate
meta: [$(#[$enum_attr:meta])*],
vis: [$vis:vis],
name: [$name:ident],
generics: [$($generics:tt)*],
body: [
$(
// Capture attributes as a sequence of token trees inside brackets
// to avoid "local ambiguity" and handle multi-token attributes (like doc="...").
$(#[ $($variant_attr:tt)* ])*
$variant:ident
$( ($($tuple:tt)*) )?
$( {$($structure:tt)*} )?
),* $(,)?
}
]
) => {
// 1. Generate the actual Enum definition
// 1. Generate the Enum Definition
$(#[$enum_attr])*
$vis enum $name {
$vis enum $name $($generics)* {
$(
$(#[ $($variant_attr)* ])*
$variant
@@ -55,20 +86,19 @@ macro_rules! documented {
)*
}
// 2. Implement the Documentation Trait
impl Documentation for $name {
// 2. Implement Documentation Trait
// We apply the captured generics (e.g., <'a>) to both the impl and the type
impl $($generics)* Documentation for $name $($generics)* {
fn docs(&self) -> String {
match self {
$(
documented!(@arm $name $variant $( ($($tuple)*) )? $( {$($structure)*} )? ) => {
// Create a temporary array of Option<&str> for all attributes
let doc_lines: &[Option<&str>] = &[
$(
documented!(@doc_filter #[ $($variant_attr)* ])
),*
];
// Filter out the Nones (non-doc attributes), join, and return
doc_lines.iter()
.filter_map(|&d| d)
.collect::<Vec<_>>()
@@ -80,7 +110,6 @@ macro_rules! documented {
}
}
// 3. Implement Static Documentation Provider
#[allow(dead_code)]
fn get_all_documentation() -> Vec<(&'static str, String)> {
vec![
@@ -88,7 +117,6 @@ macro_rules! documented {
(
stringify!($variant),
{
// Re-use the same extraction logic
let doc_lines: &[Option<&str>] = &[
$(
documented!(@doc_filter #[ $($variant_attr)* ])

View File

@@ -4,10 +4,10 @@ version = "0.1.0"
edition = "2024"
[dependencies]
quick-error = { workspace = true }
tokenizer = { path = "../tokenizer" }
helpers = { path = "../helpers" }
lsp-types = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]

View File

@@ -1,13 +1,12 @@
pub mod sys_call;
#[cfg(test)]
mod test;
pub mod sys_call;
pub mod tree_node;
use crate::sys_call::{Math, System};
use quick_error::quick_error;
use std::io::SeekFrom;
use std::{borrow::Cow, io::SeekFrom};
use sys_call::SysCall;
use thiserror::Error;
use tokenizer::{
self, Tokenizer, TokenizerBuffer,
token::{Keyword, Symbol, Token, TokenType},
@@ -26,38 +25,33 @@ macro_rules! boxed {
};
}
quick_error! {
#[derive(Debug)]
pub enum Error {
TokenizerError(err: tokenizer::Error) {
from()
display("Tokenizer Error: {}", err)
source(err)
}
UnexpectedToken(span: Span, token: Token) {
display("Unexpected token: {}", token.token_type)
}
DuplicateIdentifier(span: Span, token: Token) {
display("Duplicate identifier: {}", token.token_type)
}
InvalidSyntax(span: Span, reason: String) {
display("Invalid syntax: {}", reason)
}
UnsupportedKeyword(span: Span, token: Token) {
display("Unsupported keyword: {}", token.token_type)
}
UnexpectedEOF {
display("Unexpected EOF")
}
}
#[derive(Error, Debug)]
pub enum Error<'a> {
#[error(transparent)]
Tokenizer(#[from] tokenizer::Error),
#[error("Unexpected token: {1}")]
UnexpectedToken(Span, Token<'a>),
#[error("Duplicate identifier: {1}")]
DuplicateIdentifier(Span, Token<'a>),
#[error("Invalid Syntax: {1}")]
InvalidSyntax(Span, String),
#[error("Unsupported Keyword: {1}")]
UnsupportedKeyword(Span, Token<'a>),
#[error("Unexpected End of File")]
UnexpectedEOF,
}
impl From<Error> for lsp_types::Diagnostic {
impl<'a> From<Error<'a>> for lsp_types::Diagnostic {
fn from(value: Error) -> Self {
use Error::*;
use lsp_types::*;
match value {
TokenizerError(e) => e.into(),
Tokenizer(e) => e.into(),
UnexpectedToken(span, _)
| DuplicateIdentifier(span, _)
| InvalidSyntax(span, _)
@@ -111,8 +105,8 @@ macro_rules! self_matches_current {
pub struct Parser<'a> {
tokenizer: TokenizerBuffer<'a>,
current_token: Option<Token>,
pub errors: Vec<Error>,
current_token: Option<Token<'a>>,
pub errors: Vec<Error<'a>>,
}
impl<'a> Parser<'a> {
@@ -125,13 +119,12 @@ impl<'a> Parser<'a> {
}
/// Calculates a Span from a given Token reference.
fn token_to_span(t: &Token) -> Span {
let len = t.original_string.as_ref().map(|s| s.len()).unwrap_or(0);
fn token_to_span(t: &Token<'a>) -> Span {
Span {
start_line: t.line,
start_col: t.column,
start_col: t.span.start,
end_line: t.line,
end_col: t.column + len,
end_col: t.span.end,
}
}
@@ -148,9 +141,9 @@ impl<'a> Parser<'a> {
}
/// Helper to run a parsing closure and wrap the result in a Spanned struct
fn spanned<F, T>(&mut self, parser: F) -> Result<Spanned<T>, Error>
fn spanned<F, T>(&mut self, parser: F) -> Result<Spanned<T>, Error<'a>>
where
F: FnOnce(&mut Self) -> Result<T, Error>,
F: FnOnce(&mut Self) -> Result<T, Error<'a>>,
{
let start_token = if self.current_token.is_some() {
self.current_token.clone()
@@ -160,18 +153,16 @@ impl<'a> Parser<'a> {
let (start_line, start_col) = start_token
.as_ref()
.map(|t| (t.line, t.column))
.unwrap_or((1, 1));
.map(|t| (t.line, t.span.start))
.unwrap_or((0, 0));
let node = parser(self)?;
let end_token = self.current_token.as_ref();
let end_token = &self.current_token;
let (end_line, end_col) = end_token
.map(|t| {
let len = t.original_string.as_ref().map(|s| s.len()).unwrap_or(0);
(t.line, t.column + len)
})
.clone()
.map(|t| (t.line, t.span.end))
.unwrap_or((start_line, start_col));
Ok(Spanned {
@@ -185,7 +176,7 @@ impl<'a> Parser<'a> {
})
}
fn synchronize(&mut self) -> Result<(), Error> {
fn synchronize(&mut self) -> Result<(), Error<'a>> {
self.assign_next()?;
while let Some(token) = &self.current_token {
@@ -211,20 +202,20 @@ impl<'a> Parser<'a> {
Ok(())
}
pub fn parse_all(&mut self) -> Result<Option<tree_node::Expression>, Error> {
pub fn parse_all(&mut self) -> Result<Option<tree_node::Expression<'a>>, Error<'a>> {
let first_token = self.tokenizer.peek().unwrap_or(None);
let (start_line, start_col) = first_token
.as_ref()
.map(|tok| (tok.line, tok.column))
.unwrap_or((1, 1));
.map(|tok| (tok.line, tok.span.start))
.unwrap_or((0, 0));
let mut expressions = Vec::<Spanned<Expression>>::new();
let mut expressions = Vec::<Spanned<Expression<'a>>>::new();
loop {
match self.tokenizer.peek() {
Ok(None) => break,
Err(e) => {
self.errors.push(Error::TokenizerError(e));
self.errors.push(Error::Tokenizer(e));
break;
}
_ => {}
@@ -246,10 +237,7 @@ impl<'a> Parser<'a> {
let end_token_opt = self.tokenizer.peek().unwrap_or(None);
let (end_line, end_col) = end_token_opt
.map(|tok| {
let len = tok.original_string.as_ref().map(|s| s.len()).unwrap_or(0);
(tok.line, tok.column + len)
})
.map(|tok| (tok.line, tok.span.end))
.unwrap_or((start_line, start_col));
let span = Span {
@@ -265,7 +253,7 @@ impl<'a> Parser<'a> {
})))
}
pub fn parse(&mut self) -> Result<Option<Spanned<tree_node::Expression>>, Error> {
pub fn parse(&mut self) -> Result<Option<Spanned<tree_node::Expression<'a>>>, Error<'a>> {
self.assign_next()?;
if self.current_token.is_none() {
@@ -281,17 +269,17 @@ impl<'a> Parser<'a> {
Ok(expr)
}
fn assign_next(&mut self) -> Result<(), Error> {
fn assign_next(&mut self) -> Result<(), Error<'a>> {
self.current_token = self.tokenizer.next_token()?;
Ok(())
}
fn get_next(&mut self) -> Result<Option<&Token>, Error> {
fn get_next(&mut self) -> Result<Option<Token<'a>>, Error<'a>> {
self.assign_next()?;
Ok(self.current_token.as_ref())
Ok(self.current_token.clone())
}
fn expression(&mut self) -> Result<Option<Spanned<tree_node::Expression>>, Error> {
fn expression(&mut self) -> Result<Option<Spanned<tree_node::Expression<'a>>>, Error<'a>> {
// Parse the Left Hand Side (unary/primary expression)
let lhs = self.unary()?;
@@ -322,14 +310,14 @@ impl<'a> Parser<'a> {
/// Handles dot notation chains: x.y.z()
fn parse_postfix(
&mut self,
mut lhs: Spanned<Expression>,
) -> Result<Spanned<Expression>, Error> {
mut lhs: Spanned<Expression<'a>>,
) -> Result<Spanned<Expression<'a>>, Error<'a>> {
loop {
if self_matches_peek!(self, TokenType::Symbol(Symbol::Dot)) {
self.assign_next()?; // consume Dot
let identifier_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
let identifier_span = Self::token_to_span(identifier_token);
let identifier_span = Self::token_to_span(&identifier_token);
let identifier = match identifier_token.token_type {
TokenType::Identifier(ref id) => id.clone(),
_ => {
@@ -344,7 +332,7 @@ impl<'a> Parser<'a> {
if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) {
// Method Call
self.assign_next()?; // consume '('
let mut arguments = Vec::<Spanned<Expression>>::new();
let mut arguments = Vec::<Spanned<Expression<'a>>>::new();
while !token_matches!(
self.get_next()?.ok_or(Error::UnexpectedEOF)?,
@@ -366,8 +354,8 @@ impl<'a> Parser<'a> {
{
let next_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
return Err(Error::UnexpectedToken(
Self::token_to_span(next_token),
next_token.clone(),
Self::token_to_span(&next_token),
next_token,
));
}
@@ -429,7 +417,7 @@ impl<'a> Parser<'a> {
Ok(lhs)
}
fn unary(&mut self) -> Result<Option<Spanned<tree_node::Expression>>, Error> {
fn unary(&mut self) -> Result<Option<Spanned<tree_node::Expression<'a>>>, Error<'a>> {
macro_rules! matches_keyword {
($keyword:expr, $($pattern:pat),+) => {
matches!($keyword, $($pattern)|+)
@@ -507,10 +495,7 @@ impl<'a> Parser<'a> {
let span = self.current_span();
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
Some(Spanned {
span,
@@ -522,10 +507,7 @@ impl<'a> Parser<'a> {
let span = self.current_span();
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
Some(Spanned {
span,
@@ -586,17 +568,6 @@ impl<'a> Parser<'a> {
let start_span = self.current_span();
self.assign_next()?;
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
// NOTE: Unary negation can also have postfix applied to the inner expression
// But generally -a.b parses as -(a.b), which is what parse_postfix ensures if called here.
// However, we call parse_postfix on the RESULT of unary in expression(), so
// `expression` sees `Negation`. `parse_postfix` doesn't apply to Negation node unless we allow it?
// Actually, `x.y` binds tighter than `-`. `postfix` logic belongs inside `unary` logic or
// `expression` logic.
// If I have `-x.y`, standard precedence says `-(x.y)`.
// `unary` returns `Negation(x)`. Then `expression` calls `postfix` on `Negation(x)`.
// `postfix` loop runs on `Negation`. This implies `(-x).y`. This is usually WRONG.
// `.` binds tighter than `-`.
// So `unary` must call `postfix` on the *operand* of the negation.
let inner_with_postfix = self.parse_postfix(inner_expr)?;
@@ -643,7 +614,7 @@ impl<'a> Parser<'a> {
Ok(expr)
}
fn get_infix_child_node(&mut self) -> Result<Spanned<tree_node::Expression>, Error> {
fn get_infix_child_node(&mut self) -> Result<Spanned<tree_node::Expression<'a>>, Error<'a>> {
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
let start_span = self.current_span();
@@ -727,7 +698,7 @@ impl<'a> Parser<'a> {
self.parse_postfix(expr)
}
fn device(&mut self) -> Result<DeviceDeclarationExpression, Error> {
fn device(&mut self) -> Result<DeviceDeclarationExpression<'a>, Error<'a>> {
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
if !self_matches_current!(self, TokenType::Keyword(Keyword::Device)) {
return Err(Error::UnexpectedToken(
@@ -737,12 +708,12 @@ impl<'a> Parser<'a> {
}
let identifier_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
let identifier_span = Self::token_to_span(identifier_token);
let identifier_span = Self::token_to_span(&identifier_token);
let identifier = match identifier_token.token_type {
TokenType::Identifier(ref id) => id.clone(),
_ => {
return Err(Error::UnexpectedToken(
Self::token_to_span(identifier_token),
Self::token_to_span(&identifier_token),
identifier_token.clone(),
));
}
@@ -751,7 +722,7 @@ impl<'a> Parser<'a> {
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(current_token),
Self::token_to_span(&current_token),
current_token.clone(),
));
}
@@ -761,7 +732,7 @@ impl<'a> Parser<'a> {
TokenType::String(ref id) => id.clone(),
_ => {
return Err(Error::UnexpectedToken(
Self::token_to_span(device_token),
Self::token_to_span(&device_token),
device_token.clone(),
));
}
@@ -776,7 +747,10 @@ impl<'a> Parser<'a> {
})
}
fn infix(&mut self, previous: Spanned<Expression>) -> Result<Spanned<Expression>, Error> {
fn infix(
&mut self,
previous: Spanned<Expression<'a>>,
) -> Result<Spanned<Expression<'a>>, Error<'a>> {
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone();
match previous.node {
@@ -1084,7 +1058,7 @@ impl<'a> Parser<'a> {
expressions.pop().ok_or(Error::UnexpectedEOF)
}
fn priority(&mut self) -> Result<Option<Box<Spanned<Expression>>>, Error> {
fn priority(&mut self) -> Result<Option<Box<Spanned<Expression<'a>>>>, Error<'a>> {
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) {
return Err(Error::UnexpectedToken(
@@ -1099,15 +1073,15 @@ impl<'a> Parser<'a> {
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(current_token, TokenType::Symbol(Symbol::RParen)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(current_token),
current_token.clone(),
Self::token_to_span(&current_token),
current_token,
));
}
Ok(Some(boxed!(expression)))
}
fn invocation(&mut self) -> Result<InvocationExpression, Error> {
fn invocation(&mut self) -> Result<InvocationExpression<'a>, Error<'a>> {
let identifier_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
let identifier_span = Self::token_to_span(identifier_token);
let identifier = match identifier_token.token_type {
@@ -1123,8 +1097,8 @@ impl<'a> Parser<'a> {
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(current_token),
current_token.clone(),
Self::token_to_span(&current_token),
current_token,
));
}
@@ -1150,8 +1124,8 @@ impl<'a> Parser<'a> {
{
let next_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
return Err(Error::UnexpectedToken(
Self::token_to_span(next_token),
next_token.clone(),
Self::token_to_span(&next_token),
next_token,
));
}
@@ -1169,7 +1143,7 @@ impl<'a> Parser<'a> {
})
}
fn block(&mut self) -> Result<BlockExpression, Error> {
fn block(&mut self) -> Result<BlockExpression<'a>, Error<'a>> {
let mut expressions = Vec::<Spanned<Expression>>::new();
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
@@ -1192,7 +1166,7 @@ impl<'a> Parser<'a> {
if token_matches!(current_token, TokenType::Keyword(Keyword::Return)) {
// Need to capture return span
let ret_start_span = Self::token_to_span(current_token);
let ret_start_span = Self::token_to_span(&current_token);
self.assign_next()?;
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
@@ -1211,25 +1185,19 @@ impl<'a> Parser<'a> {
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::RBrace)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
}
Ok(BlockExpression(expressions))
}
fn const_declaration(&mut self) -> Result<ConstDeclarationExpression, Error> {
fn const_declaration(&mut self) -> Result<ConstDeclarationExpression<'a>, Error<'a>> {
// const
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
if !self_matches_current!(self, TokenType::Keyword(Keyword::Const)) {
@@ -1241,7 +1209,7 @@ impl<'a> Parser<'a> {
// variable_name
let ident_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
let ident_span = Self::token_to_span(ident_token);
let ident_span = Self::token_to_span(&ident_token);
let ident = match ident_token.token_type {
TokenType::Identifier(ref id) => id.clone(),
_ => return Err(Error::UnexpectedToken(ident_span, ident_token.clone())),
@@ -1299,7 +1267,7 @@ impl<'a> Parser<'a> {
}
}
fn declaration(&mut self) -> Result<Expression, Error> {
fn declaration(&mut self) -> Result<Expression<'a>, Error<'a>> {
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) {
return Err(Error::UnexpectedToken(
@@ -1308,13 +1276,13 @@ impl<'a> Parser<'a> {
));
}
let identifier_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
let identifier_span = Self::token_to_span(identifier_token);
let identifier_span = Self::token_to_span(&identifier_token);
let identifier = match identifier_token.token_type {
TokenType::Identifier(ref id) => id.clone(),
_ => {
return Err(Error::UnexpectedToken(
Self::token_to_span(identifier_token),
identifier_token.clone(),
Self::token_to_span(&identifier_token),
identifier_token,
));
}
};
@@ -1334,8 +1302,8 @@ impl<'a> Parser<'a> {
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(current_token, TokenType::Symbol(Symbol::Semicolon)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(current_token),
current_token.clone(),
Self::token_to_span(&current_token),
current_token,
));
}
@@ -1348,7 +1316,7 @@ impl<'a> Parser<'a> {
))
}
fn literal(&mut self) -> Result<Literal, Error> {
fn literal(&mut self) -> Result<Literal<'a>, Error<'a>> {
let current_token = self.current_token.clone().ok_or(Error::UnexpectedEOF)?;
let literal = match current_token.token_type {
TokenType::Number(num) => Literal::Number(num),
@@ -1358,11 +1326,11 @@ impl<'a> Parser<'a> {
Some(Token {
token_type: TokenType::Number(num),
..
}) => Literal::Number(-*num),
}) => Literal::Number(-num),
Some(wrong_token) => {
return Err(Error::UnexpectedToken(
Self::token_to_span(wrong_token),
wrong_token.clone(),
Self::token_to_span(&wrong_token),
wrong_token,
));
}
None => return Err(Error::UnexpectedEOF),
@@ -1378,14 +1346,11 @@ impl<'a> Parser<'a> {
Ok(literal)
}
fn if_expression(&mut self) -> Result<IfExpression, Error> {
fn if_expression(&mut self) -> Result<IfExpression<'a>, Error<'a>> {
// 'if' is current
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::LParen)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
self.assign_next()?;
@@ -1393,18 +1358,12 @@ impl<'a> Parser<'a> {
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::RParen)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::LBrace)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
let body = self.spanned(|p| p.block())?;
@@ -1429,10 +1388,7 @@ impl<'a> Parser<'a> {
}))
} else {
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
} else {
None
@@ -1445,13 +1401,10 @@ impl<'a> Parser<'a> {
})
}
fn loop_expression(&mut self) -> Result<LoopExpression, Error> {
fn loop_expression(&mut self) -> Result<LoopExpression<'a>, Error<'a>> {
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::LBrace)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
let body = self.spanned(|p| p.block())?;
@@ -1459,13 +1412,10 @@ impl<'a> Parser<'a> {
Ok(LoopExpression { body })
}
fn while_expression(&mut self) -> Result<WhileExpression, Error> {
fn while_expression(&mut self) -> Result<WhileExpression<'a>, Error<'a>> {
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::LParen)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
self.assign_next()?;
@@ -1473,18 +1423,12 @@ impl<'a> Parser<'a> {
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::RParen)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(next, TokenType::Symbol(Symbol::LBrace)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
let body = self.block()?;
@@ -1495,29 +1439,26 @@ impl<'a> Parser<'a> {
})
}
fn function(&mut self) -> Result<FunctionExpression, Error> {
fn function(&mut self) -> Result<FunctionExpression<'a>, Error<'a>> {
// 'fn' is current
let fn_ident_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
let fn_ident_span = Self::token_to_span(fn_ident_token);
let fn_ident_span = Self::token_to_span(&fn_ident_token);
let fn_ident = match fn_ident_token.token_type {
TokenType::Identifier(ref id) => id.clone(),
_ => {
return Err(Error::UnexpectedToken(
Self::token_to_span(fn_ident_token),
fn_ident_token.clone(),
));
return Err(Error::UnexpectedToken(fn_ident_span, fn_ident_token));
}
};
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(current_token, TokenType::Symbol(Symbol::LParen)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(current_token),
current_token.clone(),
Self::token_to_span(&current_token),
current_token,
));
}
let mut arguments = Vec::<Spanned<String>>::new();
let mut arguments = Vec::<Spanned<Cow<'a, str>>>::new();
while !token_matches!(
self.get_next()?.ok_or(Error::UnexpectedEOF)?,
@@ -1528,10 +1469,7 @@ impl<'a> Parser<'a> {
let argument = match current_token.token_type {
TokenType::Identifier(ref id) => id.clone(),
_ => {
return Err(Error::UnexpectedToken(
Self::token_to_span(current_token),
current_token.clone(),
));
return Err(Error::UnexpectedToken(arg_span, current_token.clone()));
}
};
@@ -1553,10 +1491,7 @@ impl<'a> Parser<'a> {
&& !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen))
{
let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
return Err(Error::UnexpectedToken(
Self::token_to_span(next),
next.clone(),
));
return Err(Error::UnexpectedToken(Self::token_to_span(&next), next));
}
if !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) {
@@ -1567,8 +1502,8 @@ impl<'a> Parser<'a> {
let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
if !token_matches!(current_token, TokenType::Symbol(Symbol::LBrace)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(current_token),
current_token.clone(),
Self::token_to_span(&current_token),
current_token,
));
};
@@ -1582,15 +1517,15 @@ impl<'a> Parser<'a> {
})
}
fn syscall(&mut self) -> Result<SysCall, Error> {
fn check_length(
parser: &Parser,
arguments: &[Spanned<Expression>],
fn syscall(&mut self) -> Result<SysCall<'a>, Error<'a>> {
fn check_length<'a>(
span: Span,
arguments: &[Spanned<Expression<'a>>],
length: usize,
) -> Result<(), Error> {
) -> Result<(), Error<'a>> {
if arguments.len() != length {
return Err(Error::InvalidSyntax(
parser.current_span(),
span,
format!("Expected {} arguments", length),
));
}
@@ -1648,20 +1583,20 @@ impl<'a> Parser<'a> {
let invocation = self.invocation()?;
match invocation.name.node.as_str() {
match invocation.name.node.as_ref() {
// System SysCalls
"yield" => {
check_length(self, &invocation.arguments, 0)?;
check_length(self.current_span(), &invocation.arguments, 0)?;
Ok(SysCall::System(sys_call::System::Yield))
}
"sleep" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut arg = invocation.arguments.into_iter();
let expr = arg.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::System(System::Sleep(boxed!(expr))))
}
"hash" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let lit_str = literal_or_variable!(args.next());
@@ -1682,7 +1617,7 @@ impl<'a> Parser<'a> {
})))
}
"load" | "l" => {
check_length(self, &invocation.arguments, 2)?;
check_length(self.current_span(), &invocation.arguments, 2)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next();
@@ -1727,7 +1662,7 @@ impl<'a> Parser<'a> {
)))
}
"loadBatched" | "lb" => {
check_length(self, &invocation.arguments, 3)?;
check_length(self.current_span(), &invocation.arguments, 3)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next();
let device_hash = literal_or_variable!(tmp);
@@ -1745,7 +1680,7 @@ impl<'a> Parser<'a> {
)))
}
"loadBatchedNamed" | "lbn" => {
check_length(self, &invocation.arguments, 4)?;
check_length(self.current_span(), &invocation.arguments, 4)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next();
let dev_hash = literal_or_variable!(tmp);
@@ -1764,7 +1699,7 @@ impl<'a> Parser<'a> {
)))
}
"set" | "s" => {
check_length(self, &invocation.arguments, 3)?;
check_length(self.current_span(), &invocation.arguments, 3)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next();
let device = literal_or_variable!(tmp);
@@ -1776,14 +1711,16 @@ impl<'a> Parser<'a> {
Ok(SysCall::System(sys_call::System::SetOnDevice(
device,
Spanned {
node: Literal::String(logic_type.node.to_string().replace("\"", "")),
node: Literal::String(Cow::from(
logic_type.node.to_string().replace("\"", ""),
)),
span: logic_type.span,
},
boxed!(variable),
)))
}
"setBatched" | "sb" => {
check_length(self, &invocation.arguments, 3)?;
check_length(self.current_span(), &invocation.arguments, 3)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next();
let device_hash = literal_or_variable!(tmp);
@@ -1795,14 +1732,14 @@ impl<'a> Parser<'a> {
Ok(SysCall::System(sys_call::System::SetOnDeviceBatched(
device_hash,
Spanned {
node: Literal::String(logic_type.to_string().replace("\"", "")),
node: Literal::String(Cow::from(logic_type.to_string().replace("\"", ""))),
span: logic_type.span,
},
boxed!(variable),
)))
}
"setBatchedNamed" | "sbn" => {
check_length(self, &invocation.arguments, 4)?;
check_length(self.current_span(), &invocation.arguments, 4)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next();
let device_hash = literal_or_variable!(tmp);
@@ -1825,28 +1762,28 @@ impl<'a> Parser<'a> {
}
// Math SysCalls
"acos" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Acos(boxed!(tmp))))
}
"asin" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let tmp = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Asin(boxed!(tmp))))
}
"atan" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let expr = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Atan(boxed!(expr))))
}
"atan2" => {
check_length(self, &invocation.arguments, 2)?;
check_length(self.current_span(), &invocation.arguments, 2)?;
let mut args = invocation.arguments.into_iter();
let arg1 = args.next().ok_or(Error::UnexpectedEOF)?;
let arg2 = args.next().ok_or(Error::UnexpectedEOF)?;
@@ -1854,42 +1791,42 @@ impl<'a> Parser<'a> {
Ok(SysCall::Math(Math::Atan2(boxed!(arg1), boxed!(arg2))))
}
"abs" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let expr = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Abs(boxed!(expr))))
}
"ceil" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Ceil(boxed!(arg))))
}
"cos" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Cos(boxed!(arg))))
}
"floor" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Floor(boxed!(arg))))
}
"log" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Log(boxed!(arg))))
}
"max" => {
check_length(self, &invocation.arguments, 2)?;
check_length(self.current_span(), &invocation.arguments, 2)?;
let mut args = invocation.arguments.into_iter();
let arg1 = args.next().ok_or(Error::UnexpectedEOF)?;
let arg2 = args.next().ok_or(Error::UnexpectedEOF)?;
@@ -1897,7 +1834,7 @@ impl<'a> Parser<'a> {
Ok(SysCall::Math(Math::Max(boxed!(arg1), boxed!(arg2))))
}
"min" => {
check_length(self, &invocation.arguments, 2)?;
check_length(self.current_span(), &invocation.arguments, 2)?;
let mut args = invocation.arguments.into_iter();
let arg1 = args.next().ok_or(Error::UnexpectedEOF)?;
let arg2 = args.next().ok_or(Error::UnexpectedEOF)?;
@@ -1905,32 +1842,32 @@ impl<'a> Parser<'a> {
Ok(SysCall::Math(Math::Min(boxed!(arg1), boxed!(arg2))))
}
"rand" => {
check_length(self, &invocation.arguments, 0)?;
check_length(self.current_span(), &invocation.arguments, 0)?;
Ok(SysCall::Math(Math::Rand))
}
"sin" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Sin(boxed!(arg))))
}
"sqrt" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Sqrt(boxed!(arg))))
}
"tan" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::Math(Math::Tan(boxed!(arg))))
}
"trunc" => {
check_length(self, &invocation.arguments, 1)?;
check_length(self.current_span(), &invocation.arguments, 1)?;
let mut args = invocation.arguments.into_iter();
let arg = args.next().ok_or(Error::UnexpectedEOF)?;

View File

@@ -4,73 +4,73 @@ use helpers::prelude::*;
documented! {
#[derive(Debug, PartialEq, Eq)]
pub enum Math {
pub enum Math<'a> {
/// Returns the angle in radians whose cosine is the specified number.
/// ## IC10
/// `acos r? a(r?|num)`
/// ## Slang
/// `let item = acos(number|var|expression);`
Acos(Box<Spanned<Expression>>),
Acos(Box<Spanned<Expression<'a>>>),
/// Returns the angle in radians whose sine is the specified number.
/// ## IC10
/// `asin r? a(r?|num)`
/// ## Slang
/// `let item = asin(number|var|expression);`
Asin(Box<Spanned<Expression>>),
Asin(Box<Spanned<Expression<'a>>>),
/// Returns the angle in radians whose tangent is the specified number.
/// ## IC10
/// `atan r? a(r?|num)`
/// ## Slang
/// `let item = atan(number|var|expression);`
Atan(Box<Spanned<Expression>>),
Atan(Box<Spanned<Expression<'a>>>),
/// Returns the angle in radians whose tangent is the quotient of the specified numbers.
/// ## IC10
/// `atan2 r? a(r?|num) b(r?|num)`
/// ## Slang
/// `let item = atan2((number|var|expression), (number|var|expression));`
Atan2(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Atan2(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
/// Gets the absolute value of a number.
/// ## IC10
/// `abs r? a(r?|num)`
/// ## Slang
/// `let item = abs((number|var|expression));`
Abs(Box<Spanned<Expression>>),
Abs(Box<Spanned<Expression<'a>>>),
/// Rounds a number up to the nearest whole number.
/// ## IC10
/// `ceil r? a(r?|num)`
/// ## Slang
/// `let item = ceil((number|var|expression));`
Ceil(Box<Spanned<Expression>>),
Ceil(Box<Spanned<Expression<'a>>>),
/// Returns the cosine of the specified angle in radians.
/// ## IC10
/// `cos r? a(r?|num)`
/// ## Slang
/// `let item = cos((number|var|expression));`
Cos(Box<Spanned<Expression>>),
Cos(Box<Spanned<Expression<'a>>>),
/// Rounds a number down to the nearest whole number.
/// ## IC10
/// `floor r? a(r?|num)`
/// ## Slang
/// `let item = floor((number|var|expression));`
Floor(Box<Spanned<Expression>>),
Floor(Box<Spanned<Expression<'a>>>),
/// Computes the natural logarithm of a number.
/// ## IC10
/// `log r? a(r?|num)`
/// ## Slang
/// `let item = log((number|var|expression));`
Log(Box<Spanned<Expression>>),
Log(Box<Spanned<Expression<'a>>>),
/// Computes the maximum of two numbers.
/// ## IC10
/// `max r? a(r?|num) b(r?|num)`
/// ## Slang
/// `let item = max((number|var|expression), (number|var|expression));`
Max(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Max(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
/// Computes the minimum of two numbers.
/// ## IC10
/// `min r? a(r?|num) b(r?|num)`
/// ## Slang
/// `let item = min((number|var|expression), (number|var|expression));`
Min(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Min(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
/// Gets a random number between 0 and 1.
/// ## IC10
/// `rand r?`
@@ -82,29 +82,29 @@ documented! {
/// `sin r? a(r?|num)`
/// ## Slang
/// `let item = sin((number|var|expression));`
Sin(Box<Spanned<Expression>>),
Sin(Box<Spanned<Expression<'a>>>),
/// Computes the square root of a number.
/// ## IC10
/// `sqrt r? a(r?|num)`
/// ## Slang
/// `let item = sqrt((number|var|expression));`
Sqrt(Box<Spanned<Expression>>),
Sqrt(Box<Spanned<Expression<'a>>>),
/// Returns the tangent of the specified angle in radians.
/// ## IC10
/// `tan r? a(r?|num)`
/// ## Slang
/// `let item = tan((number|var|expression));`
Tan(Box<Spanned<Expression>>),
Tan(Box<Spanned<Expression<'a>>>),
/// Truncates a number by removing the decimal portion.
/// ## IC10
/// `trunc r? a(r?|num)`
/// ## Slang
/// `let item = trunc((number|var|expression));`
Trunc(Box<Spanned<Expression>>),
Trunc(Box<Spanned<Expression<'a>>>),
}
}
impl std::fmt::Display for Math {
impl<'a> std::fmt::Display for Math<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Math::Acos(a) => write!(f, "acos({})", a),
@@ -129,7 +129,7 @@ impl std::fmt::Display for Math {
documented! {
#[derive(Debug, PartialEq, Eq)]
pub enum System {
pub enum System<'a> {
/// Pauses execution for exactly 1 tick and then resumes.
/// ## IC10
/// `yield`
@@ -141,7 +141,7 @@ documented! {
/// `sleep a(r?|num)`
/// ## Slang
/// `sleep(number|var);`
Sleep(Box<Spanned<Expression>>),
Sleep(Box<Spanned<Expression<'a>>>),
/// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY
/// optimized away unless you bind it to a `let` variable. If you use a `const` variable
/// however, the hash is correctly computed at compile time and substitued automatically.
@@ -155,7 +155,7 @@ documented! {
/// const compDoor = hash("StructureCompositeDoor");
/// setOnDeviceBatched(compDoor, "Lock", true);
/// ```
Hash(Spanned<Literal>),
Hash(Spanned<Literal<'a>>),
/// Represents a function which loads a device variable into a register.
/// ## IC10
/// `l r? d? var`
@@ -163,7 +163,7 @@ documented! {
/// `let item = load(deviceHash, "LogicType");`
/// `let item = l(deviceHash, "LogicType");`
/// `let item = deviceAlias.LogicType;`
LoadFromDevice(Spanned<LiteralOrVariable>, Spanned<Literal>),
LoadFromDevice(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>),
/// Function which gets a LogicType from all connected network devices that match
/// the provided device hash and name, aggregating them via a batchMode
/// ## IC10
@@ -172,10 +172,10 @@ documented! {
/// `loadBatchedNamed(deviceHash, deviceName, "LogicType", "BatchMode");`
/// `lbn(deviceHash, deviceName, "LogicType", "BatchMode");`
LoadBatchNamed(
Spanned<LiteralOrVariable>,
Spanned<LiteralOrVariable>,
Spanned<Literal>,
Spanned<Literal>,
Spanned<LiteralOrVariable<'a>>,
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Spanned<Literal<'a>>,
),
/// Loads a LogicType from all connected network devices, aggregating them via a
/// BatchMode
@@ -184,7 +184,7 @@ documented! {
/// ## Slang
/// `loadBatched(deviceHash, "Variable", "LogicType");`
/// `lb(deviceHash, "Variable", "LogicType");`
LoadBatch(Spanned<LiteralOrVariable>, Spanned<Literal>, Spanned<Literal>),
LoadBatch(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>, Spanned<Literal<'a>>),
/// Represents a function which stores a setting into a specific device.
/// ## IC10
/// `s d? logicType r?`
@@ -192,7 +192,7 @@ documented! {
/// `set(deviceHash, "LogicType", (number|var));`
/// `s(deviceHash, "LogicType", (number|var));`
/// `deviceAlias.LogicType = (number|var);`
SetOnDevice(Spanned<LiteralOrVariable>, Spanned<Literal>, Box<Spanned<Expression>>),
SetOnDevice(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>, Box<Spanned<Expression<'a>>>),
/// Represents a function which stores a setting to all devices that match
/// the given deviceHash
/// ## IC10
@@ -200,7 +200,7 @@ documented! {
/// ## Slang
/// `setBatched(deviceHash, "LogicType", (number|var));`
/// `sb(deviceHash, "LogicType", (number|var));`
SetOnDeviceBatched(Spanned<LiteralOrVariable>, Spanned<Literal>, Box<Spanned<Expression>>),
SetOnDeviceBatched(Spanned<LiteralOrVariable<'a>>, Spanned<Literal<'a>>, Box<Spanned<Expression<'a>>>),
/// Represents a function which stores a setting to all devices that match
/// both the given deviceHash AND the given nameHash
/// ## IC10
@@ -209,15 +209,15 @@ documented! {
/// `setBatchedNamed(deviceHash, nameHash, "LogicType", (number|var));`
/// `sbn(deviceHash, nameHash, "LogicType", (number|var));`
SetOnDeviceBatchedNamed(
Spanned<LiteralOrVariable>,
Spanned<LiteralOrVariable>,
Spanned<Literal>,
Box<Spanned<Expression>>,
Spanned<LiteralOrVariable<'a>>,
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>>,
),
}
}
impl std::fmt::Display for System {
impl<'a> std::fmt::Display for System<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
System::Yield => write!(f, "yield()"),
@@ -242,13 +242,13 @@ impl std::fmt::Display for System {
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Eq)]
/// This represents built in functions that cannot be overwritten, but can be invoked by the user as functions.
pub enum SysCall {
System(System),
pub enum SysCall<'a> {
System(System<'a>),
/// Represents any mathmatical function that can be called.
Math(Math),
Math(Math<'a>),
}
impl Documentation for SysCall {
impl<'a> Documentation for SysCall<'a> {
fn docs(&self) -> String {
match self {
Self::System(s) => s.docs(),
@@ -264,7 +264,7 @@ impl Documentation for SysCall {
}
}
impl std::fmt::Display for SysCall {
impl<'a> std::fmt::Display for SysCall<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SysCall::System(s) => write!(f, "{}", s),
@@ -273,7 +273,7 @@ impl std::fmt::Display for SysCall {
}
}
impl SysCall {
impl<'a> SysCall<'a> {
pub fn is_syscall(identifier: &str) -> bool {
tokenizer::token::is_syscall(identifier)
}

View File

@@ -1,24 +1,22 @@
use std::ops::Deref;
use crate::sys_call;
use super::sys_call::SysCall;
use crate::sys_call;
use std::{borrow::Cow, ops::Deref};
use tokenizer::token::Number;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Literal {
pub enum Literal<'a> {
Number(Number),
String(String),
String(Cow<'a, str>),
Boolean(bool),
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum LiteralOr<T> {
Literal(Spanned<Literal>),
pub enum LiteralOr<'a, T> {
Literal(Spanned<Literal<'a>>),
Or(Spanned<T>),
}
impl<T: std::fmt::Display> std::fmt::Display for LiteralOr<T> {
impl<'a, T: std::fmt::Display> std::fmt::Display for LiteralOr<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Literal(l) => write!(f, "{l}"),
@@ -27,7 +25,7 @@ impl<T: std::fmt::Display> std::fmt::Display for LiteralOr<T> {
}
}
impl std::fmt::Display for Literal {
impl<'a> std::fmt::Display for Literal<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Literal::Number(n) => write!(f, "{}", n),
@@ -38,16 +36,16 @@ impl std::fmt::Display for Literal {
}
#[derive(Debug, PartialEq, Eq)]
pub enum BinaryExpression {
Add(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Multiply(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Divide(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Subtract(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Exponent(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Modulo(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
pub enum BinaryExpression<'a> {
Add(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Multiply(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Divide(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Subtract(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Exponent(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Modulo(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
}
impl std::fmt::Display for BinaryExpression {
impl<'a> std::fmt::Display for BinaryExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BinaryExpression::Add(l, r) => write!(f, "({} + {})", l, r),
@@ -61,19 +59,19 @@ impl std::fmt::Display for BinaryExpression {
}
#[derive(Debug, PartialEq, Eq)]
pub enum LogicalExpression {
And(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Or(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
Not(Box<Spanned<Expression>>),
Equal(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
NotEqual(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
GreaterThan(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
GreaterThanOrEqual(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
LessThan(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
LessThanOrEqual(Box<Spanned<Expression>>, Box<Spanned<Expression>>),
pub enum LogicalExpression<'a> {
And(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Or(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Not(Box<Spanned<Expression<'a>>>),
Equal(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
NotEqual(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
GreaterThan(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
GreaterThanOrEqual(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
LessThan(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
LessThanOrEqual(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
}
impl std::fmt::Display for LogicalExpression {
impl<'a> std::fmt::Display for LogicalExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LogicalExpression::And(l, r) => write!(f, "({} && {})", l, r),
@@ -90,25 +88,25 @@ impl std::fmt::Display for LogicalExpression {
}
#[derive(Debug, PartialEq, Eq)]
pub struct AssignmentExpression {
pub assignee: Box<Spanned<Expression>>,
pub expression: Box<Spanned<Expression>>,
pub struct AssignmentExpression<'a> {
pub assignee: Box<Spanned<Expression<'a>>>,
pub expression: Box<Spanned<Expression<'a>>>,
}
impl std::fmt::Display for AssignmentExpression {
impl<'a> std::fmt::Display for AssignmentExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({} = {})", self.assignee, self.expression)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct FunctionExpression {
pub name: Spanned<String>,
pub arguments: Vec<Spanned<String>>,
pub body: BlockExpression,
pub struct FunctionExpression<'a> {
pub name: Spanned<Cow<'a, str>>,
pub arguments: Vec<Spanned<Cow<'a, str>>>,
pub body: BlockExpression<'a>,
}
impl std::fmt::Display for FunctionExpression {
impl<'a> std::fmt::Display for FunctionExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
@@ -125,9 +123,9 @@ impl std::fmt::Display for FunctionExpression {
}
#[derive(Debug, PartialEq, Eq)]
pub struct BlockExpression(pub Vec<Spanned<Expression>>);
pub struct BlockExpression<'a>(pub Vec<Spanned<Expression<'a>>>);
impl std::fmt::Display for BlockExpression {
impl<'a> std::fmt::Display for BlockExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
@@ -142,12 +140,12 @@ impl std::fmt::Display for BlockExpression {
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvocationExpression {
pub name: Spanned<String>,
pub arguments: Vec<Spanned<Expression>>,
pub struct InvocationExpression<'a> {
pub name: Spanned<Cow<'a, str>>,
pub arguments: Vec<Spanned<Expression<'a>>>,
}
impl std::fmt::Display for InvocationExpression {
impl<'a> std::fmt::Display for InvocationExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
@@ -163,25 +161,25 @@ impl std::fmt::Display for InvocationExpression {
}
#[derive(Debug, PartialEq, Eq)]
pub struct MemberAccessExpression {
pub object: Box<Spanned<Expression>>,
pub member: Spanned<String>,
pub struct MemberAccessExpression<'a> {
pub object: Box<Spanned<Expression<'a>>>,
pub member: Spanned<Cow<'a, str>>,
}
impl std::fmt::Display for MemberAccessExpression {
impl<'a> std::fmt::Display for MemberAccessExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}", self.object, self.member)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct MethodCallExpression {
pub object: Box<Spanned<Expression>>,
pub method: Spanned<String>,
pub arguments: Vec<Spanned<Expression>>,
pub struct MethodCallExpression<'a> {
pub object: Box<Spanned<Expression<'a>>>,
pub method: Spanned<Cow<'a, str>>,
pub arguments: Vec<Spanned<Expression<'a>>>,
}
impl std::fmt::Display for MethodCallExpression {
impl<'a> std::fmt::Display for MethodCallExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
@@ -198,12 +196,12 @@ impl std::fmt::Display for MethodCallExpression {
}
#[derive(Debug, PartialEq, Eq)]
pub enum LiteralOrVariable {
Literal(Literal),
Variable(Spanned<String>),
pub enum LiteralOrVariable<'a> {
Literal(Literal<'a>),
Variable(Spanned<Cow<'a, str>>),
}
impl std::fmt::Display for LiteralOrVariable {
impl<'a> std::fmt::Display for LiteralOrVariable<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LiteralOrVariable::Literal(l) => write!(f, "{}", l),
@@ -213,46 +211,46 @@ impl std::fmt::Display for LiteralOrVariable {
}
#[derive(Debug, PartialEq, Eq)]
pub struct ConstDeclarationExpression {
pub name: Spanned<String>,
pub value: LiteralOr<SysCall>,
pub struct ConstDeclarationExpression<'a> {
pub name: Spanned<Cow<'a, str>>,
pub value: LiteralOr<'a, SysCall<'a>>,
}
impl ConstDeclarationExpression {
impl<'a> ConstDeclarationExpression<'a> {
pub fn is_syscall_supported(call: &SysCall) -> bool {
use sys_call::System;
matches!(call, SysCall::System(sys) if matches!(sys, System::Hash(_)))
}
}
impl std::fmt::Display for ConstDeclarationExpression {
impl<'a> std::fmt::Display for ConstDeclarationExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(const {} = {})", self.name, self.value)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct DeviceDeclarationExpression {
pub struct DeviceDeclarationExpression<'a> {
/// any variable-like name
pub name: Spanned<String>,
pub name: Spanned<Cow<'a, str>>,
/// The device port, ex. (db, d0, d1, d2, d3, d4, d5)
pub device: String,
pub device: Cow<'a, str>,
}
impl std::fmt::Display for DeviceDeclarationExpression {
impl<'a> std::fmt::Display for DeviceDeclarationExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(device {} = {})", self.name, self.device)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct IfExpression {
pub condition: Box<Spanned<Expression>>,
pub body: Spanned<BlockExpression>,
pub else_branch: Option<Box<Spanned<Expression>>>,
pub struct IfExpression<'a> {
pub condition: Box<Spanned<Expression<'a>>>,
pub body: Spanned<BlockExpression<'a>>,
pub else_branch: Option<Box<Spanned<Expression<'a>>>>,
}
impl std::fmt::Display for IfExpression {
impl<'a> std::fmt::Display for IfExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(if ({}) {}", self.condition, self.body)?;
if let Some(else_branch) = &self.else_branch {
@@ -263,23 +261,23 @@ impl std::fmt::Display for IfExpression {
}
#[derive(Debug, PartialEq, Eq)]
pub struct LoopExpression {
pub body: Spanned<BlockExpression>,
pub struct LoopExpression<'a> {
pub body: Spanned<BlockExpression<'a>>,
}
impl std::fmt::Display for LoopExpression {
impl<'a> std::fmt::Display for LoopExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(loop {})", self.body)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct WhileExpression {
pub condition: Box<Spanned<Expression>>,
pub body: BlockExpression,
pub struct WhileExpression<'a> {
pub condition: Box<Spanned<Expression<'a>>>,
pub body: BlockExpression<'a>,
}
impl std::fmt::Display for WhileExpression {
impl<'a> std::fmt::Display for WhileExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(while {} {})", self.condition, self.body)
}
@@ -347,32 +345,32 @@ impl<T> Deref for Spanned<T> {
}
#[derive(Debug, PartialEq, Eq)]
pub enum Expression {
Assignment(Spanned<AssignmentExpression>),
Binary(Spanned<BinaryExpression>),
Block(Spanned<BlockExpression>),
pub enum Expression<'a> {
Assignment(Spanned<AssignmentExpression<'a>>),
Binary(Spanned<BinaryExpression<'a>>),
Block(Spanned<BlockExpression<'a>>),
Break(Span),
ConstDeclaration(Spanned<ConstDeclarationExpression>),
ConstDeclaration(Spanned<ConstDeclarationExpression<'a>>),
Continue(Span),
Declaration(Spanned<String>, Box<Spanned<Expression>>),
DeviceDeclaration(Spanned<DeviceDeclarationExpression>),
Function(Spanned<FunctionExpression>),
If(Spanned<IfExpression>),
Invocation(Spanned<InvocationExpression>),
Literal(Spanned<Literal>),
Logical(Spanned<LogicalExpression>),
Loop(Spanned<LoopExpression>),
MemberAccess(Spanned<MemberAccessExpression>),
MethodCall(Spanned<MethodCallExpression>),
Negation(Box<Spanned<Expression>>),
Priority(Box<Spanned<Expression>>),
Return(Box<Spanned<Expression>>),
Syscall(Spanned<SysCall>),
Variable(Spanned<String>),
While(Spanned<WhileExpression>),
Declaration(Spanned<Cow<'a, str>>, Box<Spanned<Expression<'a>>>),
DeviceDeclaration(Spanned<DeviceDeclarationExpression<'a>>),
Function(Spanned<FunctionExpression<'a>>),
If(Spanned<IfExpression<'a>>),
Invocation(Spanned<InvocationExpression<'a>>),
Literal(Spanned<Literal<'a>>),
Logical(Spanned<LogicalExpression<'a>>),
Loop(Spanned<LoopExpression<'a>>),
MemberAccess(Spanned<MemberAccessExpression<'a>>),
MethodCall(Spanned<MethodCallExpression<'a>>),
Negation(Box<Spanned<Expression<'a>>>),
Priority(Box<Spanned<Expression<'a>>>),
Return(Box<Spanned<Expression<'a>>>),
Syscall(Spanned<SysCall<'a>>),
Variable(Spanned<Cow<'a, str>>),
While(Spanned<WhileExpression<'a>>),
}
impl std::fmt::Display for Expression {
impl<'a> std::fmt::Display for Expression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expression::Assignment(e) => write!(f, "{}", e),

View File

@@ -5,9 +5,10 @@ edition = "2024"
[dependencies]
rust_decimal = { workspace = true }
quick-error = { workspace = true }
lsp-types = { workspace = true }
thiserror = { workspace = true }
helpers = { path = "../helpers" }
logos = "0.16"
[dev-dependencies]
anyhow = { version = "^1" }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,59 @@
use std::borrow::Cow;
use helpers::prelude::*;
use logos::{Lexer, Logos, Skip, Span};
use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
use rust_decimal::Decimal;
use thiserror::Error;
#[derive(Debug, Error, Default, Clone, PartialEq)]
pub enum LexError {
#[error("Attempted to parse an invalid number: {2}")]
NumberParse(usize, Span, String),
#[error("An invalid character was found in token stream: {2}")]
InvalidInput(usize, Span, String),
#[default]
#[error("An unknown error occurred")]
Other,
}
impl From<LexError> for Diagnostic {
fn from(value: LexError) -> Self {
match value {
LexError::NumberParse(line, col, str) | LexError::InvalidInput(line, col, str) => {
Diagnostic {
range: Range {
start: Position {
character: col.start as u32,
line: line as u32,
},
end: Position {
line: line as u32,
character: col.end as u32,
},
},
severity: Some(DiagnosticSeverity::ERROR),
message: str,
..Default::default()
}
}
_ => todo!(),
}
}
}
impl LexError {
pub fn from_lexer<'a>(lex: &mut Lexer<'a, TokenType<'a>>) -> Self {
let mut span = lex.span();
let line = lex.extras.line_count;
span.start -= lex.extras.line_start_index;
span.end -= lex.extras.line_start_index;
Self::InvalidInput(line, span, lex.slice().chars().as_str().to_string())
}
}
// Define a local macro to consume the list
macro_rules! generate_check {
@@ -10,29 +64,40 @@ macro_rules! generate_check {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Token {
/// The type of the token
pub token_type: TokenType,
/// The line where the token was found
pub line: usize,
/// The column where the token was found
pub column: usize,
pub original_string: Option<String>,
#[derive(Default)]
pub struct Extras {
pub line_count: usize,
pub line_start_index: usize,
}
impl Token {
pub fn new(
token_type: TokenType,
line: usize,
column: usize,
original: Option<String>,
) -> Self {
fn update_line_index<'a>(lex: &mut Lexer<'a, TokenType<'a>>) -> Skip {
lex.extras.line_count += 1;
lex.extras.line_start_index = lex.span().end;
Skip
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Token<'a> {
/// The type of the token
pub token_type: TokenType<'a>,
/// The line where the token was found
pub line: usize,
/// The span where the token starts and ends
pub span: Span,
}
impl<'a> std::fmt::Display for Token<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.token_type)
}
}
impl<'a> Token<'a> {
pub fn new(token_type: TokenType<'a>, line: usize, span: Span) -> Self {
Self {
token_type,
line,
column,
original_string: original,
span,
}
}
}
@@ -79,25 +144,186 @@ impl Temperature {
}
}
#[derive(Debug, PartialEq, Hash, Eq, Clone)]
pub enum TokenType {
macro_rules! symbol {
($var:ident) => {
|_| Symbol::$var
};
}
macro_rules! keyword {
($var:ident) => {
|_| Keyword::$var
};
}
#[derive(Debug, PartialEq, Hash, Eq, Clone, Logos)]
#[logos(skip r"[ \t\f]+")]
#[logos(extras = Extras)]
#[logos(error(LexError, LexError::from_lexer))]
pub enum TokenType<'a> {
#[regex(r"\n", update_line_index)]
Newline,
// matches strings with double quotes
#[regex(r#""(?:[^"\\]|\\.)*""#, |v| {
let str = v.slice();
Cow::from(&str[1..str.len() - 1])
})]
// matches strings with single quotes
#[regex(r#"'(?:[^'\\]|\\.)*'"#, |v| {
let str = v.slice();
Cow::from(&str[1..str.len() - 1])
})]
/// Represents a string token
String(String),
String(Cow<'a, str>),
#[regex(r"[0-9][0-9_]*(\.[0-9][0-9_]*)?([cfk])?", parse_number)]
/// Represents a number token
Number(Number),
#[token("true", |_| true)]
#[token("false", |_| false)]
/// Represents a boolean token
Boolean(bool),
#[token("continue", keyword!(Continue))]
#[token("const", keyword!(Const))]
#[token("let", keyword!(Let))]
#[token("fn", keyword!(Fn))]
#[token("if", keyword!(If))]
#[token("device", keyword!(Device))]
#[token("else", keyword!(Else))]
#[token("return", keyword!(Return))]
#[token("enum", keyword!(Enum))]
#[token("loop", keyword!(Loop))]
#[token("break", keyword!(Break))]
#[token("while", keyword!(While))]
/// Represents a keyword token
Keyword(Keyword),
#[regex(r"[a-zA-Z_][a-zA-Z0-9_]*", |v| Cow::from(v.slice()))]
/// Represents an identifier token
Identifier(String),
Identifier(Cow<'a, str>),
#[token("(", symbol!(LParen))]
#[token(")", symbol!(RParen))]
#[token("{", symbol!(LBrace))]
#[token("}", symbol!(RBrace))]
#[token("[", symbol!(LBracket))]
#[token("]", symbol!(RBracket))]
#[token(";", symbol!(Semicolon))]
#[token(":", symbol!(Colon))]
#[token(",", symbol!(Comma))]
#[token("+", symbol!(Plus))]
#[token("-", symbol!(Minus))]
#[token("*", symbol!(Asterisk))]
#[token("/", symbol!(Slash))]
#[token("<", symbol!(LessThan))]
#[token(">", symbol!(GreaterThan))]
#[token("=", symbol!(Assign))]
#[token("!", symbol!(LogicalNot))]
#[token(".", symbol!(Dot))]
#[token("^", symbol!(Caret))]
#[token("%", symbol!(Percent))]
#[token("==", symbol!(Equal))]
#[token("!=", symbol!(NotEqual))]
#[token("&&", symbol!(LogicalAnd))]
#[token("||", symbol!(LogicalOr))]
#[token("<=", symbol!(LessThanOrEqual))]
#[token(">=", symbol!(GreaterThanOrEqual))]
#[token("**", symbol!(Exp))]
/// Represents a symbol token
Symbol(Symbol),
#[token("//", |lex| Comment::Line(read_line(lex)))]
#[token("///", |lex| Comment::Doc(read_line(lex)))]
/// Represents a comment, both a line comment and a doc comment
Comment(Comment<'a>),
#[end]
/// Represents an end of file token
EOF,
}
impl Documentation for TokenType {
fn read_line<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Cow<'a, str> {
let rem = lexer.remainder();
let len = rem.find('\n').unwrap_or(rem.len());
let content = rem[..len].trim().to_string();
lexer.bump(len);
Cow::from(content)
}
#[derive(Hash, Debug, Eq, PartialEq, Clone)]
pub enum Comment<'a> {
Line(Cow<'a, str>),
Doc(Cow<'a, str>),
}
fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexError> {
let slice = lexer.slice();
let last_char = slice.chars().last().unwrap_or_default();
let (num_str, suffix) = match last_char {
'c' | 'k' | 'f' => (&slice[..slice.len() - 1], Some(last_char)),
_ => (slice, None),
};
let clean_str = if num_str.contains('_') {
num_str.replace('_', "")
} else {
num_str.to_string()
};
let line = lexer.extras.line_count;
let mut span = lexer.span();
span.end -= lexer.extras.line_start_index;
span.start -= lexer.extras.line_start_index;
let num = if clean_str.contains('.') {
Number::Decimal(
clean_str
.parse::<Decimal>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
)
} else {
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)
}
}
impl<'a> std::fmt::Display for Comment<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Line(c) => write!(f, "// {}", c),
Self::Doc(d) => {
let lines = d
.split('\n')
.map(|s| format!("/// {s}"))
.collect::<Vec<_>>()
.join("\n");
write!(f, "{}", lines)
}
}
}
}
impl<'a> Documentation for TokenType<'a> {
fn docs(&self) -> String {
match self {
Self::Keyword(k) => k.docs(),
@@ -112,7 +338,7 @@ impl Documentation for TokenType {
helpers::with_syscalls!(generate_check);
impl From<TokenType> for u32 {
impl<'a> From<TokenType<'a>> for u32 {
fn from(value: TokenType) -> Self {
match value {
TokenType::String(_) => 1,
@@ -128,6 +354,7 @@ impl From<TokenType> for u32 {
| Keyword::Return => 4,
_ => 5,
},
TokenType::Comment(_) => 8,
TokenType::Identifier(s) => {
if is_syscall(&s) {
10
@@ -146,12 +373,12 @@ impl From<TokenType> for u32 {
7
}
}
TokenType::EOF => 0,
_ => 0,
}
}
}
impl std::fmt::Display for TokenType {
impl<'a> std::fmt::Display for TokenType<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TokenType::String(s) => write!(f, "{}", s),
@@ -160,7 +387,9 @@ impl std::fmt::Display for TokenType {
TokenType::Keyword(k) => write!(f, "{:?}", k),
TokenType::Identifier(i) => write!(f, "{}", i),
TokenType::Symbol(s) => write!(f, "{}", s),
TokenType::Comment(c) => write!(f, "{}", c),
TokenType::EOF => write!(f, "EOF"),
_ => write!(f, ""),
}
}
}

View File

@@ -96,9 +96,10 @@ pub fn free_docs_vec(v: safer_ffi::Vec<FfiDocumentedItem>) {
#[ffi_export]
pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::String {
let res = std::panic::catch_unwind(|| {
let input = String::from_utf16_lossy(input.as_slice());
let mut writer = BufWriter::new(Vec::new());
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice()));
let tokenizer = Tokenizer::from(input.as_str());
let parser = Parser::new(tokenizer);
let compiler = Compiler::new(parser, &mut writer, None);
@@ -120,7 +121,8 @@ pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::
#[ffi_export]
pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<FfiToken> {
let res = std::panic::catch_unwind(|| {
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice()));
let input = String::from_utf16_lossy(input.as_slice());
let tokenizer = Tokenizer::from(input.as_str());
let mut tokens = Vec::new();
@@ -136,34 +138,31 @@ pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<Ff
}
match token {
Err(ref e) => {
use tokenizer::token::LexError;
use tokenizer::Error::*;
let (err_str, col, og) = match e {
NumberParseError(_, _, col, og)
| DecimalParseError(_, _, col, og)
| UnknownSymbolError(_, _, col, og)
| UnknownKeywordOrIdentifierError(_, _, col, og) => {
(e.to_string(), col, og)
let (err_str, _, span) = match e {
LexError(LexError::NumberParse(line, span, err))
| LexError(LexError::InvalidInput(line, span, err)) => {
(err.to_string(), line, span)
}
_ => continue,
};
tokens.push(FfiToken {
column: *col as i32,
column: span.start as i32,
error: err_str.into(),
tooltip: "".into(),
length: og.len() as i32,
length: (span.end - span.start) as i32,
token_kind: 0,
})
}
Ok(Token {
column,
original_string,
token_type,
..
span, token_type, ..
}) => tokens.push(FfiToken {
column: column as i32,
column: span.start as i32,
error: "".into(),
length: (original_string.unwrap_or_default().len()) as i32,
length: (span.end - span.start) as i32,
tooltip: token_type.docs().into(),
token_kind: token_type.into(),
}),
@@ -179,8 +178,10 @@ pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<Ff
#[ffi_export]
pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<FfiDiagnostic> {
let res = std::panic::catch_unwind(|| {
let input = String::from_utf16_lossy(input.as_slice());
let mut writer = BufWriter::new(Vec::new());
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice()));
let tokenizer = Tokenizer::from(input.as_str());
let compiler = Compiler::new(Parser::new(tokenizer), &mut writer, None);
let diagnosis = compiler.compile();

View File

@@ -1,8 +1,5 @@
#![allow(clippy::result_large_err)]
#[macro_use]
extern crate quick_error;
use clap::Parser;
use compiler::Compiler;
use parser::Parser as ASTParser;
@@ -11,27 +8,39 @@ use std::{
io::{stderr, BufWriter, Read, Write},
path::PathBuf,
};
use thiserror::Error;
use tokenizer::{self, Tokenizer};
quick_error! {
#[derive(Debug)]
enum StationlangError {
TokenizerError(err: tokenizer::Error) {
from()
display("Tokenizer error: {}", err)
}
ParserError(err: parser::Error) {
from()
display("Parser error: {}", err)
}
CompileError(err: compiler::Error) {
from()
display("Compile error: {}", err)
}
IoError(err: std::io::Error) {
from()
display("IO error: {}", err)
}
#[derive(Error, Debug)]
enum Error<'a> {
#[error(transparent)]
Tokenizer(tokenizer::Error),
#[error(transparent)]
Parser(parser::Error<'a>),
#[error(transparent)]
Compile(compiler::Error<'a>),
#[error(transparent)]
IO(#[from] std::io::Error),
}
impl<'a> From<parser::Error<'a>> for Error<'a> {
fn from(value: parser::Error<'a>) -> Self {
Self::Parser(value)
}
}
impl<'a> From<compiler::Error<'a>> for Error<'a> {
fn from(value: compiler::Error<'a>) -> Self {
Self::Compile(value)
}
}
impl<'a> From<tokenizer::Error> for Error<'a> {
fn from(value: tokenizer::Error) -> Self {
Self::Tokenizer(value)
}
}
@@ -46,12 +55,17 @@ struct Args {
output_file: Option<PathBuf>,
}
fn run_logic() -> Result<(), StationlangError> {
fn run_logic<'a>() -> Result<(), Error<'a>> {
let args = Args::parse();
let input_file = args.input_file;
let tokenizer: Tokenizer = match input_file {
Some(input_file) => Tokenizer::from_path(&input_file)?,
let input_string = match input_file {
Some(input_path) => {
let mut buf = String::new();
let mut file = std::fs::File::open(input_path).unwrap();
file.read_to_string(&mut buf).unwrap();
buf
}
None => {
let mut buf = String::new();
let stdin = std::io::stdin();
@@ -62,10 +76,11 @@ fn run_logic() -> Result<(), StationlangError> {
return Ok(());
}
Tokenizer::from(buf)
buf
}
};
let tokenizer = Tokenizer::from(input_string.as_str());
let parser = ASTParser::new(tokenizer);
let mut writer: BufWriter<Box<dyn Write>> = match args.output_file {
@@ -75,20 +90,17 @@ fn run_logic() -> Result<(), StationlangError> {
let compiler = Compiler::new(parser, &mut writer, None);
let mut errors = compiler.compile();
let errors = compiler.compile();
if !errors.is_empty() {
let mut std_error = stderr();
let last = errors.pop();
let errors = errors.into_iter().map(StationlangError::from);
let errors = errors.into_iter().map(Error::from);
std_error.write_all(b"Compilation error:\n")?;
for err in errors {
std_error.write_all(format!("{}\n", err).as_bytes())?;
}
return Err(StationlangError::from(last.unwrap()));
}
writer.flush()?;
@@ -96,7 +108,7 @@ fn run_logic() -> Result<(), StationlangError> {
Ok(())
}
fn main() -> Result<(), StationlangError> {
fn main() -> anyhow::Result<()> {
run_logic()?;
Ok(())