Array indexing support first pass
This commit is contained in:
@@ -272,3 +272,108 @@ fn device_property_with_underscore_name() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_read() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device printer = \"d0\";
|
||||||
|
let value = printer[255];
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
compiled.errors.is_empty(),
|
||||||
|
"Expected no errors, got: {:?}",
|
||||||
|
compiled.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compiled.output,
|
||||||
|
indoc! {
|
||||||
|
"
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
get r1 d0 255
|
||||||
|
move r8 r1
|
||||||
|
"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_write() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device printer = \"d0\";
|
||||||
|
printer[255] = 42;
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
compiled.errors.is_empty(),
|
||||||
|
"Expected no errors, got: {:?}",
|
||||||
|
compiled.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compiled.output,
|
||||||
|
indoc! {
|
||||||
|
"
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
put d0 255 42
|
||||||
|
"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_db_not_allowed() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device stack = \"db\";
|
||||||
|
let x = stack[10];
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!compiled.errors.is_empty(),
|
||||||
|
"Expected error for db indexing"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
compiled.errors[0]
|
||||||
|
.to_string()
|
||||||
|
.contains("Direct stack access on 'db' is not yet supported"),
|
||||||
|
"Expected db restriction error"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_db_write_not_allowed() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device stack = \"db\";
|
||||||
|
stack[10] = 42;
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!compiled.errors.is_empty(),
|
||||||
|
"Expected error for db indexing"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
compiled.errors[0]
|
||||||
|
.to_string()
|
||||||
|
.contains("Direct stack access on 'db' is not yet supported"),
|
||||||
|
"Expected db restriction error"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ use parser::{
|
|||||||
tree_node::{
|
tree_node::{
|
||||||
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
||||||
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
||||||
InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression,
|
IndexAccessExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable,
|
||||||
LoopExpression, MemberAccessExpression, Spanned, TernaryExpression,
|
LogicalExpression, LoopExpression, MemberAccessExpression, Spanned, TernaryExpression,
|
||||||
TupleAssignmentExpression, TupleDeclarationExpression, WhileExpression,
|
TupleAssignmentExpression, TupleDeclarationExpression, WhileExpression,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -487,6 +487,50 @@ impl<'a> Compiler<'a> {
|
|||||||
temp_name: Some(result_name),
|
temp_name: Some(result_name),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
Expression::IndexAccess(access) => {
|
||||||
|
// "get" behavior (e.g. `let x = d0[255]`)
|
||||||
|
let IndexAccessExpression { object, index } = access.node;
|
||||||
|
|
||||||
|
// 1. Resolve the object to a device string
|
||||||
|
let (device, dev_cleanup) = self.resolve_device(*object, scope)?;
|
||||||
|
|
||||||
|
// Check if device is "db" (not allowed)
|
||||||
|
if let Operand::Device(ref dev_str) = device {
|
||||||
|
if dev_str.as_ref() == "db" {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
"Direct stack access on 'db' is not yet supported".to_string(),
|
||||||
|
Some(expr.span),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Compile the index expression to get the address
|
||||||
|
let (addr, addr_cleanup) = self.compile_operand(*index, scope)?;
|
||||||
|
|
||||||
|
// 3. Allocate a temp register for the result
|
||||||
|
let result_name = self.next_temp_name();
|
||||||
|
let loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
|
||||||
|
let reg = self.resolve_register(&loc)?;
|
||||||
|
|
||||||
|
// 4. Emit get instruction: get rX device address
|
||||||
|
self.write_instruction(
|
||||||
|
Instruction::Get(Operand::Register(reg), device, addr),
|
||||||
|
Some(expr.span),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 5. Cleanup
|
||||||
|
if let Some(c) = dev_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
if let Some(c) = addr_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(CompileLocation {
|
||||||
|
location: loc,
|
||||||
|
temp_name: Some(result_name),
|
||||||
|
}))
|
||||||
|
}
|
||||||
Expression::MethodCall(call) => {
|
Expression::MethodCall(call) => {
|
||||||
// Methods are not yet fully supported (e.g. `d0.SomeFunc()`).
|
// Methods are not yet fully supported (e.g. `d0.SomeFunc()`).
|
||||||
// This would likely map to specialized syscalls or batch instructions.
|
// This would likely map to specialized syscalls or batch instructions.
|
||||||
@@ -937,6 +981,32 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
(var_loc, None)
|
(var_loc, None)
|
||||||
}
|
}
|
||||||
|
Expression::IndexAccess(_) => {
|
||||||
|
// Compile the index access expression
|
||||||
|
let result = self.expression(expr, scope)?;
|
||||||
|
let var_loc = scope.add_variable(
|
||||||
|
name_str.clone(),
|
||||||
|
LocationRequest::Persist,
|
||||||
|
Some(name_span),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(res) = result {
|
||||||
|
// Move result from temp to new persistent variable
|
||||||
|
let result_reg = self.resolve_register(&res.location)?;
|
||||||
|
self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?;
|
||||||
|
|
||||||
|
// Free the temp result
|
||||||
|
if let Some(name) = res.temp_name {
|
||||||
|
scope.free_temp(name, None)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
format!("`{name_str}` index access expression did not produce a value"),
|
||||||
|
Some(name_span),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(var_loc, None)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
format!("`{name_str}` declaration of this type is not supported/implemented."),
|
format!("`{name_str}` declaration of this type is not supported/implemented."),
|
||||||
@@ -1073,6 +1143,37 @@ impl<'a> Compiler<'a> {
|
|||||||
scope.free_temp(c, None)?;
|
scope.free_temp(c, None)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expression::IndexAccess(access) => {
|
||||||
|
// Put instruction: put device address value
|
||||||
|
let IndexAccessExpression { object, index } = access.node;
|
||||||
|
|
||||||
|
let (device, dev_cleanup) = self.resolve_device(*object, scope)?;
|
||||||
|
|
||||||
|
// Check if device is "db" (not allowed)
|
||||||
|
if let Operand::Device(ref dev_str) = device {
|
||||||
|
if dev_str.as_ref() == "db" {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
"Direct stack access on 'db' is not yet supported".to_string(),
|
||||||
|
Some(assignee.span),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (addr, addr_cleanup) = self.compile_operand(*index, scope)?;
|
||||||
|
let (val, val_cleanup) = self.compile_operand(*expression, scope)?;
|
||||||
|
|
||||||
|
self.write_instruction(Instruction::Put(device, addr, val), Some(assignee.span))?;
|
||||||
|
|
||||||
|
if let Some(c) = dev_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
if let Some(c) = addr_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
if let Some(c) = val_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Some(lhs))
|
Ok(Some(lhs))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles dot notation chains: x.y.z()
|
/// Handles dot notation chains: x.y.z() and bracket notation: x[i]
|
||||||
fn parse_postfix(
|
fn parse_postfix(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut lhs: Spanned<Expression<'a>>,
|
mut lhs: Spanned<Expression<'a>>,
|
||||||
@@ -411,6 +411,40 @@ impl<'a> Parser<'a> {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} else if self_matches_peek!(self, TokenType::Symbol(Symbol::LBracket)) {
|
||||||
|
// Index Access
|
||||||
|
self.assign_next()?; // consume '[', now current is '['
|
||||||
|
self.assign_next()?; // advance to the expression, now current is first token of index expr
|
||||||
|
let index = self.expression()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
|
||||||
|
// After expression(), current is still on the last token of the index expression
|
||||||
|
// We need to get the next token and verify it's ']'
|
||||||
|
let rbracket_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
|
||||||
|
if !token_matches!(rbracket_token, TokenType::Symbol(Symbol::RBracket)) {
|
||||||
|
return Err(Error::UnexpectedToken(
|
||||||
|
Self::token_to_span(&rbracket_token),
|
||||||
|
rbracket_token.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_span = Self::token_to_span(&rbracket_token);
|
||||||
|
let combined_span = Span {
|
||||||
|
start_line: lhs.span.start_line,
|
||||||
|
start_col: lhs.span.start_col,
|
||||||
|
end_line: end_span.end_line,
|
||||||
|
end_col: end_span.end_col,
|
||||||
|
};
|
||||||
|
|
||||||
|
lhs = Spanned {
|
||||||
|
span: combined_span,
|
||||||
|
node: Expression::IndexAccess(Spanned {
|
||||||
|
span: combined_span,
|
||||||
|
node: IndexAccessExpression {
|
||||||
|
object: boxed!(lhs),
|
||||||
|
index: boxed!(index),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -811,6 +845,7 @@ impl<'a> Parser<'a> {
|
|||||||
| Expression::BitwiseNot(_)
|
| Expression::BitwiseNot(_)
|
||||||
| Expression::MemberAccess(_)
|
| Expression::MemberAccess(_)
|
||||||
| Expression::MethodCall(_)
|
| Expression::MethodCall(_)
|
||||||
|
| Expression::IndexAccess(_)
|
||||||
| Expression::Tuple(_) => {}
|
| Expression::Tuple(_) => {}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidSyntax(
|
return Err(Error::InvalidSyntax(
|
||||||
|
|||||||
@@ -209,6 +209,18 @@ impl<'a> std::fmt::Display for MethodCallExpression<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct IndexAccessExpression<'a> {
|
||||||
|
pub object: Box<Spanned<Expression<'a>>>,
|
||||||
|
pub index: Box<Spanned<Expression<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for IndexAccessExpression<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}[{}]", self.object, self.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum LiteralOrVariable<'a> {
|
pub enum LiteralOrVariable<'a> {
|
||||||
Literal(Literal<'a>),
|
Literal(Literal<'a>),
|
||||||
@@ -402,6 +414,7 @@ pub enum Expression<'a> {
|
|||||||
TupleDeclaration(Spanned<TupleDeclarationExpression<'a>>),
|
TupleDeclaration(Spanned<TupleDeclarationExpression<'a>>),
|
||||||
Variable(Spanned<Cow<'a, str>>),
|
Variable(Spanned<Cow<'a, str>>),
|
||||||
While(Spanned<WhileExpression<'a>>),
|
While(Spanned<WhileExpression<'a>>),
|
||||||
|
IndexAccess(Spanned<IndexAccessExpression<'a>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Display for Expression<'a> {
|
impl<'a> std::fmt::Display for Expression<'a> {
|
||||||
@@ -450,6 +463,7 @@ impl<'a> std::fmt::Display for Expression<'a> {
|
|||||||
Expression::TupleDeclaration(e) => write!(f, "{}", e),
|
Expression::TupleDeclaration(e) => write!(f, "{}", e),
|
||||||
Expression::Variable(id) => write!(f, "{}", id),
|
Expression::Variable(id) => write!(f, "{}", id),
|
||||||
Expression::While(e) => write!(f, "{}", e),
|
Expression::While(e) => write!(f, "{}", e),
|
||||||
|
Expression::IndexAccess(e) => write!(f, "{}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user