Skip to content

Commit 83abd33

Browse files
authored
feat: Global Namespace Operator (#1442)
Introduces the global namespace operator to access global variables directly when they would otherwise be shadowed by a local variable. The operator is a simple dot that is prefixed before the variable name, for example `.foo` would access the global variable `foo`. This feature is achieved by introducing a `ReferenceAccess::Global(AstNode)` variant and a new resolving strategy to annotate such nodes accordingly.
1 parent 427dde5 commit 83abd33

File tree

10 files changed

+528
-34
lines changed

10 files changed

+528
-34
lines changed

compiler/plc_ast/src/ast.rs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -784,25 +784,22 @@ fn replace_reference(
784784

785785
#[derive(Debug, Clone, PartialEq)]
786786
pub enum ReferenceAccess {
787-
/**
788-
* a, a.b
789-
*/
787+
/// `.foo`
788+
Global(Box<AstNode>),
789+
790+
/// a, a.b
790791
Member(Box<AstNode>),
791-
/**
792-
* a[3]
793-
*/
792+
793+
/// a[3]
794794
Index(Box<AstNode>),
795-
/**
796-
* Color#Red
797-
*/
795+
796+
/// Color#Red
798797
Cast(Box<AstNode>),
799-
/**
800-
* a^
801-
*/
798+
799+
/// a^
802800
Deref,
803-
/**
804-
* &a
805-
*/
801+
802+
/// &a
806803
Address,
807804
}
808805

@@ -1101,11 +1098,11 @@ impl AstNode {
11011098
/// Returns the reference-name if this is a flat reference like `a`, or None if this is no flat reference
11021099
pub fn get_flat_reference_name(&self) -> Option<&str> {
11031100
match &self.stmt {
1104-
AstStatement::ReferenceExpr(
1105-
ReferenceExpr { access: ReferenceAccess::Member(reference), .. },
1106-
..,
1107-
) => reference.as_ref().get_flat_reference_name(),
11081101
AstStatement::Identifier(name, ..) => Some(name),
1102+
AstStatement::ReferenceExpr(ReferenceExpr {
1103+
access: ReferenceAccess::Member(reference) | ReferenceAccess::Global(reference),
1104+
..
1105+
}) => reference.as_ref().get_flat_reference_name(),
11091106
_ => None,
11101107
}
11111108
}
@@ -1586,6 +1583,17 @@ impl AstFactory {
15861583
}
15871584
}
15881585

1586+
pub fn create_global_reference(id: AstId, member: AstNode, location: SourceLocation) -> AstNode {
1587+
AstNode {
1588+
stmt: AstStatement::ReferenceExpr(ReferenceExpr {
1589+
access: ReferenceAccess::Global(Box::new(member)),
1590+
base: None,
1591+
}),
1592+
id,
1593+
location,
1594+
}
1595+
}
1596+
15891597
pub fn create_index_reference(
15901598
index: AstNode,
15911599
base: Option<AstNode>,

src/codegen/generators/expression_generator.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2539,7 +2539,18 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
25392539
original_expression: &AstNode,
25402540
) -> Result<ExpressionValue<'ink>, Diagnostic> {
25412541
match (access, base) {
2542-
// expressions like `base.member`, or just `member`
2542+
// `.foo`
2543+
(ReferenceAccess::Global(node), _) => {
2544+
let name = node.get_flat_reference_name().unwrap_or("unknown");
2545+
2546+
self.create_llvm_pointer_value_for_reference(
2547+
None,
2548+
self.get_load_name(node).as_deref().unwrap_or(name),
2549+
node,
2550+
).map(ExpressionValue::LValue)
2551+
}
2552+
2553+
// `base.member` or just `member`
25432554
(ReferenceAccess::Member(member), base) => {
25442555
let base_value = base.map(|it| self.generate_expression_value(it)).transpose()?;
25452556

@@ -2550,16 +2561,16 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
25502561
self.generate_direct_access_expression(base, &base_value, member, &data.access, &data.index)
25512562
} else {
25522563
let member_name = member.get_flat_reference_name().unwrap_or("unknown");
2564+
25532565
self.create_llvm_pointer_value_for_reference(
25542566
base_value.map(|it| it.get_basic_value_enum().into_pointer_value()).as_ref(),
25552567
self.get_load_name(member).as_deref().unwrap_or(member_name),
25562568
original_expression,
2557-
)
2558-
.map(ExpressionValue::LValue)
2569+
).map(ExpressionValue::LValue)
25592570
}
25602571
}
25612572

2562-
// expressions like: base[idx]
2573+
// `base[idx]`
25632574
(ReferenceAccess::Index(array_idx), Some(base)) => {
25642575
if self.annotations.get_type_or_void(base, self.index).is_vla() {
25652576
// vla array needs special handling
@@ -2576,7 +2587,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
25762587
}
25772588
}
25782589

2579-
// INT#target (INT = base)
2590+
// `INT#target` (INT = base)
25802591
(ReferenceAccess::Cast(target), Some(_base)) => {
25812592
if target.as_ref().is_identifier() {
25822593
let mr =
@@ -2587,7 +2598,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
25872598
}
25882599
}
25892600

2590-
// base^
2601+
// `base^`
25912602
(ReferenceAccess::Deref, Some(base)) => {
25922603
let ptr = self.generate_expression_value(base)?;
25932604
Ok(ExpressionValue::LValue(
@@ -2597,20 +2608,20 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
25972608
))
25982609
}
25992610

2600-
// &base
2611+
// `&base`
26012612
(ReferenceAccess::Address, Some(base)) => {
26022613
let lvalue = self.generate_expression_value(base)?;
26032614
Ok(ExpressionValue::RValue(lvalue.get_basic_value_enum()))
26042615
}
26052616

2606-
(ReferenceAccess::Index(_), None) // [idx];
2607-
| (ReferenceAccess::Cast(_), None) // INT#;
2608-
| (ReferenceAccess::Deref, None) // ^;
2609-
| (ReferenceAccess::Address, None) // &;
2617+
(ReferenceAccess::Index(_), None) // [idx];
2618+
| (ReferenceAccess::Cast(_), None) // INT#;
2619+
| (ReferenceAccess::Deref, None) // ^;
2620+
| (ReferenceAccess::Address, None) // &;
26102621
=> Err(Diagnostic::codegen_error(
2611-
"Expected a base-expressions, but found none.",
2622+
"Expected a base expression, but found none",
26122623
original_expression,
2613-
)),
2624+
))
26142625
}
26152626
}
26162627

