Merge branch 'master' into python
This commit is contained in:
commit
8e918dcf34
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "sls_rs"
|
||||
version = "0.0.1-alpha"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.4"
|
||||
rand = "0.8"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.4"
|
||||
chrono = "0.4"
|
||||
vergen = { version = "8", features = ["build"] }
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
use std::process::Command;
|
||||
use vergen::EmitBuilder;
|
||||
|
||||
fn try_cmd(cmd: &mut Command) -> Option<String> {
|
||||
let out = cmd.output().ok()?;
|
||||
if !out.status.success() {
|
||||
return None;
|
||||
}
|
||||
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Emit all default vergen build info (BUILD_DATE / BUILD_TIME, etc.)
|
||||
EmitBuilder::builder().all_build();
|
||||
|
||||
// Git describe + commit date (matches your Python logic)
|
||||
let commit_info = (|| {
|
||||
let hash = try_cmd(
|
||||
Command::new("git")
|
||||
.arg("describe")
|
||||
.arg("--always")
|
||||
.arg("--dirty")
|
||||
.arg("--abbrev=7"),
|
||||
)?;
|
||||
|
||||
let date = try_cmd(
|
||||
Command::new("git")
|
||||
.arg("show")
|
||||
.arg("-s")
|
||||
.arg("--format=%ci"),
|
||||
)?;
|
||||
|
||||
Some(format!("{} {}", hash, date))
|
||||
})()
|
||||
.unwrap_or_else(|| "unknown".into());
|
||||
|
||||
println!("cargo:rustc-env=GIT_COMMIT_HASH={}", commit_info);
|
||||
|
||||
// Compiler info
|
||||
println!("cargo:rustc-env=COMPILER_NAME=rustc");
|
||||
|
||||
let rustc_ver = try_cmd(Command::new("rustc").arg("--version"))
|
||||
.unwrap_or_else(|| "unknown".into());
|
||||
println!("cargo:rustc-env=COMPILER_VER={}", rustc_ver);
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,52 @@
|
|||
use std::fs;
|
||||
|
||||
use crate::interpreter::InterpreterState;
|
||||
use crate::lexer::{LexerInfo, lexical_analysis, LexResult};
|
||||
|
||||
/// Execute the contents of a script file.
|
||||
pub fn exec_file(interpreter: &mut InterpreterState, filename: &str) -> bool {
|
||||
// Read the whole file
|
||||
let source = match fs::read_to_string(filename) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Cannot read file: {} ({})", filename, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let mut lexer_info = LexerInfo::new(filename, source.clone());
|
||||
|
||||
let result = lexical_analysis(&mut lexer_info);
|
||||
|
||||
match result {
|
||||
LexResult::Ok(tokens) => {
|
||||
for token in tokens {
|
||||
if !interpreter.execute(&token) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
LexResult::Err(err) => {
|
||||
dbg!(err);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stand-alone file execution entry point.
|
||||
pub fn run_file(filename: &str) -> i32 {
|
||||
println!("Executing file: {}", filename);
|
||||
|
||||
let mut interpreter = InterpreterState::new();
|
||||
if !interpreter.init() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if exec_file(&mut interpreter, filename) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::lexer::*; // Identifier, Token, TokenString, etc.
|
||||
use crate::builtin::load_builtins;
|
||||
|
||||
pub type BuiltinFn = fn(&mut InterpreterState) -> bool;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StackValue {
|
||||
Identifier(Identifier),
|
||||
|
||||
I64(i64),
|
||||
I32(i32),
|
||||
I16(i16),
|
||||
I8(i8),
|
||||
|
||||
U64(u64),
|
||||
U32(u32),
|
||||
U16(u16),
|
||||
U8(u8),
|
||||
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
|
||||
Character(u8),
|
||||
Boolean(bool),
|
||||
|
||||
TokenString(TokenString),
|
||||
|
||||
Callable(TokenString),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FunctionItem {
|
||||
TokenString(TokenString),
|
||||
Builtin(BuiltinFn),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InterpreterState {
|
||||
pub stack: Vec<StackValue>,
|
||||
pub functions: HashMap<String, FunctionItem>,
|
||||
}
|
||||
|
||||
impl InterpreterState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stack: Vec::new(),
|
||||
functions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> bool {
|
||||
load_builtins(self)
|
||||
}
|
||||
|
||||
pub fn push_token(&mut self, token: &Token) -> bool {
|
||||
let value = match token {
|
||||
Token::Eof => return true,
|
||||
|
||||
Token::Identifier(id) => {
|
||||
StackValue::Identifier(id.clone())
|
||||
}
|
||||
|
||||
Token::I64(v) => StackValue::I64(*v),
|
||||
Token::I32(v) => StackValue::I32(*v),
|
||||
Token::I16(v) => StackValue::I16(*v),
|
||||
Token::I8(v) => StackValue::I8(*v),
|
||||
|
||||
Token::U64(v) => StackValue::U64(*v),
|
||||
Token::U32(v) => StackValue::U32(*v),
|
||||
Token::U16(v) => StackValue::U16(*v),
|
||||
Token::U8(v) => StackValue::U8(*v),
|
||||
|
||||
Token::Float(v) => StackValue::F32(*v),
|
||||
Token::Double(v) => StackValue::F64(*v),
|
||||
|
||||
Token::Character(c) => StackValue::Character(*c),
|
||||
Token::Boolean(b) => StackValue::Boolean(*b),
|
||||
|
||||
Token::TokenString(ts) => StackValue::TokenString(ts.clone()),
|
||||
|
||||
Token::StringLiteral(_) |
|
||||
Token::Array(_) |
|
||||
Token::TypeTuple(_) => return false,
|
||||
};
|
||||
|
||||
self.stack.push(value);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn execute_func(&mut self, key: &str) -> bool {
|
||||
let item = match self.functions.get(key) {
|
||||
Some(v) => v.clone(),
|
||||
None => return false,
|
||||
};
|
||||
|
||||
match item {
|
||||
FunctionItem::Builtin(f) => f(self),
|
||||
FunctionItem::TokenString(ts) => self.execute_token_string(&ts),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_token_string(&mut self, ts: &TokenString) -> bool {
|
||||
for token in &ts.tokens {
|
||||
if let Token::Identifier(id) = &token {
|
||||
if !id.is_literal {
|
||||
if !self.execute_func(&id.name) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.push_token(&token) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, token: &Token) -> bool {
|
||||
match token {
|
||||
Token::Identifier(id) if !id.is_literal => {
|
||||
self.execute_func(&id.name)
|
||||
}
|
||||
_ => self.push_token(token),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack_top(&self) -> Option<&StackValue> {
|
||||
self.stack.last()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,737 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct LexerInfo {
|
||||
pub filename: String,
|
||||
pub source: String,
|
||||
pub pos: usize,
|
||||
pub column: usize,
|
||||
pub line: usize,
|
||||
}
|
||||
|
||||
impl LexerInfo {
|
||||
pub fn new(filename: impl Into<String>, source: impl Into<String>) -> Self {
|
||||
Self {
|
||||
filename: filename.into(),
|
||||
source: source.into(),
|
||||
pos: 0,
|
||||
column: 1,
|
||||
line: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&self) -> char {
|
||||
self.source.chars().nth(self.pos).unwrap_or('\0')
|
||||
}
|
||||
|
||||
fn far_peek(&self, offset: usize) -> char {
|
||||
self.source.chars().nth(self.pos + offset).unwrap_or('\0')
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> char {
|
||||
if self.peek() == '\n' {
|
||||
self.line += 1;
|
||||
self.column = 1;
|
||||
} else {
|
||||
self.column += 1;
|
||||
}
|
||||
self.pos += 1;
|
||||
self.peek()
|
||||
}
|
||||
|
||||
fn skip_comments_and_whitespace(&mut self) {
|
||||
loop {
|
||||
let c = self.peek();
|
||||
|
||||
// Skip comments
|
||||
if (c == '/' && self.far_peek(1) == '/') || c == '#' {
|
||||
while self.peek() != '\n' && self.peek() != '\0' {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
// Skip whitespace
|
||||
if self.peek().is_whitespace() {
|
||||
while self.peek().is_whitespace() {
|
||||
self.advance();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Identifier {
|
||||
pub name: String,
|
||||
pub is_literal: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ArrayLiteral {
|
||||
_Identifiers(Vec<Identifier>),
|
||||
_I64(Vec<i64>),
|
||||
_I32(Vec<i32>),
|
||||
_I16(Vec<i16>),
|
||||
_I8(Vec<i8>),
|
||||
_U64(Vec<u64>),
|
||||
_U32(Vec<u32>),
|
||||
_U16(Vec<u16>),
|
||||
_U8(Vec<u8>),
|
||||
_Float(Vec<f32>),
|
||||
_Double(Vec<f64>),
|
||||
_Character(Vec<u8>),
|
||||
_Strings(Vec<String>),
|
||||
_Boolean(Vec<bool>),
|
||||
_TokenStrings(Vec<TokenString>),
|
||||
_TypeTuples(Vec<TypeTuple>),
|
||||
_StructInline(StructInline),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShapedArray {
|
||||
pub _array: ArrayLiteral,
|
||||
pub _shape: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TokenString {
|
||||
pub tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeTuple {
|
||||
pub _inputs: Vec<Identifier>,
|
||||
pub _outputs: Vec<Identifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StructInline {
|
||||
pub _name: String,
|
||||
pub _values: Vec<StructValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StructValue {
|
||||
_Integer(i64),
|
||||
_Float(f32),
|
||||
_Double(f64),
|
||||
_Boolean(bool),
|
||||
_Character(u8),
|
||||
_String(String),
|
||||
_Token(Token),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token {
|
||||
Eof,
|
||||
Identifier(Identifier),
|
||||
I64(i64),
|
||||
I32(i32),
|
||||
I16(i16),
|
||||
I8(i8),
|
||||
U64(u64),
|
||||
U32(u32),
|
||||
U16(u16),
|
||||
U8(u8),
|
||||
Float(f32),
|
||||
Double(f64),
|
||||
Character(u8),
|
||||
StringLiteral(String),
|
||||
Boolean(bool),
|
||||
Array(ShapedArray),
|
||||
TokenString(TokenString),
|
||||
TypeTuple(TypeTuple),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LexError {
|
||||
pub message: String,
|
||||
pub file: String,
|
||||
pub line: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
pub type LexResult<T> = Result<T, LexError>;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum NumericLiteralType {
|
||||
Binary,
|
||||
Octal,
|
||||
Decimal,
|
||||
Hexadecimal,
|
||||
Float,
|
||||
}
|
||||
|
||||
impl LexerInfo {
|
||||
fn make_error(&self, message: impl Into<String>, start_line: usize, start_col: usize) -> LexError {
|
||||
LexError {
|
||||
message: message.into(),
|
||||
file: self.filename.clone(),
|
||||
line: start_line,
|
||||
column: start_col,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_identifier_continue(&self, c: char) -> bool {
|
||||
if !c.is_ascii() || !c.is_ascii_graphic() {
|
||||
return false;
|
||||
}
|
||||
if c == '/' && self.far_peek(1) == '/' {
|
||||
return false;
|
||||
}
|
||||
!matches!(c, '{' | '}' | '[' | ']' | '(' | ')' | '\'' | '"' | '#') && !c.is_whitespace()
|
||||
}
|
||||
|
||||
fn is_identifier_start(&self) -> bool {
|
||||
let mut c = self.peek();
|
||||
if c == ':' && self.far_peek(1) == ':' {
|
||||
c = self.far_peek(2);
|
||||
}
|
||||
!c.is_ascii_digit() && self.is_identifier_continue(c)
|
||||
}
|
||||
|
||||
fn parse_identifiers_and_booleans(&mut self, _start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
let mut literal = false;
|
||||
|
||||
// Skip leading `::` for identifier literals
|
||||
if c == ':' && self.far_peek(1) == ':' {
|
||||
literal = true;
|
||||
self.advance();
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
// Read the name
|
||||
let name_start = self.pos;
|
||||
while self.is_identifier_continue(c) {
|
||||
if c == ':' {
|
||||
return Err(self.make_error("Invalid identifier: ':' is not allowed in identifiers.", start_line, start_col));
|
||||
}
|
||||
if c == '.' {
|
||||
return Err(self.make_error("Invalid identifier: '.' is not allowed in identifiers.", start_line, start_col));
|
||||
}
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
let name = self.source[name_start..self.pos].to_string();
|
||||
|
||||
// Check for booleans
|
||||
match name.as_str() {
|
||||
"false" => Ok(Token::Boolean(false)),
|
||||
"true" => Ok(Token::Boolean(true)),
|
||||
_ => Ok(Token::Identifier(Identifier { name, is_literal: literal })),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_character_literal(&mut self, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
|
||||
if c == '\'' {
|
||||
return Err(self.make_error("Invalid character literal: empty character literal.", start_line, start_col));
|
||||
}
|
||||
|
||||
let value = if c == '\\' {
|
||||
c = self.advance();
|
||||
match c {
|
||||
'n' => b'\n',
|
||||
'r' => b'\r',
|
||||
't' => b'\t',
|
||||
'\\' => b'\\',
|
||||
'\'' => b'\'',
|
||||
'0' => b'\0',
|
||||
_ => return Err(self.make_error(format!("Invalid character literal: unknown escape sequence '\\{}'.", c), start_line, start_col)),
|
||||
}
|
||||
} else if c == '\n' || c == '\r' {
|
||||
return Err(self.make_error("Invalid character literal: unclosed character literal.", start_line, start_col));
|
||||
} else {
|
||||
c as u8
|
||||
};
|
||||
|
||||
c = self.advance();
|
||||
|
||||
if c.is_whitespace() || c == '/' || c == '\0' {
|
||||
return Err(self.make_error("Invalid character literal: unclosed character literal.", start_line, start_col));
|
||||
} else if c != '\'' {
|
||||
return Err(self.make_error(format!("Invalid character literal: unexpected '{}' in character.", c), start_line, start_col));
|
||||
}
|
||||
|
||||
self.advance();
|
||||
Ok(Token::Character(value))
|
||||
}
|
||||
|
||||
fn parse_token_string(&mut self, _start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut tokens = Vec::new();
|
||||
self.advance(); // skip '{'
|
||||
|
||||
loop {
|
||||
self.skip_comments_and_whitespace();
|
||||
let c = self.peek();
|
||||
|
||||
if c == '}' {
|
||||
self.advance();
|
||||
return Ok(Token::TokenString(TokenString { tokens }));
|
||||
}
|
||||
|
||||
if c == '\0' {
|
||||
return Err(self.make_error("Unclosed token string: missing closing brace '}'.", start_line, start_col));
|
||||
}
|
||||
|
||||
match get_token(self) {
|
||||
Some(token) => {
|
||||
if matches!(token, Token::Eof) {
|
||||
break;
|
||||
}
|
||||
tokens.push(token);
|
||||
}
|
||||
None => return Err(self.make_error("Failed to parse token in token string.", start_line, start_col)),
|
||||
}
|
||||
}
|
||||
|
||||
Err(self.make_error("Unclosed token string: missing closing brace '}'.", start_line, start_col))
|
||||
}
|
||||
|
||||
fn parse_numeric_literal(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
|
||||
if c == '-' {
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
if c == '0' {
|
||||
c = self.advance();
|
||||
match c {
|
||||
'b' | 'B' => {
|
||||
self.advance();
|
||||
return self.parse_binary_integer(start, start_line, start_col);
|
||||
}
|
||||
'o' | 'O' => {
|
||||
self.advance();
|
||||
return self.parse_octal_integer(start, start_line, start_col);
|
||||
}
|
||||
'x' | 'X' => {
|
||||
self.advance();
|
||||
return self.parse_hexadecimal_integer(start, start_line, start_col);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.parse_decimal_integer(start, start_line, start_col)
|
||||
}
|
||||
|
||||
fn parse_binary_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
while c == '0' || c == '1' || c == '_' {
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
if c == ':' {
|
||||
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Binary);
|
||||
}
|
||||
|
||||
let value = self.create_binary_integer(start);
|
||||
Ok(Token::I64(value as i64))
|
||||
}
|
||||
|
||||
fn parse_octal_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
while c.is_ascii_digit() && c != '8' && c != '9' || c == '_' {
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
if c == ':' {
|
||||
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Octal);
|
||||
}
|
||||
|
||||
let value = self.create_octal_integer(start);
|
||||
Ok(Token::I64(value as i64))
|
||||
}
|
||||
|
||||
fn parse_decimal_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
while c.is_ascii_digit() || c == '_' {
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
if c == '.' {
|
||||
self.advance();
|
||||
return self.parse_float(start, start_line, start_col);
|
||||
}
|
||||
|
||||
if c == ':' {
|
||||
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Decimal);
|
||||
}
|
||||
|
||||
let value = self.create_decimal_integer(start);
|
||||
Ok(Token::I64(value as i64))
|
||||
}
|
||||
|
||||
fn parse_hexadecimal_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
while c.is_ascii_hexdigit() || c == '_' {
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
if c == ':' {
|
||||
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Hexadecimal);
|
||||
}
|
||||
|
||||
let value = self.create_hexadecimal_integer(start);
|
||||
Ok(Token::I64(value as i64))
|
||||
}
|
||||
|
||||
fn parse_float(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let mut c = self.peek();
|
||||
while c.is_ascii_digit() || c == '_' {
|
||||
c = self.advance();
|
||||
}
|
||||
|
||||
if c == ':' {
|
||||
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Float);
|
||||
}
|
||||
|
||||
let value = self.create_float(start);
|
||||
Ok(Token::Double(value))
|
||||
}
|
||||
|
||||
fn parse_numeric_type(&mut self, start: usize, start_line: usize, start_col: usize, literal_type: NumericLiteralType) -> LexResult<Token> {
|
||||
let mut c = self.advance(); // skip ':'
|
||||
|
||||
let mut is_float = false;
|
||||
let mut is_unsigned = false;
|
||||
let bit_size: u32;
|
||||
|
||||
if c == 'f' {
|
||||
is_float = true;
|
||||
if !matches!(literal_type, NumericLiteralType::Decimal | NumericLiteralType::Float) {
|
||||
return Err(self.make_error("Invalid numeric literal: float type not allowed.", start_line, start_col));
|
||||
}
|
||||
c = self.advance();
|
||||
if c == '6' && self.far_peek(1) == '4' {
|
||||
bit_size = 64;
|
||||
self.advance();
|
||||
self.advance();
|
||||
} else if c == '3' && self.far_peek(1) == '2' {
|
||||
bit_size = 32;
|
||||
self.advance();
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(self.make_error("Invalid float type: must be of type 'f64' or 'f32'.", start_line, start_col));
|
||||
}
|
||||
} else if c == 'i' || c == 'u' {
|
||||
if matches!(literal_type, NumericLiteralType::Float) {
|
||||
return Err(self.make_error("Invalid float type: must be of type 'f64' or 'f32'.", start_line, start_col));
|
||||
}
|
||||
is_unsigned = c == 'u';
|
||||
c = self.advance();
|
||||
if c == '6' && self.far_peek(1) == '4' {
|
||||
bit_size = 64;
|
||||
self.advance();
|
||||
self.advance();
|
||||
} else if c == '3' && self.far_peek(1) == '2' {
|
||||
bit_size = 32;
|
||||
self.advance();
|
||||
self.advance();
|
||||
} else if c == '1' && self.far_peek(1) == '6' {
|
||||
bit_size = 16;
|
||||
self.advance();
|
||||
self.advance();
|
||||
} else if c == '8' {
|
||||
bit_size = 8;
|
||||
self.advance();
|
||||
} else {
|
||||
let type_name = if is_unsigned { "unsigned" } else { "signed" };
|
||||
return Err(self.make_error(
|
||||
format!("Invalid {} integer type: must be of type '{}64', '{}32', '{}16', or '{}8'.",
|
||||
type_name, if is_unsigned { "u" } else { "i" },
|
||||
if is_unsigned { "u" } else { "i" },
|
||||
if is_unsigned { "u" } else { "i" },
|
||||
if is_unsigned { "u" } else { "i" }),
|
||||
start_line, start_col));
|
||||
}
|
||||
} else {
|
||||
return Err(self.make_error("Invalid numeric type: type must start with 'f', 'i', or 'u'.", start_line, start_col));
|
||||
}
|
||||
|
||||
// Create the token based on the parsed type
|
||||
if is_float {
|
||||
let value = self.create_float(start);
|
||||
match bit_size {
|
||||
32 => Ok(Token::Float(value as f32)),
|
||||
64 => Ok(Token::Double(value)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
} else {
|
||||
let value = match literal_type {
|
||||
NumericLiteralType::Binary => self.create_binary_integer(start),
|
||||
NumericLiteralType::Octal => self.create_octal_integer(start),
|
||||
NumericLiteralType::Decimal => self.create_decimal_integer(start),
|
||||
NumericLiteralType::Hexadecimal => self.create_hexadecimal_integer(start),
|
||||
NumericLiteralType::Float => return Err(self.make_error("Internal error: float literal in integer path", start_line, start_col)),
|
||||
};
|
||||
|
||||
self.create_integer_token(value, is_unsigned, bit_size, start, start_line, start_col)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_integer_token(&self, value: u64, is_unsigned: bool, bit_size: u32, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
|
||||
let is_negative = self.source[start..].starts_with('-');
|
||||
|
||||
match (is_unsigned, bit_size) {
|
||||
(false, 64) => Ok(Token::I64(value as i64)),
|
||||
(false, 32) => {
|
||||
let signed = value as i64;
|
||||
if signed < i32::MIN as i64 || signed > i32::MAX as i64 {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for i32.", start_line, start_col));
|
||||
}
|
||||
Ok(Token::I32(value as i32))
|
||||
}
|
||||
(false, 16) => {
|
||||
let signed = value as i64;
|
||||
if signed < i16::MIN as i64 || signed > i16::MAX as i64 {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for i16.", start_line, start_col));
|
||||
}
|
||||
Ok(Token::I16(value as i16))
|
||||
}
|
||||
(false, 8) => {
|
||||
let signed = value as i64;
|
||||
if signed < i8::MIN as i64 || signed > i8::MAX as i64 {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for i8.", start_line, start_col));
|
||||
}
|
||||
Ok(Token::I8(value as i8))
|
||||
}
|
||||
(true, 64) => {
|
||||
if is_negative {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for u64.", start_line, start_col));
|
||||
}
|
||||
Ok(Token::U64(value))
|
||||
}
|
||||
(true, 32) => {
|
||||
if is_negative {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for u32.", start_line, start_col));
|
||||
}
|
||||
if value > u32::MAX as u64 {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for u32.", start_line, start_col));
|
||||
}
|
||||
Ok(Token::U32(value as u32))
|
||||
}
|
||||
(true, 16) => {
|
||||
if is_negative {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for u16.", start_line, start_col));
|
||||
}
|
||||
if value > u16::MAX as u64 {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for u16.", start_line, start_col));
|
||||
}
|
||||
Ok(Token::U16(value as u16))
|
||||
}
|
||||
(true, 8) => {
|
||||
if is_negative {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for u8.", start_line, start_col));
|
||||
}
|
||||
if value > u8::MAX as u64 {
|
||||
return Err(self.make_error("Integer overflow: value exceeds range for u8.", start_line, start_col));
|
||||
}
|
||||
Ok(Token::U8(value as u8))
|
||||
}
|
||||
_ => Err(self.make_error("Invalid bit size for integer type.", start_line, start_col))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_binary_integer(&self, start: usize) -> u64 {
|
||||
let token = &self.source[start..self.pos];
|
||||
let mut value = 0u64;
|
||||
let mut i = 2;
|
||||
|
||||
if token.starts_with('-') {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
for c in token[i..].chars() {
|
||||
if c == '_' || c == '.' {
|
||||
continue;
|
||||
}
|
||||
if c.is_whitespace() || c == '/' || c == ':' {
|
||||
break;
|
||||
}
|
||||
value *= 2;
|
||||
if c == '1' {
|
||||
value += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if token.starts_with('-') {
|
||||
(!value).wrapping_add(1)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn create_octal_integer(&self, start: usize) -> u64 {
|
||||
let token = &self.source[start..self.pos];
|
||||
let mut value = 0u64;
|
||||
let mut i = 2;
|
||||
|
||||
if token.starts_with('-') {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
for c in token[i..].chars() {
|
||||
if c == '_' || c == '.' {
|
||||
continue;
|
||||
}
|
||||
if c.is_whitespace() || c == '/' || c == ':' {
|
||||
break;
|
||||
}
|
||||
value *= 8;
|
||||
value += c.to_digit(8).unwrap_or(0) as u64;
|
||||
}
|
||||
|
||||
if token.starts_with('-') {
|
||||
(!value).wrapping_add(1)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn create_decimal_integer(&self, start: usize) -> u64 {
|
||||
let token = &self.source[start..self.pos];
|
||||
let mut value = 0u64;
|
||||
let mut i = 0;
|
||||
|
||||
if token.starts_with('-') {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
for c in token[i..].chars() {
|
||||
if c == '_' {
|
||||
continue;
|
||||
}
|
||||
if c.is_whitespace() || c == '/' || c == ':' {
|
||||
break;
|
||||
}
|
||||
value *= 10;
|
||||
value += c.to_digit(10).unwrap_or(0) as u64;
|
||||
}
|
||||
|
||||
if token.starts_with('-') {
|
||||
(!value).wrapping_add(1)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn create_hexadecimal_integer(&self, start: usize) -> u64 {
|
||||
let token = &self.source[start..self.pos];
|
||||
let mut value = 0u64;
|
||||
let mut i = 2;
|
||||
|
||||
if token.starts_with('-') {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
for c in token[i..].chars() {
|
||||
if c == '_' || c == '.' {
|
||||
continue;
|
||||
}
|
||||
if c.is_whitespace() || c == '/' || c == ':' {
|
||||
break;
|
||||
}
|
||||
value *= 16;
|
||||
value += c.to_digit(16).unwrap_or(0) as u64;
|
||||
}
|
||||
|
||||
if token.starts_with('-') {
|
||||
(!value).wrapping_add(1)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn create_float(&self, start: usize) -> f64 {
|
||||
let token = &self.source[start..self.pos];
|
||||
let mut value = 0.0;
|
||||
let mut fractional = 0u64;
|
||||
let mut i = 0;
|
||||
|
||||
if token.starts_with('-') {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
for c in token[i..].chars() {
|
||||
if c == '_' {
|
||||
continue;
|
||||
}
|
||||
if c.is_whitespace() || c == '/' || c == ':' {
|
||||
break;
|
||||
}
|
||||
if c == '.' {
|
||||
fractional = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if fractional == 0 {
|
||||
value *= 10.0;
|
||||
} else {
|
||||
fractional *= 10;
|
||||
}
|
||||
|
||||
let digit = c.to_digit(10).unwrap_or(0) as f64;
|
||||
if fractional == 0 {
|
||||
value += digit;
|
||||
} else {
|
||||
value += digit / fractional as f64;
|
||||
}
|
||||
}
|
||||
|
||||
if token.starts_with('-') {
|
||||
-value
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_token(lexer: &mut LexerInfo) -> Option<Token> {
|
||||
lexer.skip_comments_and_whitespace();
|
||||
|
||||
let c = lexer.peek();
|
||||
let start = lexer.pos;
|
||||
let start_line = lexer.line;
|
||||
let start_col = lexer.column;
|
||||
|
||||
if c == '\0' {
|
||||
return Some(Token::Eof);
|
||||
}
|
||||
|
||||
let result = if c.is_ascii_digit() || (c == '.' && lexer.far_peek(1).is_ascii_digit()) || (c == '-' && lexer.far_peek(1).is_ascii_digit()) {
|
||||
lexer.parse_numeric_literal(start, start_line, start_col)
|
||||
} else if c == '\'' {
|
||||
lexer.advance();
|
||||
lexer.parse_character_literal(start_line, start_col)
|
||||
} else if c == '{' {
|
||||
lexer.parse_token_string(start, start_line, start_col)
|
||||
} else if lexer.is_identifier_start() {
|
||||
lexer.parse_identifiers_and_booleans(start, start_line, start_col)
|
||||
} else {
|
||||
Err(lexer.make_error(format!("Unexpected character: '{}'", c), start_line, start_col))
|
||||
};
|
||||
|
||||
result.ok()
|
||||
}
|
||||
|
||||
pub fn lexical_analysis(lexer: &mut LexerInfo) -> LexResult<Vec<Token>> {
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
loop {
|
||||
match get_token(lexer) {
|
||||
Some(Token::Eof) => {
|
||||
tokens.push(Token::Eof);
|
||||
break;
|
||||
}
|
||||
Some(token) => tokens.push(token),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
mod builtin;
|
||||
mod file;
|
||||
mod interpreter;
|
||||
mod lexer;
|
||||
mod repl;
|
||||
|
||||
use std::env;
|
||||
use std::process;
|
||||
|
||||
use file::run_file;
|
||||
use repl::repl;
|
||||
|
||||
// These mirror the C macros.
|
||||
const SLS_NAME: &str = "SLS_RUST";
|
||||
const SLS_VER: &str = "a.0.0";
|
||||
|
||||
pub fn print_version() {
|
||||
let git_hash = option_env!("GIT_COMMIT_HASH").unwrap_or("unknown");
|
||||
let compiler = option_env!("COMPILER_NAME").unwrap_or("rustc");
|
||||
let compiler_ver = option_env!("COMPILER_VER").unwrap_or("unknown");
|
||||
let build_date = std::env::var("BUILD_DATE").unwrap_or_else(|_| "unknown".into());
|
||||
let build_time = std::env::var("BUILD_TIME").unwrap_or_else(|_| "unknown".into());
|
||||
|
||||
println!("YREA SLS ({SLS_NAME}) {SLS_VER} ({git_hash})");
|
||||
println!("Compiled with {compiler} {compiler_ver} at {build_date} {build_time}");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
let mut version_flag = false;
|
||||
let mut filename: Option<String> = None;
|
||||
|
||||
match args.len() {
|
||||
0 => {}
|
||||
1 => {
|
||||
let arg = args.next().unwrap();
|
||||
if arg == "--version" || arg == "-v" {
|
||||
version_flag = true;
|
||||
} else {
|
||||
filename = Some(arg);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Too many arguments!");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if version_flag {
|
||||
print_version();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
if let Some(file) = filename {
|
||||
let status = run_file(&file);
|
||||
process::exit(status);
|
||||
}
|
||||
|
||||
// Default to REPL
|
||||
let status = repl();
|
||||
process::exit(status);
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
use crate::lexer::{LexerInfo, LexResult, lexical_analysis};
|
||||
use crate::print_version;
|
||||
use crate::interpreter::{InterpreterState, StackValue};
|
||||
|
||||
static REPL_FILE_NAME: &str = "<STDIN>";
|
||||
|
||||
fn print_top_of_stack(state: &InterpreterState) {
|
||||
let Some(item) = state.stack_top() else {
|
||||
println!("#0: <STACK IS EMPTY>");
|
||||
return;
|
||||
};
|
||||
|
||||
match &item {
|
||||
StackValue::Identifier(id) => {
|
||||
println!("#0: ::{}", id.name);
|
||||
}
|
||||
StackValue::I64(v) => println!("#0: {}", v),
|
||||
StackValue::I32(v) => println!("#0: {}:i32", v),
|
||||
StackValue::I16(v) => println!("#0: {}:i16", v),
|
||||
StackValue::I8(v) => println!("#0: {}:i8", v),
|
||||
|
||||
StackValue::U64(v) => println!("#0: {}:u64", v),
|
||||
StackValue::U32(v) => println!("#0: {}:u32", v),
|
||||
StackValue::U16(v) => println!("#0: {}:u16", v),
|
||||
StackValue::U8(v) => println!("#0: {}:u8", v),
|
||||
|
||||
StackValue::F32(v) => println!("#0: {}:f32", v),
|
||||
StackValue::F64(v) => println!("#0: {}", v),
|
||||
|
||||
StackValue::Character(ch) => println!("#0: {}", ch),
|
||||
StackValue::Boolean(b) => println!("#0: {}", if *b { "TRUE" } else { "FALSE" }),
|
||||
|
||||
StackValue::TokenString(_) => println!("#0: <TOKEN STRING>"),
|
||||
StackValue::Callable(_) => println!("#0: <CALLABLE>"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn repl() -> i32 {
|
||||
print_version();
|
||||
println!("===== YREA SLS REPL =====");
|
||||
println!("Type `#exit` to exit.");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let mut interpreter = InterpreterState::new();
|
||||
if !interpreter.init() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let stdin = io::stdin();
|
||||
let mut buf = String::new();
|
||||
|
||||
loop {
|
||||
buf.clear();
|
||||
|
||||
if stdin.read_line(&mut buf).is_err() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if buf.trim_end() == "#exit" {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let code = buf.clone();
|
||||
let mut lexer_info = LexerInfo::new(REPL_FILE_NAME, code.clone());
|
||||
let result = lexical_analysis(&mut lexer_info);
|
||||
|
||||
match result {
|
||||
LexResult::Ok(tokens) => {
|
||||
for token in tokens {
|
||||
if !interpreter.execute(&token) {
|
||||
eprintln!("A runtime error occured!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
print_top_of_stack(&interpreter);
|
||||
}
|
||||
|
||||
LexResult::Err(err) => {
|
||||
dbg!(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1993,7 +1993,7 @@
|
|||
stack_final:
|
||||
- type: f32
|
||||
value: -0.0
|
||||
- name: Char Simple Letter A
|
||||
- name: Char Simple Letter Uppercase A
|
||||
code: '''A'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2005,7 +2005,7 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: A
|
||||
- name: Char Simple Letter a
|
||||
- name: Char Simple Letter Lowercase a
|
||||
code: '''a'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2017,7 +2017,7 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: a
|
||||
- name: Char Simple Letter Z
|
||||
- name: Char Simple Letter Uppercase Z
|
||||
code: '''Z'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2029,7 +2029,7 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: Z
|
||||
- name: Char Simple Letter z
|
||||
- name: Char Simple Letter Lowercase z
|
||||
code: '''z'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2317,42 +2317,6 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: '}'
|
||||
- name: Char Escape Tab
|
||||
code: '''\\t'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\t"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\t"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\t"
|
||||
- name: Char Escape Backslash
|
||||
code: '''\\\\'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: \
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: \
|
||||
stack_final:
|
||||
- type: char
|
||||
value: \
|
||||
- name: Char Escape Null character
|
||||
code: '''\\0'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\0"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\0"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\0"
|
||||
- name: Char Escape Single quote
|
||||
code: '''\\'''''
|
||||
tokens:
|
||||
|
|
@ -2365,18 +2329,6 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: ''''
|
||||
- name: Char Escape Carriage return
|
||||
code: '''\\r'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\r"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\r"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\r"
|
||||
- name: Char Escape Newline
|
||||
code: '''\\n'''
|
||||
tokens:
|
||||
|
|
@ -2395,6 +2347,54 @@
|
|||
value: '
|
||||
|
||||
'
|
||||
- name: Char Escape Null character
|
||||
code: '''\\0'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\0"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\0"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\0"
|
||||
- name: Char Escape Backslash
|
||||
code: '''\\\\'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: \
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: \
|
||||
stack_final:
|
||||
- type: char
|
||||
value: \
|
||||
- name: Char Escape Tab
|
||||
code: '''\\t'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\t"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\t"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\t"
|
||||
- name: Char Escape Carriage return
|
||||
code: '''\\r'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\r"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\r"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\r"
|
||||
- name: Char With Leading Whitespace
|
||||
code: ' ''A'''
|
||||
tokens:
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ class CharTestGenerator(BaseTestGenerator):
|
|||
def generate_basic_tests(self):
|
||||
"""Generate basic character literal tests."""
|
||||
# Simple ASCII letters
|
||||
self.make_success_test("Char Simple Letter A", "'A'", "char", 'A')
|
||||
self.make_success_test("Char Simple Letter a", "'a'", "char", 'a')
|
||||
self.make_success_test("Char Simple Letter Z", "'Z'", "char", 'Z')
|
||||
self.make_success_test("Char Simple Letter z", "'z'", "char", 'z')
|
||||
self.make_success_test("Char Simple Letter Uppercase A", "'A'", "char", 'A')
|
||||
self.make_success_test("Char Simple Letter Lowercase a", "'a'", "char", 'a')
|
||||
self.make_success_test("Char Simple Letter Uppercase Z", "'Z'", "char", 'Z')
|
||||
self.make_success_test("Char Simple Letter Lowercase z", "'z'", "char", 'z')
|
||||
|
||||
# Digits
|
||||
self.make_success_test("Char Digit 0", "'0'", "char", '0')
|
||||
|
|
|
|||
Loading…
Reference in New Issue