diff --git a/rust_compiler/libs/compiler/src/test/device_access.rs b/rust_compiler/libs/compiler/src/test/device_access.rs index e4dee6b..a607b36 100644 --- a/rust_compiler/libs/compiler/src/test/device_access.rs +++ b/rust_compiler/libs/compiler/src/test/device_access.rs @@ -272,3 +272,108 @@ fn device_property_with_underscore_name() -> anyhow::Result<()> { 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(()) +} diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index d153a69..49c1bbb 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -8,8 +8,8 @@ use parser::{ tree_node::{ AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, - InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression, - LoopExpression, MemberAccessExpression, Spanned, TernaryExpression, + IndexAccessExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable, + LogicalExpression, LoopExpression, MemberAccessExpression, Spanned, TernaryExpression, TupleAssignmentExpression, TupleDeclarationExpression, WhileExpression, }, }; @@ -487,6 +487,50 @@ impl<'a> Compiler<'a> { 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) => { // Methods are not yet fully supported (e.g. `d0.SomeFunc()`). // This would likely map to specialized syscalls or batch instructions. @@ -937,6 +981,32 @@ impl<'a> Compiler<'a> { } (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( format!("`{name_str}` declaration of this type is not supported/implemented."), @@ -1073,6 +1143,37 @@ impl<'a> Compiler<'a> { 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( diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index 5e67e8d..12ed6fd 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -308,7 +308,7 @@ impl<'a> Parser<'a> { 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( &mut self, mut lhs: Spanned>, @@ -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 { break; } @@ -811,6 +845,7 @@ impl<'a> Parser<'a> { | Expression::BitwiseNot(_) | Expression::MemberAccess(_) | Expression::MethodCall(_) + | Expression::IndexAccess(_) | Expression::Tuple(_) => {} _ => { return Err(Error::InvalidSyntax( diff --git a/rust_compiler/libs/parser/src/tree_node.rs b/rust_compiler/libs/parser/src/tree_node.rs index 18e6bff..961c9df 100644 --- a/rust_compiler/libs/parser/src/tree_node.rs +++ b/rust_compiler/libs/parser/src/tree_node.rs @@ -209,6 +209,18 @@ impl<'a> std::fmt::Display for MethodCallExpression<'a> { } } +#[derive(Debug, PartialEq, Eq)] +pub struct IndexAccessExpression<'a> { + pub object: Box>>, + pub index: Box>>, +} + +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)] pub enum LiteralOrVariable<'a> { Literal(Literal<'a>), @@ -402,6 +414,7 @@ pub enum Expression<'a> { TupleDeclaration(Spanned>), Variable(Spanned>), While(Spanned>), + IndexAccess(Spanned>), } 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::Variable(id) => write!(f, "{}", id), Expression::While(e) => write!(f, "{}", e), + Expression::IndexAccess(e) => write!(f, "{}", e), } } }