src/codegen/tests/expression_tests.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,3 +747,87 @@ fn builtin_div_mixed() {
747747

748748
insta::assert_snapshot!(res);
749749
}
750+
751+
#[test]
752+
fn global_namespace_operator() {
753+
let src = r#"
754+
VAR_GLOBAL
755+
foo : DINT;
756+
END_VAR
757+
758+
PROGRAM main
759+
VAR
760+
foo : DINT;
761+
END_VAR
762+
foo := .foo;
763+
foo := .foo + 1;
764+
foo := .foo + .foo;
765+
766+
.foo := foo;
767+
.foo := .foo + 1;
768+
END_PROGRAM
769+
"#;
770+
771+
let res = codegen(src);
772+
insta::assert_snapshot!(res, @r#"
773+
; ModuleID = '<internal>'
774+
source_filename = "<internal>"
775+
776+
%main = type { i32 }
777+
778+
@foo = global i32 0
779+
@main_instance = global %main zeroinitializer
780+
781+
define void @main(%main* %0) {
782+
entry:
783+
%foo = getelementptr inbounds %main, %main* %0, i32 0, i32 0
784+
%load_foo = load i32, i32* @foo, align 4
785+
store i32 %load_foo, i32* %foo, align 4
786+
%load_foo1 = load i32, i32* @foo, align 4
787+
%tmpVar = add i32 %load_foo1, 1
788+
store i32 %tmpVar, i32* %foo, align 4
789+
%load_foo2 = load i32, i32* @foo, align 4
790+
%load_foo3 = load i32, i32* @foo, align 4
791+
%tmpVar4 = add i32 %load_foo2, %load_foo3
792+
store i32 %tmpVar4, i32* %foo, align 4
793+
%load_foo5 = load i32, i32* %foo, align 4
794+
store i32 %load_foo5, i32* @foo, align 4
795+
%load_foo6 = load i32, i32* @foo, align 4
796+
%tmpVar7 = add i32 %load_foo6, 1
797+
store i32 %tmpVar7, i32* @foo, align 4
798+
ret void
799+
}
800+
; ModuleID = '__initializers'
801+
source_filename = "__initializers"
802+
803+
%main = type { i32 }
804+
805+
@main_instance = external global %main
806+
807+
define void @__init_main(%main* %0) {
808+
entry:
809+
%self = alloca %main*, align 8
810+
store %main* %0, %main** %self, align 8
811+
ret void
812+
}
813+
814+
declare void @main(%main*)
815+
; ModuleID = '__init___testproject'
816+
source_filename = "__init___testproject"
817+
818+
%main = type { i32 }
819+
820+
@main_instance = external global %main
821+
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___testproject, i8* null }]
822+
823+
define void @__init___testproject() {
824+
entry:
825+
call void @__init_main(%main* @main_instance)
826+
ret void
827+
}
828+
829+
declare void @__init_main(%main*)
830+
831+
declare void @main(%main*)
832+
"#);
833+
}

src/parser/expressions_parser.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,16 @@ pub fn parse_qualified_reference(lexer: &mut ParseSession) -> Option<AstNode> {
424424
Some(exp)
425425
};
426426
}
427+
// Global Namespace Operator, e.g. `.foo`
428+
(None, Some(KeywordDot)) => {
429+
let location_dot = lexer.location();
430+
lexer.advance();
431+
432+
let expr = parse_atomic_leaf_expression(lexer)?;
433+
let location = location_dot.span(&expr.location);
434+
435+
current = Some(AstFactory::create_global_reference(lexer.next_id(), expr, location));
436+
}
427437
// base._ -> a segment of a qualified reference, we stand right after the dot
428438
(Some(base), Some(KeywordDot)) => {
429439
lexer.advance();

src/parser/tests/expressions_parser_tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,3 +1756,25 @@ fn function_call_array_index() {
17561756
let parse_result = parse(src).0;
17571757
assert_debug_snapshot!(parse_result.implementations[0].statements);
17581758
}
1759+
1760+
#[test]
1761+
fn global_namespace_operator() {
1762+
let src = r#"
1763+
FUNCTION main
1764+
.foo;
1765+
.foo := 1;
1766+
.foo := .foo + 1;
1767+
1768+
foo := 1 + .foo + foo();
1769+
foo := 1 + .foo + 2 + .foo;
1770+
1771+
someFunc(.foo);
1772+
printf("%d$N", .foo);
1773+
END_FUNCTION
1774+
"#;
1775+
1776+
let result = parse(src).0;
1777+
1778+
assert_eq!(&src[result.implementations[0].statements[0].get_location().to_range().unwrap()], ".foo");
1779+
assert_debug_snapshot!(result.implementations[0].statements);
1780+
}

0 commit comments

Comments
 (0)