diff --git a/.github/workflows/rust.yml b/.github/workflows/linux.yml
similarity index 100%
rename from .github/workflows/rust.yml
rename to .github/workflows/linux.yml
diff --git a/.github/workflows/lit.yml b/.github/workflows/lit.yml
new file mode 100644
index 0000000000..4858e5864b
--- /dev/null
+++ b/.github/workflows/lit.yml
@@ -0,0 +1,35 @@
+name: Build
+
+on:
+ # Triggers the workflow on push or pull request events but only for the master branch
+ push:
+ pull_request:
+ branches: [ master ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ lit-linux-debug:
+ name: lit tests (Linux, debug build)
+ runs-on: ubuntu-latest
+ container: ghcr.io/plc-lang/rust-llvm:latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Run `build.sh --lit`
+ shell: bash
+ run: |
+ ./scripts/build.sh --lit
+
+ lit-linux-release:
+ name: lit tests (Linux, release build)
+ runs-on: ubuntu-latest
+ container: ghcr.io/plc-lang/rust-llvm:latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Run `build.sh --lit --release`
+ shell: bash
+ run: |
+ ./scripts/build.sh --lit --release
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index cb17664f45..3d60db547f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,7 @@
*.a
*.elf
*.out
+
+# Garbage generated by llvm-lit
+tests/lit/**/*.txt
+tests/lit/**/Output/
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 21eab6ec81..8241a1f9ec 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -20,9 +20,13 @@
}
},
"args": [
- "target/demo.st",
+ "target/demo.st"
],
- "cwd": "${workspaceFolder}"
+ "cwd": "${workspaceFolder}",
+ "env": {
+ "RUST_LOG": "rusty"
+ },
+ "terminal": "integrated"
},
{
"type": "lldb",
@@ -43,7 +47,11 @@
"args": [
"demo"
],
- "cwd": "${workspaceFolder}"
+ "cwd": "${workspaceFolder}",
+ "env": {
+ "RUST_LOG": "rusty"
+ },
+ "terminal": "integrated"
},
]
}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 2dea6b2af6..e206b259bb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2221,9 +2221,9 @@ dependencies = [
[[package]]
name = "openssl"
-version = "0.10.64"
+version = "0.10.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
+checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
dependencies = [
"bitflags 2.4.2",
"cfg-if",
@@ -2253,9 +2253,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
-version = "0.9.101"
+version = "0.9.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
+checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
dependencies = [
"cc",
"libc",
@@ -2537,6 +2537,7 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
+ "toml",
]
[[package]]
@@ -3023,6 +3024,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "section_mangler"
version = "0.0.1"
+dependencies = [
+ "nom",
+]
[[package]]
name = "security-framework"
diff --git a/README.md b/README.md
index e52386e6b0..f209f5bc75 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,34 @@
-# RuSTy
-[](https://github.com/PLC-lang/ruSTy/actions)
-[](https://codecov.io/gh/PLC-lang/rusty)
-[](https://github.com/XAMPPRocky/tokei)
+
+
+
RuSTy
+
A structured text compiler written in Rust, utilizing the LLVM framework for native code compilation.
-[Structured text](https://en.wikipedia.org/wiki/Structured_text) compiler written in Rust
+

+

+

+

+

-## About RuSTy
+
+ Examples |
+ Documentation |
+ Contributing
+
-RuSTy is a structured text (ST) compiler written in Rust. RuSTy utilizes the
-LLVM framework to compile eventually to native code.
+
-## Getting started
-
-The easiest way to compile this project is to use the provided `Dockerfile`. The project offers a `.devcontainer` when using [VSCode](https://code.visualstudio.com/docs/remote/containers). The Dockerfile offers a linux-image which contains everything you need to run `cargo build` / `cargo test` in the project's root directory.
-
-If you want to build the project without docker, start [here](https://plc-lang.github.io/rusty/build_and_install.html).
-
-### Documentation
-
-The compiler's documentation can be found here: [documentation](https://plc-lang.github.io/rusty/).
-### Contributing
-
-If you want to contribute to the project you should look for some [beginner-friendly issues](https://github.com/PLC-lang/rusty/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and reach out to project's maintainers.
-
-## Why RuSTy
+## Why RuSTy?
Structured Text is a popular language in the domain of automation. A standardized specification of the language ([IEC 61131](https://en.wikipedia.org/wiki/IEC_61131)) was published in the 90s. It was updated several times in the meantime, while its initial spirit - being built for cyclic, robust and deterministic automation applications - still applies.
Several automation platform suppliers built proprietary compilers and runtime libraries, native to the vendor's hard- and software platform.
-RuSTy is aiming towards a _fast_, _modern_ and _open-source_ industry-grade ST compiler for a wide range of platforms, sticking close to the standard.
+RuSTy is aiming towards a **fast**, **modern** and **open-source** industry-grade ST compiler for a wide range of platforms, sticking close to the standard.
-## Dependencies
+## Getting started
+
+The easiest way to compile this project is to use the provided `Dockerfile`. The project offers a `.devcontainer` when using [VSCode](https://code.visualstudio.com/docs/remote/containers). The Dockerfile offers a linux-image which contains everything you need to run `cargo build` / `cargo test` in the project's root directory.
-We use the [_logos_](https://crates.io/crates/logos/)
-crate library to perform lexical analysis before a handwritten recursive decent parser creates the AST.
-Generating LLVM IR is accomplished with the help of [_inkwell_](https://github.com/TheDan64/inkwell), a Rust-wrapper around the native LLVM C-API.
+If you want to build the project without docker, start [here](https://plc-lang.github.io/rusty/build_and_install.html).
\ No newline at end of file
diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs
index 83a218d5ec..f851a8fd31 100644
--- a/compiler/plc_ast/src/ast.rs
+++ b/compiler/plc_ast/src/ast.rs
@@ -487,6 +487,7 @@ pub enum DataType {
PointerType {
name: Option,
referenced_type: Box,
+ auto_deref: Option,
},
StringType {
name: Option,
@@ -504,6 +505,18 @@ pub enum DataType {
},
}
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum AutoDerefType {
+ /// A plain pointer variable with the auto-deref trait, e.g. VAR_IN_OUT or VAR_INPUT{ref} variables
+ Default,
+
+ /// An alias pointer variable, e.g. `foo AT bar : DINT`
+ Alias,
+
+ /// A reference pointer variable, e.g. `foo : REFERENCE TO DINT;`
+ Reference,
+}
+
impl DataType {
pub fn set_name(&mut self, new_name: String) {
match self {
@@ -596,12 +609,14 @@ pub struct AstNode {
#[derive(Debug, Clone, PartialEq)]
pub enum AstStatement {
EmptyStatement(EmptyStatement),
- // a placeholder that indicates a default value of a datatype
+
+ // A placeholder which indicates a default value of a datatype
DefaultValue(DefaultValue),
+
// Literals
Literal(AstLiteral),
- CastStatement(CastStatement),
MultipliedStatement(MultipliedStatement),
+
// Expressions
ReferenceExpr(ReferenceExpr),
Identifier(String),
@@ -613,15 +628,17 @@ pub enum AstStatement {
ParenExpression(Box),
RangeStatement(RangeStatement),
VlaRangeStatement,
- // Assignment
+
+ // TODO: Merge these variants with a `kind` field?
+ // Assignments
Assignment(Assignment),
- // OutputAssignment
OutputAssignment(Assignment),
- //Call Statement
+ RefAssignment(Assignment),
+
CallStatement(CallStatement),
+
// Control Statements
ControlStatement(AstControlStatement),
-
CaseCondition(Box),
ExitStatement(()),
ContinueStatement(()),
@@ -662,6 +679,9 @@ impl Debug for AstNode {
AstStatement::OutputAssignment(Assignment { left, right }) => {
f.debug_struct("OutputAssignment").field("left", left).field("right", right).finish()
}
+ AstStatement::RefAssignment(Assignment { left, right }) => {
+ f.debug_struct("ReferenceAssignment").field("left", left).field("right", right).finish()
+ }
AstStatement::CallStatement(CallStatement { operator, parameters }) => f
.debug_struct("CallStatement")
.field("operator", operator)
@@ -735,9 +755,6 @@ impl Debug for AstNode {
}
AstStatement::ContinueStatement(..) => f.debug_struct("ContinueStatement").finish(),
AstStatement::ExitStatement(..) => f.debug_struct("ExitStatement").finish(),
- AstStatement::CastStatement(CastStatement { target, type_name }) => {
- f.debug_struct("CastStatement").field("type_name", type_name).field("target", target).finish()
- }
AstStatement::ReferenceExpr(ReferenceExpr { access, base }) => {
f.debug_struct("ReferenceExpr").field("kind", access).field("base", base).finish()
}
@@ -855,6 +872,14 @@ impl AstNode {
matches!(self.stmt, AstStatement::EmptyStatement(..))
}
+ pub fn is_assignment(&self) -> bool {
+ matches!(self.stmt, AstStatement::Assignment(..))
+ }
+
+ pub fn is_output_assignment(&self) -> bool {
+ matches!(self.stmt, AstStatement::OutputAssignment(..))
+ }
+
pub fn is_reference(&self) -> bool {
matches!(self.stmt, AstStatement::ReferenceExpr(..))
}
@@ -1280,8 +1305,11 @@ impl AstFactory {
}
/// creates a new Identifier
- pub fn create_identifier(name: &str, location: &SourceLocation, id: AstId) -> AstNode {
- AstNode::new(AstStatement::Identifier(name.to_string()), id, location.clone())
+ pub fn create_identifier(name: &str, location: T, id: AstId) -> AstNode
+ where
+ T: Into,
+ {
+ AstNode::new(AstStatement::Identifier(name.to_string()), id, location.into())
}
pub fn create_unary_expression(
@@ -1315,6 +1343,19 @@ impl AstFactory {
)
}
+ // TODO: Merge `create_assignment`, `create_output_assignment` and `create_ref_assignment`
+ // once the the Assignment AstStatements have been merged and a `kind` field is available
+ // I.e. something like `AstStatement::Assignment { data, kind: AssignmentKind { Normal, Output, Reference } }
+ // and then fn create_assignment(kind: AssignmentKind, ...)
+ pub fn create_ref_assignment(left: AstNode, right: AstNode, id: AstId) -> AstNode {
+ let location = left.location.span(&right.location);
+ AstNode::new(
+ AstStatement::RefAssignment(Assignment { left: Box::new(left), right: Box::new(right) }),
+ id,
+ location,
+ )
+ }
+
pub fn create_member_reference(member: AstNode, base: Option, id: AstId) -> AstNode {
let location = base
.as_ref()
@@ -1413,18 +1454,21 @@ impl AstFactory {
}
}
- pub fn create_call_statement(
+ pub fn create_call_statement(
operator: AstNode,
parameters: Option,
id: usize,
- location: SourceLocation,
- ) -> AstNode {
+ location: T,
+ ) -> AstNode
+ where
+ T: Into,
+ {
AstNode {
stmt: AstStatement::CallStatement(CallStatement {
operator: Box::new(operator),
parameters: parameters.map(Box::new),
}),
- location,
+ location: location.into(),
id,
}
}
diff --git a/compiler/plc_ast/src/lib.rs b/compiler/plc_ast/src/lib.rs
index 7e7d78a7de..33febadee0 100644
--- a/compiler/plc_ast/src/lib.rs
+++ b/compiler/plc_ast/src/lib.rs
@@ -7,3 +7,4 @@ pub mod control_statements;
pub mod literals;
mod pre_processor;
pub mod provider;
+pub mod visitor;
diff --git a/compiler/plc_ast/src/visitor.rs b/compiler/plc_ast/src/visitor.rs
new file mode 100644
index 0000000000..c50b7c3c1b
--- /dev/null
+++ b/compiler/plc_ast/src/visitor.rs
@@ -0,0 +1,724 @@
+//! This module defines the `AstVisitor` trait and its associated macros.
+//! The `AstVisitor` trait provides a set of methods for traversing and visiting ASTs
+
+use crate::ast::AstNode;
+use crate::ast::*;
+use crate::control_statements::{AstControlStatement, ConditionalBlock, ReturnStatement};
+use crate::literals::AstLiteral;
+
+/// Macro that calls the visitor's `visit` method for every AstNode in the passed iterator `iter`.
+macro_rules! visit_all_nodes {
+ ($visitor:expr, $iter:expr) => {
+ for node in $iter {
+ $visitor.visit(node);
+ }
+ };
+}
+
+/// Macro that calls the visitor's `visit` method for every AstNode in the passed sequence of nodes.
+macro_rules! visit_nodes {
+ ($visitor:expr, $($node:expr),*) => {
+ $(
+ $visitor.visit($node);
+ )*
+ };
+}
+
+/// The `Walker` implements the traversal of the AST nodes and Ast-related objects (e.g. CompilationUnit).
+/// The `walk` method is called on the object to visit its children.
+/// If the object passed to a `AstVisitor`'s `visit` method implements the `Walker` trait,
+/// a call to the it's walk function continues the visiting process on its children.
+///
+/// Spliting the traversal logic into a separate trait allows to call the default traversal logic
+/// from the visitor while overriding the visitor's `visit` method for specific nodes.
+///
+/// # Example
+/// ```
+/// use plc_ast::ast::AstNode;
+/// use plc_ast::visitor::Walker;
+/// use plc_ast::visitor::AstVisitor;
+///
+/// struct MyAssignment {
+/// left: AstNode,
+/// right: AstNode,
+/// }
+///
+/// impl Walker for MyAssignment {
+/// fn walk(&self, visitor: &mut V)
+/// where
+/// V: AstVisitor,
+/// {
+/// visitor.visit(&self.right);
+/// visitor.visit(&self.left);
+/// }
+/// }
+/// ```
+///
+pub trait Walker {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor;
+}
+
+/// The `AstVisitor` trait provides a set of methods for visiting different types of AST nodes.
+/// Implementors can individually override the methods they are interested in. When overriding a method,
+/// make sure to call `walk` on the visited statement to visit its children. DO NOT call walk on
+/// the node itself to avoid a recursion (last parameter). Implementors may also decide to not call
+/// the statement's `walk` method to avoid visiting the children of the statement.
+///
+/// The visitor offers strongly typed `visit_X` functions for every node type. The function's signature
+/// is `fn visit_X(&mut self, stmt: &X, node: &AstNode)`. The `stmt` parameter is the unwrapped, typed
+/// node and the `node` parameter is the `AstNode` wrapping the stmt. The `AstNode` node offers access to location
+/// information and the AstId. Note that some nodes are not wrapped in an `AstNode` node (e.g. `CompilationUnit`)
+/// and therefore only the strongly typed node is passed to the `visit_X` function.
+///
+/// # Example
+/// ```
+/// use plc_ast::{
+/// ast::{Assignment, AstNode},
+/// visitor::{AstVisitor, Walker},
+/// };
+///
+/// struct AssignmentCounter {
+/// count: usize,
+/// }
+///
+/// impl AstVisitor for AssignmentCounter {
+/// fn visit_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
+/// self.count += 1;
+/// // visit child nodes
+/// stmt.walk(self);
+/// }
+///
+/// fn visit_output_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
+/// self.count += 1;
+/// // visit child nodes
+/// stmt.walk(self);
+/// }
+/// }
+/// ```
+pub trait AstVisitor: Sized {
+ /// Visits this `AstNode`. The default implementation calls the `walk` method on the node
+ /// and will eventually call the strongly typed `visit` method for the node (e.g. visit_assignment
+ /// if the node is an `AstStatement::Assignment`).
+ /// # Arguments
+ /// * `node` - The `AstNode` node to visit.
+ fn visit(&mut self, node: &AstNode) {
+ node.walk(self)
+ }
+
+ /// Visits a `CompilationUnit` node.
+ /// Make sure to call `walk` on the `CompilationUnit` node to visit its children.
+ /// # Arguments
+ /// * `unit` - The unwraped, typed `CompilationUnit` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_compilation_unit(&mut self, unit: &CompilationUnit) {
+ unit.walk(self)
+ }
+
+ /// Visits an `Implementation` node.
+ /// Make sure to call `walk` on the `Implementation` node to visit its children.
+ /// # Arguments
+ /// * `implementation` - The unwraped, typed `Implementation` node to visit.
+ fn visit_implementation(&mut self, implementation: &Implementation) {
+ implementation.walk(self);
+ }
+
+ /// Visits a `DataTypeDeclaration` node.
+ /// Make sure to call `walk` on the `VariableBlock` node to visit its children.
+ /// # Arguments
+ /// * `block` - The unwraped, typed `VariableBlock` node to visit.
+ fn visit_variable_block(&mut self, block: &VariableBlock) {
+ block.walk(self)
+ }
+
+ /// Visits a `Variable` node.
+ /// Make sure to call `walk` on the `Variable` node to visit its children.
+ /// # Arguments
+ /// * `variable` - The unwraped, typed `Variable` node to visit.
+ fn visit_variable(&mut self, variable: &Variable) {
+ variable.walk(self);
+ }
+
+ /// Visits an enum element `AstNode` node.
+ /// Make sure to call `walk` on the `AstNode` node to visit its children.
+ /// # Arguments
+ /// * `element` - The unwraped, typed `AstNode` node to visit.
+ fn visit_enum_element(&mut self, element: &AstNode) {
+ element.walk(self);
+ }
+
+ /// Visits a `DataTypeDeclaration` node.
+ /// Make sure to call `walk` on the `DataTypeDeclaration` node to visit its children.
+ /// # Arguments
+ /// * `data_type_declaration` - The unwraped, typed `DataTypeDeclaration` node to visit.
+ fn visit_data_type_declaration(&mut self, data_type_declaration: &DataTypeDeclaration) {
+ data_type_declaration.walk(self);
+ }
+
+ /// Visits a `UserTypeDeclaration` node.
+ /// Make sure to call `walk` on the `UserTypeDeclaration` node to visit its children.
+ /// # Arguments
+ /// * `user_type` - The unwraped, typed `UserTypeDeclaration` node to visit.
+ fn visit_user_type_declaration(&mut self, user_type: &UserTypeDeclaration) {
+ user_type.walk(self);
+ }
+
+ /// Visits a `UserTypeDeclaration` node.
+ /// Make sure to call `walk` on the `DataType` node to visit its children.
+ /// # Arguments
+ /// * `data_type` - The unwraped, typed `DataType` node to visit.
+ fn visit_data_type(&mut self, data_type: &DataType) {
+ data_type.walk(self);
+ }
+
+ /// Visits a `Pou` node.
+ /// Make sure to call `walk` on the `Pou` node to visit its children.
+ /// # Arguments
+ /// * `pou` - The unwraped, typed `Pou` node to visit.
+ fn visit_pou(&mut self, pou: &Pou) {
+ pou.walk(self);
+ }
+
+ /// Visits an `EmptyStatement` node.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `EmptyStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_empty_statement(&mut self, _stmt: &EmptyStatement, _node: &AstNode) {}
+
+ /// Visits a `DefaultValue` node.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `DefaultValue` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_default_value(&mut self, _stmt: &DefaultValue, _node: &AstNode) {}
+
+ /// Visits an `AstLiteral` node.
+ /// Make sure to call `walk` on the `AstLiteral` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `AstLiteral` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_literal(&mut self, stmt: &AstLiteral, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `MultipliedStatement` node.
+ /// Make sure to call `walk` on the `MultipliedStatement` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `MultipliedStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_multiplied_statement(&mut self, stmt: &MultipliedStatement, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `ReferenceExpr` node.
+ /// Make sure to call `walk` on the `ReferenceExpr` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `ReferenceExpr` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_reference_expr(&mut self, stmt: &ReferenceExpr, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits an `Identifier` node.
+ /// Make sure to call `walk` on the `Identifier` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `Identifier` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_identifier(&mut self, _stmt: &str, _node: &AstNode) {}
+
+ /// Visits a `DirectAccess` node.
+ /// Make sure to call `walk` on the `DirectAccess` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `DirectAccess` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_direct_access(&mut self, stmt: &DirectAccess, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `HardwareAccess` node.
+ /// Make sure to call `walk` on the `HardwareAccess` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `HardwareAccess` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_hardware_access(&mut self, stmt: &HardwareAccess, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `BinaryExpression` node.
+ /// Make sure to call `walk` on the `BinaryExpression` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `BinaryExpression` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_binary_expression(&mut self, stmt: &BinaryExpression, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `UnaryExpression` node.
+ /// Make sure to call `walk` on the `UnaryExpression` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `UnaryExpression` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_unary_expression(&mut self, stmt: &UnaryExpression, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits an `ExpressionList` node.
+ /// Make sure to call `walk` on the `Vec` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `ExpressionList` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_expression_list(&mut self, stmt: &Vec, _node: &AstNode) {
+ visit_all_nodes!(self, stmt);
+ }
+
+ /// Visits a `ParenExpression` node.
+ /// Make sure to call `walk` on the inner `AstNode` node to visit its children.
+ /// # Arguments
+ /// * `inner` - The unwraped, typed inner `AstNode` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_paren_expression(&mut self, inner: &AstNode, _node: &AstNode) {
+ inner.walk(self)
+ }
+
+ /// Visits a `RangeStatement` node.
+ /// Make sure to call `walk` on the `RangeStatement` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `RangeStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_range_statement(&mut self, stmt: &RangeStatement, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `VlaRangeStatement` node.
+ /// # Arguments
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_vla_range_statement(&mut self, _node: &AstNode) {}
+
+ /// Visits an `Assignment` node.
+ /// Make sure to call `walk` on the `Assignment` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `Assignment` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits an `OutputAssignment` node.
+ /// Make sure to call `walk` on the `Assignment` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `Assignment` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_output_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits an `RefAssignment` node.
+ /// Make sure to call `walk` on the `Assignment` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `Assignment` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_ref_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `CallStatement` node.
+ /// Make sure to call `walk` on the `CallStatement` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `CallStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_call_statement(&mut self, stmt: &CallStatement, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits an `AstControlStatement` node.
+ /// Make sure to call `walk` on the `AstControlStatement` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `AstControlStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_control_statement(&mut self, stmt: &AstControlStatement, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `CaseCondition` node.
+ /// Make sure to call `walk` on the child-`AstNode` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `CaseCondition` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_case_condition(&mut self, child: &AstNode, _node: &AstNode) {
+ child.walk(self)
+ }
+
+ /// Visits an `ExitStatement` node.
+ /// # Arguments
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_exit_statement(&mut self, _node: &AstNode) {}
+
+ /// Visits a `ContinueStatement` node.
+ /// # Arguments
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_continue_statement(&mut self, _node: &AstNode) {}
+
+ /// Visits a `ReturnStatement` node.
+ /// Make sure to call `walk` on the `ReturnStatement` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `ReturnStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_return_statement(&mut self, stmt: &ReturnStatement, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `JumpStatement` node.
+ /// Make sure to call `walk` on the `JumpStatement` node to visit its children.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `JumpStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_jump_statement(&mut self, stmt: &JumpStatement, _node: &AstNode) {
+ stmt.walk(self)
+ }
+
+ /// Visits a `LabelStatement` node.
+ /// # Arguments
+ /// * `stmt` - The unwraped, typed `LabelStatement` node to visit.
+ /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
+ fn visit_label_statement(&mut self, _stmt: &LabelStatement, _node: &AstNode) {}
+}
+
+/// Helper method that walks through a slice of `ConditionalBlock` and applies the visitor's `walk` method to each node.
+fn walk_conditional_blocks(visitor: &mut V, blocks: &[ConditionalBlock])
+where
+ V: AstVisitor,
+{
+ for b in blocks {
+ visit_nodes!(visitor, &b.condition);
+ visit_all_nodes!(visitor, &b.body);
+ }
+}
+
+impl Walker for AstLiteral {
+ fn walk(&self, _visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ // do nothing
+ }
+}
+
+impl Walker for MultipliedStatement {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visitor.visit(&self.element)
+ }
+}
+
+impl Walker for ReferenceExpr {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ if let Some(base) = &self.base {
+ visitor.visit(base);
+ }
+
+ match &self.access {
+ ReferenceAccess::Member(t) | ReferenceAccess::Index(t) | ReferenceAccess::Cast(t) => {
+ visitor.visit(t)
+ }
+ _ => {}
+ }
+ }
+}
+
+impl Walker for DirectAccess {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_nodes!(visitor, &self.index);
+ }
+}
+
+impl Walker for HardwareAccess {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_all_nodes!(visitor, &self.address);
+ }
+}
+
+impl Walker for BinaryExpression {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_nodes!(visitor, &self.left, &self.right);
+ }
+}
+
+impl Walker for UnaryExpression {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_nodes!(visitor, &self.value);
+ }
+}
+
+impl Walker for Assignment {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_nodes!(visitor, &self.left, &self.right);
+ }
+}
+
+impl Walker for RangeStatement {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_nodes!(visitor, &self.start, &self.end);
+ }
+}
+
+impl Walker for CallStatement {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_nodes!(visitor, &self.operator);
+ if let Some(params) = &self.parameters {
+ visit_nodes!(visitor, params);
+ }
+ }
+}
+
+impl Walker for AstControlStatement {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ match self {
+ AstControlStatement::If(stmt) => {
+ walk_conditional_blocks(visitor, &stmt.blocks);
+ visit_all_nodes!(visitor, &stmt.else_block);
+ }
+ AstControlStatement::WhileLoop(stmt) | AstControlStatement::RepeatLoop(stmt) => {
+ visit_nodes!(visitor, &stmt.condition);
+ visit_all_nodes!(visitor, &stmt.body);
+ }
+ AstControlStatement::ForLoop(stmt) => {
+ visit_nodes!(visitor, &stmt.counter, &stmt.start, &stmt.end);
+ visit_all_nodes!(visitor, &stmt.by_step);
+ visit_all_nodes!(visitor, &stmt.body);
+ }
+ AstControlStatement::Case(stmt) => {
+ visit_nodes!(visitor, &stmt.selector);
+ walk_conditional_blocks(visitor, &stmt.case_blocks);
+ visit_all_nodes!(visitor, &stmt.else_block);
+ }
+ }
+ }
+}
+
+impl Walker for ReturnStatement {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_all_nodes!(visitor, &self.condition);
+ }
+}
+
+impl Walker for JumpStatement {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_nodes!(visitor, &self.condition, &self.target);
+ }
+}
+
+impl Walker for AstNode {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ let node = self;
+ match &self.stmt {
+ AstStatement::EmptyStatement(stmt) => visitor.visit_empty_statement(stmt, node),
+ AstStatement::DefaultValue(stmt) => visitor.visit_default_value(stmt, node),
+ AstStatement::Literal(stmt) => visitor.visit_literal(stmt, node),
+ AstStatement::MultipliedStatement(stmt) => visitor.visit_multiplied_statement(stmt, node),
+ AstStatement::ReferenceExpr(stmt) => visitor.visit_reference_expr(stmt, node),
+ AstStatement::Identifier(stmt) => visitor.visit_identifier(stmt, node),
+ AstStatement::DirectAccess(stmt) => visitor.visit_direct_access(stmt, node),
+ AstStatement::HardwareAccess(stmt) => visitor.visit_hardware_access(stmt, node),
+ AstStatement::BinaryExpression(stmt) => visitor.visit_binary_expression(stmt, node),
+ AstStatement::UnaryExpression(stmt) => visitor.visit_unary_expression(stmt, node),
+ AstStatement::ExpressionList(stmt) => visitor.visit_expression_list(stmt, node),
+ AstStatement::ParenExpression(stmt) => visitor.visit_paren_expression(stmt, node),
+ AstStatement::RangeStatement(stmt) => visitor.visit_range_statement(stmt, node),
+ AstStatement::VlaRangeStatement => visitor.visit_vla_range_statement(node),
+ AstStatement::Assignment(stmt) => visitor.visit_assignment(stmt, node),
+ AstStatement::OutputAssignment(stmt) => visitor.visit_output_assignment(stmt, node),
+ AstStatement::RefAssignment(stmt) => visitor.visit_ref_assignment(stmt, node),
+ AstStatement::CallStatement(stmt) => visitor.visit_call_statement(stmt, node),
+ AstStatement::ControlStatement(stmt) => visitor.visit_control_statement(stmt, node),
+ AstStatement::CaseCondition(stmt) => visitor.visit_case_condition(stmt, node),
+ AstStatement::ExitStatement(_stmt) => visitor.visit_exit_statement(node),
+ AstStatement::ContinueStatement(_stmt) => visitor.visit_continue_statement(node),
+ AstStatement::ReturnStatement(stmt) => visitor.visit_return_statement(stmt, node),
+ AstStatement::JumpStatement(stmt) => visitor.visit_jump_statement(stmt, node),
+ AstStatement::LabelStatement(stmt) => visitor.visit_label_statement(stmt, node),
+ }
+ }
+}
+
+impl Walker for CompilationUnit {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ for block in &self.global_vars {
+ visitor.visit_variable_block(block);
+ }
+
+ for user_type in &self.user_types {
+ visitor.visit_user_type_declaration(user_type);
+ }
+
+ for pou in &self.units {
+ visitor.visit_pou(pou);
+ }
+
+ for i in &self.implementations {
+ visitor.visit_implementation(i);
+ }
+ }
+}
+
+impl Walker for UserTypeDeclaration {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visitor.visit_data_type(&self.data_type);
+ visit_all_nodes!(visitor, &self.initializer);
+ }
+}
+
+impl Walker for VariableBlock {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ for v in self.variables.iter() {
+ visitor.visit_variable(v);
+ }
+ }
+}
+
+impl Walker for Variable {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ visit_all_nodes!(visitor, &self.address);
+ visitor.visit_data_type_declaration(&self.data_type_declaration);
+ visit_all_nodes!(visitor, &self.initializer);
+ }
+}
+
+impl Walker for DataType {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ match self {
+ DataType::StructType { variables, .. } => {
+ for v in variables.iter() {
+ visitor.visit_variable(v);
+ }
+ }
+ DataType::EnumType { elements, .. } => {
+ for ele in flatten_expression_list(elements) {
+ visitor.visit_enum_element(ele);
+ }
+ }
+ DataType::SubRangeType { bounds, .. } => {
+ visit_all_nodes!(visitor, bounds);
+ }
+ DataType::ArrayType { bounds, referenced_type, .. } => {
+ visitor.visit(bounds);
+ visitor.visit_data_type_declaration(referenced_type);
+ }
+ DataType::PointerType { referenced_type, .. } => {
+ visitor.visit_data_type_declaration(referenced_type);
+ }
+ DataType::StringType { size, .. } => {
+ visit_all_nodes!(visitor, size);
+ }
+ DataType::VarArgs { referenced_type, .. } => {
+ if let Some(data_type_declaration) = referenced_type {
+ visitor.visit_data_type_declaration(data_type_declaration);
+ }
+ }
+ DataType::GenericType { .. } => {
+ //no further visits
+ }
+ }
+ }
+}
+
+impl Walker for DataTypeDeclaration {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ if let DataTypeDeclaration::DataTypeDefinition { data_type, .. } = self {
+ visitor.visit_data_type(data_type);
+ }
+ }
+}
+
+impl Walker for Option
+where
+ T: Walker,
+{
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ if let Some(node) = self {
+ node.walk(visitor);
+ }
+ }
+}
+
+impl Walker for Pou {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ for block in &self.variable_blocks {
+ visitor.visit_variable_block(block);
+ }
+
+ self.return_type.as_ref().inspect(|rt| visitor.visit_data_type_declaration(rt));
+ }
+}
+
+impl Walker for Implementation {
+ fn walk(&self, visitor: &mut V)
+ where
+ V: AstVisitor,
+ {
+ for n in &self.statements {
+ visitor.visit(n);
+ }
+ }
+}
diff --git a/compiler/plc_diagnostics/src/diagnostics.rs b/compiler/plc_diagnostics/src/diagnostics.rs
index d45546a92d..1aa1441df2 100644
--- a/compiler/plc_diagnostics/src/diagnostics.rs
+++ b/compiler/plc_diagnostics/src/diagnostics.rs
@@ -2,6 +2,7 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
+use crate::diagnostics::diagnostics_registry::DIAGNOSTICS;
use plc_ast::ast::AstNode;
use plc_source::{
source_location::{SourceLocation, SourceLocationFactory},
@@ -88,8 +89,10 @@ impl Diagnostic {
self
}
- pub fn with_error_code(mut self, error_code: &'static str) -> Self {
- self.error_code = error_code;
+ pub fn with_error_code(mut self, code: &'static str) -> Self {
+ debug_assert!(DIAGNOSTICS.get(code).is_some(), "Error {code} does not exist");
+
+ self.error_code = code;
self
}
@@ -199,12 +202,26 @@ impl Diagnostic {
.with_error_code("E006")
}
- pub fn invalid_parameter_count(expected: usize, received: usize, location: SourceLocation) -> Diagnostic {
- Diagnostic::new(
- format!(
- "Invalid parameter count. Received {received} parameters while {expected} parameters were expected.",
- )).with_error_code("E032")
- .with_location(location)
+ pub fn invalid_argument_count(expected: usize, actual: usize, location: T) -> Diagnostic
+ where
+ T: Into,
+ {
+ // Let's be extra fancy here 🕺
+ fn message(value: usize) -> String {
+ match value {
+ 1 => format!("{value} argument"),
+ _ => format!("{value} arguments"),
+ }
+ }
+
+ Diagnostic::new(format!(
+ "this POU takes {expected} but {actual} {was_or_were} supplied",
+ expected = message(expected),
+ actual = message(actual),
+ was_or_were = if actual == 1 { "was" } else { "were" }
+ ))
+ .with_error_code("E032")
+ .with_location(location.into())
}
pub fn unknown_type(type_name: &str, location: SourceLocation) -> Diagnostic {
@@ -217,7 +234,10 @@ impl Diagnostic {
.with_location(location)
}
- pub fn invalid_assignment(right_type: &str, left_type: &str, location: SourceLocation) -> Diagnostic {
+ pub fn invalid_assignment(right_type: &str, left_type: &str, location: T) -> Diagnostic
+ where
+ T: Into,
+ {
Diagnostic::new(format!("Invalid assignment: cannot assign '{right_type}' to '{left_type}'"))
.with_error_code("E037")
.with_location(location)
diff --git a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs
index b9999558bc..f3db285d77 100644
--- a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs
+++ b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs
@@ -99,296 +99,110 @@ impl From<&DiagnosticsRegistry> for DiagnosticsConfiguration {
}
}
+#[rustfmt::skip]
lazy_static! {
- static ref DIAGNOSTICS: FxHashMap<&'static str, DiagnosticEntry> = add_diagnostic!(
- E001,
- Error,
- include_str!("./error_codes/E001.md"), //General Error
- E002,
- Error,
- include_str!("./error_codes/E002.md"), //General IO Error
- E003,
- Error,
- include_str!("./error_codes/E003.md"), //Parameter Error
- E004,
- Error,
- include_str!("./error_codes/E004.md"), //Duplicate Symbol
- E005,
- Error,
- include_str!("./error_codes/E005.md"), //Generic LLVM Error
- E006,
- Error,
- include_str!("./error_codes/E006.md"), //Missing Token
- E007,
- Error,
- include_str!("./error_codes/E007.md"), //Unexpected Token
- E008,
- Error,
- include_str!("./error_codes/E008.md"), //Invalid Range
- E009,
- Error,
- include_str!("./error_codes/E009.md"), //Mismatched Parantheses
- E010,
- Error,
- include_str!("./error_codes/E010.md"), //Invalid Time Literal
- E011,
- Error,
- include_str!("./error_codes/E011.md"), //Invalid Number
- E012,
- Error,
- include_str!("./error_codes/E012.md"), //Missing Case Condition
- E013,
- Warning,
- include_str!("./error_codes/E013.md"), //Keywords shoud contain underscores
- E014,
- Warning,
- include_str!("./error_codes/E014.md"), //Wrong parantheses type
- E015,
- Warning,
- include_str!("./error_codes/E015.md"), //Pointer is not standard
- E016,
- Warning,
- include_str!("./error_codes/E016.md"), //Return types cannot have a default value
- E017,
- Error,
- include_str!("./error_codes/E017.md"), //Classes cannot contain implementations
- E018,
- Error,
- include_str!("./error_codes/E018.md"), //Duplicate Label
- E019,
- Error,
- include_str!("./error_codes/E019.md"), //Classes cannot contain IN_OUT variables
- E020,
- Error,
- include_str!("./error_codes/E020.md"), //Classes cannot contain a return type
- E021,
- Error,
- include_str!("./error_codes/E021.md"), //POUs cannot be extended
- E022,
- Warning,
- include_str!("./error_codes/E022.md"), //Missing action container
- E023,
- Warning,
- include_str!("./error_codes/E023.md"), //Statement with no effect
- E024,
- Warning,
- include_str!("./error_codes/E024.md"), //Invalid pragma location
- E025,
- Error,
- include_str!("./error_codes/E025.md"), // Missing return type
- E026,
- Error,
- include_str!("./error_codes/E026.md"),
- E027,
- Error,
- include_str!("./error_codes/E027.md"),
- E028,
- Error,
- include_str!("./error_codes/E028.md"),
- E029,
- Error,
- include_str!("./error_codes/E029.md"),
- E030,
- Error,
- include_str!("./error_codes/E030.md"),
- E031,
- Error,
- include_str!("./error_codes/E031.md"),
- E032,
- Error,
- include_str!("./error_codes/E032.md"),
- E033,
- Error,
- include_str!("./error_codes/E033.md"),
- E034,
- Error,
- include_str!("./error_codes/E034.md"),
- E035,
- Error,
- include_str!("./error_codes/E035.md"),
- E036,
- Error,
- include_str!("./error_codes/E036.md"),
- E037,
- Error,
- include_str!("./error_codes/E037.md"),
- E038,
- Error,
- include_str!("./error_codes/E038.md"), //Missing type
- E039,
- Warning,
- include_str!("./error_codes/E039.md"),
- E040,
- Error,
- include_str!("./error_codes/E040.md"),
- E041,
- Error,
- include_str!("./error_codes/E041.md"),
- E042,
- Warning,
- include_str!("./error_codes/E042.md"), //Assignment to reference
- E043,
- Error,
- include_str!("./error_codes/E043.md"),
- E044,
- Error,
- include_str!("./error_codes/E044.md"),
- E045,
- Error,
- include_str!("./error_codes/E045.md"),
- E046,
- Error,
- include_str!("./error_codes/E046.md"),
- E047,
- Warning,
- include_str!("./error_codes/E047.md"), //VLAs are always by reference
- E048,
- Error,
- include_str!("./error_codes/E048.md"),
- E049,
- Error,
- include_str!("./error_codes/E049.md"),
- E050,
- Error,
- include_str!("./error_codes/E050.md"),
- E051,
- Error,
- include_str!("./error_codes/E051.md"),
- E052,
- Error,
- include_str!("./error_codes/E052.md"),
- E053,
- Error,
- include_str!("./error_codes/E053.md"),
- E054,
- Error,
- include_str!("./error_codes/E054.md"),
- E055,
- Error,
- include_str!("./error_codes/E055.md"),
- E056,
- Error,
- include_str!("./error_codes/E056.md"),
- E057,
- Error,
- include_str!("./error_codes/E057.md"),
- E058,
- Error,
- include_str!("./error_codes/E058.md"),
- E059,
- Error,
- include_str!("./error_codes/E059.md"),
- E060,
- Info,
- include_str!("./error_codes/E060.md"), //Variable direct access with %
- E061,
- Error,
- include_str!("./error_codes/E061.md"),
- E062,
- Error,
- include_str!("./error_codes/E062.md"),
- E063,
- Error,
- include_str!("./error_codes/E063.md"),
- E064,
- Error,
- include_str!("./error_codes/E064.md"),
- E065,
- Error,
- include_str!("./error_codes/E065.md"),
- E066,
- Error,
- include_str!("./error_codes/E066.md"),
- E067,
- Warning,
- include_str!("./error_codes/E067.md"), //Implicit typecast
- E068,
- Error,
- include_str!("./error_codes/E068.md"),
- E069,
- Error,
- include_str!("./error_codes/E069.md"),
- E070,
- Error,
- include_str!("./error_codes/E070.md"),
- E071,
- Error,
- include_str!("./error_codes/E071.md"),
- E072,
- Error,
- include_str!("./error_codes/E072.md"),
- E073,
- Error,
- include_str!("./error_codes/E073.md"),
- E074,
- Error,
- include_str!("./error_codes/E074.md"),
- E075,
- Error,
- include_str!("./error_codes/E075.md"),
- E076,
- Error,
- include_str!("./error_codes/E076.md"),
- E077,
- Error,
- include_str!("./error_codes/E077.md"),
- E078,
- Error,
- include_str!("./error_codes/E078.md"),
- E079,
- Error,
- include_str!("./error_codes/E079.md"),
- E080,
- Error,
- include_str!("./error_codes/E080.md"),
- E081,
- Error,
- include_str!("./error_codes/E081.md"),
- E082,
- Error,
- include_str!("./error_codes/E082.md"),
- E083,
- Error,
- include_str!("./error_codes/E083.md"),
- E084,
- Error,
- include_str!("./error_codes/E084.md"),
- E085,
- Error,
- include_str!("./error_codes/E085.md"),
- E086,
- Error,
- include_str!("./error_codes/E086.md"),
- E087,
- Error,
- include_str!("./error_codes/E087.md"),
- E088,
- Error,
- include_str!("./error_codes/E088.md"),
- E089,
- Error,
- include_str!("./error_codes/E089.md"),
- E090,
- Warning,
- include_str!("./error_codes/E090.md"), //Incompatible reference Assignment
- E091,
- Warning,
- include_str!("./error_codes/E091.md"),
- E092,
- Info,
- include_str!("./error_codes/E092.md"),
- E093,
- Warning,
- include_str!("./error_codes/E093.md"),
- E094,
- Error,
- include_str!("./error_codes/E094.md"),
- E095,
- Error,
- include_str!("./error_codes/E095.md"), // Action call without `()`
- E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition
- E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range
-);
+ pub static ref DIAGNOSTICS: FxHashMap<&'static str, DiagnosticEntry> = add_diagnostic!(
+ E001, Error, include_str!("./error_codes/E001.md"), //General Error
+ E002, Error, include_str!("./error_codes/E002.md"), //General IO Error
+ E003, Error, include_str!("./error_codes/E003.md"), //Parameter Error
+ E004, Error, include_str!("./error_codes/E004.md"), //Duplicate Symbol
+ E005, Error, include_str!("./error_codes/E005.md"), //Generic LLVM Error
+ E006, Error, include_str!("./error_codes/E006.md"), //Missing Token
+ E007, Error, include_str!("./error_codes/E007.md"), //Unexpected Token
+ E008, Error, include_str!("./error_codes/E008.md"), //Invalid Range
+ E009, Error, include_str!("./error_codes/E009.md"), //Mismatched Parantheses
+ E010, Error, include_str!("./error_codes/E010.md"), //Invalid Time Literal
+ E011, Error, include_str!("./error_codes/E011.md"), //Invalid Number
+ E012, Error, include_str!("./error_codes/E012.md"), //Missing Case Condition
+ E013, Warning, include_str!("./error_codes/E013.md"), //Keywords shoud contain underscores
+ E014, Warning, include_str!("./error_codes/E014.md"), //Wrong parantheses type
+ E015, Warning, include_str!("./error_codes/E015.md"), //Pointer is not standard
+ E016, Warning, include_str!("./error_codes/E016.md"), //Return types cannot have a default value
+ E017, Error, include_str!("./error_codes/E017.md"), //Classes cannot contain implementations
+ E018, Error, include_str!("./error_codes/E018.md"), //Duplicate Label
+ E019, Error, include_str!("./error_codes/E019.md"), //Classes cannot contain IN_OUT variables
+ E020, Error, include_str!("./error_codes/E020.md"), //Classes cannot contain a return type
+ E021, Error, include_str!("./error_codes/E021.md"), //POUs cannot be extended
+ E022, Warning, include_str!("./error_codes/E022.md"), //Missing action container
+ E023, Warning, include_str!("./error_codes/E023.md"), //Statement with no effect
+ E024, Warning, include_str!("./error_codes/E024.md"), //Invalid pragma location
+ E025, Error, include_str!("./error_codes/E025.md"), // Missing return type
+ E026, Error, include_str!("./error_codes/E026.md"),
+ E027, Error, include_str!("./error_codes/E027.md"),
+ E028, Error, include_str!("./error_codes/E028.md"),
+ E029, Error, include_str!("./error_codes/E029.md"),
+ E030, Error, include_str!("./error_codes/E030.md"),
+ E031, Error, include_str!("./error_codes/E031.md"),
+ E032, Error, include_str!("./error_codes/E032.md"),
+ E033, Error, include_str!("./error_codes/E033.md"),
+ E034, Error, include_str!("./error_codes/E034.md"),
+ E035, Error, include_str!("./error_codes/E035.md"),
+ E036, Error, include_str!("./error_codes/E036.md"),
+ E037, Error, include_str!("./error_codes/E037.md"),
+ E038, Error, include_str!("./error_codes/E038.md"), //Missing type
+ E039, Warning, include_str!("./error_codes/E039.md"),
+ E040, Error, include_str!("./error_codes/E040.md"),
+ E041, Error, include_str!("./error_codes/E041.md"),
+ E042, Warning, include_str!("./error_codes/E042.md"), //Assignment to reference
+ E043, Error, include_str!("./error_codes/E043.md"),
+ E044, Error, include_str!("./error_codes/E044.md"),
+ E045, Error, include_str!("./error_codes/E045.md"),
+ E046, Error, include_str!("./error_codes/E046.md"),
+ E047, Warning, include_str!("./error_codes/E047.md"), //VLAs are always by reference
+ E048, Error, include_str!("./error_codes/E048.md"),
+ E049, Error, include_str!("./error_codes/E049.md"),
+ E050, Error, include_str!("./error_codes/E050.md"),
+ E051, Error, include_str!("./error_codes/E051.md"),
+ E052, Error, include_str!("./error_codes/E052.md"),
+ E053, Error, include_str!("./error_codes/E053.md"),
+ E054, Error, include_str!("./error_codes/E054.md"),
+ E055, Error, include_str!("./error_codes/E055.md"),
+ E056, Error, include_str!("./error_codes/E056.md"),
+ E057, Error, include_str!("./error_codes/E057.md"),
+ E058, Error, include_str!("./error_codes/E058.md"),
+ E059, Error, include_str!("./error_codes/E059.md"),
+ E060, Info, include_str!("./error_codes/E060.md"), //Variable direct access with %
+ E061, Error, include_str!("./error_codes/E061.md"),
+ E062, Error, include_str!("./error_codes/E062.md"),
+ E063, Error, include_str!("./error_codes/E063.md"),
+ E064, Error, include_str!("./error_codes/E064.md"),
+ E065, Error, include_str!("./error_codes/E065.md"),
+ E066, Error, include_str!("./error_codes/E066.md"),
+ E067, Warning, include_str!("./error_codes/E067.md"), //Implicit typecast
+ E068, Error, include_str!("./error_codes/E068.md"),
+ E069, Error, include_str!("./error_codes/E069.md"),
+ E070, Error, include_str!("./error_codes/E070.md"),
+ E071, Error, include_str!("./error_codes/E071.md"),
+ E072, Error, include_str!("./error_codes/E072.md"),
+ E073, Error, include_str!("./error_codes/E073.md"),
+ E074, Error, include_str!("./error_codes/E074.md"),
+ E075, Error, include_str!("./error_codes/E075.md"),
+ E076, Error, include_str!("./error_codes/E076.md"),
+ E077, Error, include_str!("./error_codes/E077.md"),
+ E078, Error, include_str!("./error_codes/E078.md"),
+ E079, Error, include_str!("./error_codes/E079.md"),
+ E080, Error, include_str!("./error_codes/E080.md"),
+ E081, Error, include_str!("./error_codes/E081.md"),
+ E082, Error, include_str!("./error_codes/E082.md"),
+ E083, Error, include_str!("./error_codes/E083.md"),
+ E084, Error, include_str!("./error_codes/E084.md"),
+ E085, Error, include_str!("./error_codes/E085.md"),
+ E086, Error, include_str!("./error_codes/E086.md"),
+ E087, Error, include_str!("./error_codes/E087.md"),
+ E088, Error, include_str!("./error_codes/E088.md"),
+ E089, Error, include_str!("./error_codes/E089.md"),
+ E090, Warning, include_str!("./error_codes/E090.md"), //Incompatible reference Assignment
+ E091, Warning, include_str!("./error_codes/E091.md"),
+ E092, Info, include_str!("./error_codes/E092.md"),
+ E093, Warning, include_str!("./error_codes/E093.md"),
+ E094, Error, include_str!("./error_codes/E094.md"),
+ E095, Error, include_str!("./error_codes/E095.md"), // Action call without `()`
+ E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition
+ E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range
+ E098, Error, include_str!("./error_codes/E098.md"), // Invalid `REF=` assignment
+ E099, Error, include_str!("./error_codes/E099.md"), // Invalid `REFERENCE TO` declaration
+ E100, Error, include_str!("./error_codes/E100.md"), // Immutable variable address
+ );
}
#[cfg(test)]
diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md
index 53e170129c..cb2b5ed020 100644
--- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md
+++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md
@@ -1 +1,18 @@
-# Invalid number of parameters
+# Invalid number of arguments
+
+An invalid number of arguments was passed to a POU. For example
+
+```
+FUNCTION foo
+ (* ... *)
+END_FUNCTION
+
+FUNCTION main : DINT
+ foo('bar'); // Error, foo isn't expecting any arguments
+END_FUNCTION
+```
+
+Note that for `FUNCTION`s the argument count must match with the parameter list and can be bigger if a variadic
+parameter is present. For stateful POUs variadic parameters are not supported, thus the argument count must be equal
+or less than the parameter list depending on whether optional arguments such as `VAR_INPUT` or `VAR_OUTPUT` were
+passed or not.
diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E098.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E098.md
new file mode 100644
index 0000000000..f550271947
--- /dev/null
+++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E098.md
@@ -0,0 +1,19 @@
+# Invalid REF= assignment
+
+`REF=` assignments are considered valid if the left-hand side of the assignment is a pointer variable
+and the right-hand side is a variable of the type that is being referenced.
+
+For example assignments such as the following are invalid
+
+```smalltalk
+VAR
+ foo : DINT;
+ bar : DINT;
+ qux : SINT;
+ refFoo : REFERENCE TO DINT;
+END_VAR
+
+refFoo REF= 5; // `5` is not a variable
+foo REF= bar; // `foo` is not a pointer
+refFoo REF= qux; // `refFoo` and `qux` have different types, DINT vs SINT
+```
\ No newline at end of file
diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md
new file mode 100644
index 0000000000..f78cc78448
--- /dev/null
+++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md
@@ -0,0 +1,6 @@
+# Invalid `REFERENCE TO` declaration
+
+`REFERENCE TO` variable declarations are considered valid if the referenced type is not of the following form
+* `foo : REFERENCE TO REFERENCE TO (* ... *)`
+* `foo : ARRAY[...] OF REFERENCE TO (* ... *)`
+* `foo : REF_TO REFERENCE TO (* ... *)`
\ No newline at end of file
diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E100.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E100.md
new file mode 100644
index 0000000000..5d9e852ac4
--- /dev/null
+++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E100.md
@@ -0,0 +1,15 @@
+# Immutable Variable Address
+
+Alias variables are immutable with regards to their pointer address, thus re-assigning an address will return an error. For example the following code will not compile
+```ST
+FUNCTION main
+ VAR
+ foo AT bar : DINT;
+ bar : DINT;
+ baz : DINT;
+ END_VAR
+
+ foo := baz; // Valid, because we are changing the pointers dereferenced value
+ foo REF= baz; // Invalid, `foo` is immutable with regards to it's pointer address
+END_FUNCTION
+```
\ No newline at end of file
diff --git a/compiler/plc_driver/Cargo.toml b/compiler/plc_driver/Cargo.toml
index 743087961a..8eccbdd01a 100644
--- a/compiler/plc_driver/Cargo.toml
+++ b/compiler/plc_driver/Cargo.toml
@@ -16,6 +16,7 @@ plc_index = { path = "../plc_index" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
+toml = "0.5"
clap = { version = "3.0", features = ["derive"] }
rayon = "1.6.1"
tempfile = "3"
diff --git a/compiler/plc_driver/src/cli.rs b/compiler/plc_driver/src/cli.rs
index b086c79cfd..63ae99640e 100644
--- a/compiler/plc_driver/src/cli.rs
+++ b/compiler/plc_driver/src/cli.rs
@@ -5,7 +5,8 @@ use encoding_rs::Encoding;
use plc_diagnostics::diagnostics::{diagnostics_registry::DiagnosticsConfiguration, Diagnostic};
use std::{env, ffi::OsStr, num::ParseIntError, path::PathBuf};
-use plc::{output::FormatOption, ConfigFormat, DebugLevel, ErrorFormat, Target, Threads};
+use plc::output::FormatOption;
+use plc::{ConfigFormat, DebugLevel, ErrorFormat, Target, Threads, DEFAULT_GOT_LAYOUT_FILE};
pub type ParameterError = clap::Error;
@@ -22,6 +23,14 @@ pub struct CompileParameters {
#[clap(short, long, global = true, name = "output-file", help = "Write output to ")]
pub output: Option,
+ #[clap(
+ long = "ast",
+ group = "format",
+ global = true,
+ help = "Emit AST (Abstract Syntax Tree) as output"
+ )]
+ pub output_ast: bool,
+
#[clap(
long = "ir",
group = "format",
@@ -100,6 +109,21 @@ pub struct CompileParameters {
) ]
pub hardware_config: Option,
+ #[clap(
+ name = "got-layout-file",
+ long,
+ global = true,
+ help = "Obtain information about the current custom GOT layout from the given file if it exists.
+ Save information about the generated custom GOT layout to the given file.
+ Format is detected by extension.
+ Supported formats : json, toml",
+ default_value = DEFAULT_GOT_LAYOUT_FILE,
+ parse(try_from_str = validate_config),
+ // FIXME: For some reason, this does not work at the moment but it really should
+ // requires = "online_change"
+ ) ]
+ pub got_layout_file: String,
+
#[clap(
name = "optimization",
long,
@@ -193,6 +217,12 @@ pub struct CompileParameters {
#[clap(name = "check", long, help = "Check only, do not generate any output", global = true)]
pub check_only: bool,
+ #[clap(
+ long,
+ help = "Emit a binary with specific compilation information, suitable for online changes when ran under a conforming runtime"
+ )]
+ pub online_change: bool,
+
#[clap(subcommand)]
pub commands: Option,
}
@@ -308,7 +338,10 @@ pub fn get_config_format(name: &str) -> Option {
impl CompileParameters {
pub fn parse + AsRef>(args: &[T]) -> Result {
- CompileParameters::try_parse_from(args).and_then(|result| {
+ CompileParameters::try_parse_from(args).and_then(|mut result| {
+ result.got_layout_file = String::from("tmp.json");
+ result.online_change = true;
+
if result.sysroot.len() > result.target.len() {
let mut cmd = CompileParameters::command();
Err(cmd.error(
@@ -379,6 +412,11 @@ impl CompileParameters {
self.hardware_config.as_deref().and_then(get_config_format)
}
+ pub fn got_layout_format(&self) -> ConfigFormat {
+ // It is safe to unwrap here, since the provided argument to `--got-online-change` has been checked with `validate_config`
+ get_config_format(&self.got_layout_file).unwrap()
+ }
+
/// Returns the location where the build artifacts should be stored / output
pub fn get_build_location(&self) -> Option {
match &self.commands {
@@ -637,6 +675,16 @@ mod cli_tests {
fn valid_output_formats() {
let parameters = CompileParameters::parse(vec_of_strings!("input.st", "--ir")).unwrap();
assert!(parameters.output_ir);
+ assert!(!parameters.output_ast);
+ assert!(!parameters.output_bit_code);
+ assert!(!parameters.output_obj_code);
+ assert!(!parameters.output_pic_obj);
+ assert!(!parameters.output_shared_obj);
+ assert!(!parameters.output_reloc_code);
+
+ let parameters = CompileParameters::parse(vec_of_strings!("input.st", "--ast")).unwrap();
+ assert!(!parameters.output_ir);
+ assert!(parameters.output_ast);
assert!(!parameters.output_bit_code);
assert!(!parameters.output_obj_code);
assert!(!parameters.output_pic_obj);
@@ -645,6 +693,7 @@ mod cli_tests {
let parameters = CompileParameters::parse(vec_of_strings!("input.st", "--bc")).unwrap();
assert!(!parameters.output_ir);
+ assert!(!parameters.output_ast);
assert!(parameters.output_bit_code);
assert!(!parameters.output_obj_code);
assert!(!parameters.output_pic_obj);
@@ -653,6 +702,7 @@ mod cli_tests {
let parameters = CompileParameters::parse(vec_of_strings!("input.st", "--static")).unwrap();
assert!(!parameters.output_ir);
+ assert!(!parameters.output_ast);
assert!(!parameters.output_bit_code);
assert!(parameters.output_obj_code);
assert!(!parameters.output_pic_obj);
@@ -661,6 +711,7 @@ mod cli_tests {
let parameters = CompileParameters::parse(vec_of_strings!("input.st", "--pic")).unwrap();
assert!(!parameters.output_ir);
+ assert!(!parameters.output_ast);
assert!(!parameters.output_bit_code);
assert!(!parameters.output_obj_code);
assert!(parameters.output_pic_obj);
@@ -669,6 +720,7 @@ mod cli_tests {
let parameters = CompileParameters::parse(vec_of_strings!("input.st", "--shared")).unwrap();
assert!(!parameters.output_ir);
+ assert!(!parameters.output_ast);
assert!(!parameters.output_bit_code);
assert!(!parameters.output_obj_code);
assert!(!parameters.output_pic_obj);
@@ -677,6 +729,7 @@ mod cli_tests {
let parameters = CompileParameters::parse(vec_of_strings!("input.st", "--relocatable")).unwrap();
assert!(!parameters.output_ir);
+ assert!(!parameters.output_ast);
assert!(!parameters.output_bit_code);
assert!(!parameters.output_obj_code);
assert!(!parameters.output_pic_obj);
@@ -685,6 +738,7 @@ mod cli_tests {
let parameters = CompileParameters::parse(vec_of_strings!("input.st")).unwrap();
assert!(!parameters.output_ir);
+ assert!(!parameters.output_ast);
assert!(!parameters.output_bit_code);
assert!(!parameters.output_obj_code);
assert!(!parameters.output_pic_obj);
diff --git a/compiler/plc_driver/src/lib.rs b/compiler/plc_driver/src/lib.rs
index 750095032c..77b5d1ce68 100644
--- a/compiler/plc_driver/src/lib.rs
+++ b/compiler/plc_driver/src/lib.rs
@@ -19,8 +19,8 @@ use std::{
use cli::{CompileParameters, ParameterError, SubCommands};
use pipelines::AnnotatedProject;
use plc::{
- codegen::CodegenContext, linker::LinkerType, output::FormatOption, DebugLevel, ErrorFormat,
- OptimizationLevel, Target, Threads,
+ codegen::CodegenContext, linker::LinkerType, output::FormatOption, ConfigFormat, DebugLevel, ErrorFormat,
+ OnlineChange, OptimizationLevel, Target, Threads, DEFAULT_GOT_LAYOUT_FILE,
};
use plc_diagnostics::{diagnostician::Diagnostician, diagnostics::Diagnostic};
@@ -50,10 +50,13 @@ pub struct CompileOptions {
/// The name of the resulting compiled file
pub output: String,
pub output_format: FormatOption,
+ pub got_layout_file: String,
+ pub got_layout_format: ConfigFormat,
pub optimization: OptimizationLevel,
pub error_format: ErrorFormat,
pub debug_level: DebugLevel,
pub single_module: bool,
+ pub online_change: OnlineChange,
}
impl Default for CompileOptions {
@@ -63,10 +66,13 @@ impl Default for CompileOptions {
build_location: None,
output: String::new(),
output_format: Default::default(),
+ got_layout_file: String::from(DEFAULT_GOT_LAYOUT_FILE),
+ got_layout_format: ConfigFormat::JSON,
optimization: OptimizationLevel::None,
error_format: ErrorFormat::None,
debug_level: DebugLevel::None,
single_module: false,
+ online_change: OnlineChange::Disabled,
}
}
}
@@ -172,10 +178,17 @@ pub fn get_compilation_context + AsRef + Debug>(
build_location: compile_parameters.get_build_location(),
output: project.get_output_name(),
output_format,
+ got_layout_file: compile_parameters.got_layout_file.clone(),
+ got_layout_format: compile_parameters.got_layout_format(),
optimization: compile_parameters.optimization,
error_format: compile_parameters.error_format,
debug_level: compile_parameters.debug_level(),
single_module: compile_parameters.single_module,
+ online_change: if compile_parameters.online_change {
+ OnlineChange::Enabled
+ } else {
+ OnlineChange::Disabled
+ },
};
let libraries =
@@ -247,6 +260,11 @@ pub fn compile_with_options(compile_options: CompilationContext) -> Result<()> {
.index(ctxt.provider())
.annotate(ctxt.provider());
+ if compile_parameters.output_ast {
+ println!("{:#?}", annotated_project.units);
+ return Ok(());
+ }
+
// 4 : Validate
annotated_project.validate(&ctxt, &mut diagnostician)?;
diff --git a/compiler/plc_driver/src/pipelines.rs b/compiler/plc_driver/src/pipelines.rs
index 67d8991311..6a4793e548 100644
--- a/compiler/plc_driver/src/pipelines.rs
+++ b/compiler/plc_driver/src/pipelines.rs
@@ -1,8 +1,10 @@
use std::{
+ collections::HashMap,
env,
fs::{self, File},
io::Write,
path::{Path, PathBuf},
+ sync::Mutex,
};
use crate::{CompileOptions, LinkOptions};
@@ -33,6 +35,42 @@ use project::{
use rayon::prelude::*;
use source_code::{source_location::SourceLocation, SourceContainer};
+use serde_json;
+use toml;
+
+pub fn read_got_layout(location: &str, format: ConfigFormat) -> Result, Diagnostic> {
+ if !Path::new(location).is_file() {
+ // Assume if the file doesn't exist that there is no existing GOT layout yet. write_got_layout will handle
+ // creating our file when we want to.
+ return Ok(HashMap::new());
+ }
+
+ let s = fs::read_to_string(location)
+ .map_err(|_| Diagnostic::new("GOT layout could not be read from file"))?;
+ match format {
+ ConfigFormat::JSON => serde_json::from_str(&s)
+ .map_err(|_| Diagnostic::new("Could not deserialize GOT layout from JSON")),
+ ConfigFormat::TOML => {
+ toml::de::from_str(&s).map_err(|_| Diagnostic::new("Could not deserialize GOT layout from TOML"))
+ }
+ }
+}
+
+fn write_got_layout(
+ got_entries: HashMap,
+ location: &str,
+ format: ConfigFormat,
+) -> Result<(), Diagnostic> {
+ let s = match format {
+ ConfigFormat::JSON => serde_json::to_string(&got_entries)
+ .map_err(|_| Diagnostic::new("Could not serialize GOT layout to JSON"))?,
+ ConfigFormat::TOML => toml::ser::to_string(&got_entries)
+ .map_err(|_| Diagnostic::new("Could not serialize GOT layout to TOML"))?,
+ };
+
+ fs::write(location, s).map_err(|_| Diagnostic::new("GOT layout could not be written to file"))
+}
+
///Represents a parsed project
///For this struct to be built, the project would have been parsed correctly and an AST would have
///been generated
@@ -234,8 +272,15 @@ impl AnnotatedProject {
.iter()
.map(|(unit, dependencies, literals)| {
let context = CodegenContext::create();
- self.generate_module(&context, compile_options, unit, dependencies, literals)
- .map(|it| it.persist_to_string())
+ self.generate_module(
+ &context,
+ compile_options,
+ unit,
+ dependencies,
+ literals,
+ todo!("GOT layout for codegen_to_string?"),
+ )
+ .map(|it| it.persist_to_string())
})
.collect()
}
@@ -249,7 +294,14 @@ impl AnnotatedProject {
.units
.iter()
.map(|(unit, dependencies, literals)| {
- self.generate_module(context, compile_options, unit, dependencies, literals)
+ self.generate_module(
+ context,
+ compile_options,
+ unit,
+ dependencies,
+ literals,
+ todo!("give GOT layout for single modules?"),
+ )
})
.reduce(|a, b| {
let a = a?;
@@ -269,13 +321,16 @@ impl AnnotatedProject {
unit: &CompilationUnit,
dependencies: &FxIndexSet,
literals: &StringLiterals,
+ got_layout: &Mutex>,
) -> Result, Diagnostic> {
let mut code_generator = plc::codegen::CodeGen::new(
context,
compile_options.root.as_deref(),
&unit.file_name,
+ (compile_options.got_layout_file.clone(), compile_options.got_layout_format),
compile_options.optimization,
compile_options.debug_level,
+ compile_options.online_change,
);
//Create a types codegen, this contains all the type declarations
//Associate the index type with LLVM types
@@ -285,8 +340,9 @@ impl AnnotatedProject {
literals,
dependencies,
&self.index,
+ got_layout,
)?;
- code_generator.generate(context, unit, &self.annotations, &self.index, &llvm_index)
+ code_generator.generate(context, unit, &self.annotations, &self.index, llvm_index)
}
pub fn codegen_single_module<'ctx>(
@@ -331,6 +387,10 @@ impl AnnotatedProject {
});
ensure_compile_dirs(targets, &compile_directory)?;
let targets = if targets.is_empty() { &[Target::System] } else { targets };
+
+ let got_layout = read_got_layout(&compile_options.got_layout_file, ConfigFormat::JSON)?;
+ let got_layout = Mutex::new(got_layout);
+
let res = targets
.par_iter()
.map(|target| {
@@ -364,8 +424,15 @@ impl AnnotatedProject {
};
let context = CodegenContext::create(); //Create a build location for the generated object files
- let module =
- self.generate_module(&context, compile_options, unit, dependencies, literals)?;
+ let module = self.generate_module(
+ &context,
+ compile_options,
+ unit,
+ dependencies,
+ literals,
+ &got_layout,
+ )?;
+
module
.persist(
Some(&compile_directory),
@@ -384,6 +451,8 @@ impl AnnotatedProject {
})
.collect::, Diagnostic>>()?;
+ write_got_layout(got_layout.into_inner().unwrap(), &compile_options.got_layout_file, ConfigFormat::JSON)?;
+
Ok(res)
}
diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_function_call.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_function_call.snap
index 3436f9b653..468b3c6ec0 100644
--- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_function_call.snap
+++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_function_call.snap
@@ -5,7 +5,7 @@ expression: "results.join(\"\\n\")"
; ModuleID = 'main.st'
source_filename = "main.st"
-define i16 @main() section "fn-main:i16" {
+define i16 @main() {
entry:
%main = alloca i16, align 2
store i16 0, i16* %main, align 2
@@ -14,9 +14,9 @@ entry:
ret i16 %main_ret
}
-declare i16 @external() section "fn-external:i16"
+declare i16 @external()
; ModuleID = 'external.st'
source_filename = "external.st"
-declare i16 @external() section "fn-external:i16"
+declare i16 @external()
diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_global_var.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_global_var.snap
index 275c650b64..8b2e5d9c13 100644
--- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_global_var.snap
+++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__external_files__external_file_global_var.snap
@@ -5,10 +5,10 @@ expression: "results.join(\"\\n\")"
; ModuleID = 'main.st'
source_filename = "main.st"
-@x = external global i16
-@y = external global i16
+@x = external global i16, section "var-$RUSTY$x:i16"
+@y = external global i16, section "var-$RUSTY$y:i16"
-define i16 @main() section "fn-main:i16" {
+define i16 @main() {
entry:
%main = alloca i16, align 2
store i16 0, i16* %main, align 2
@@ -19,12 +19,12 @@ entry:
ret i16 %main_ret
}
-declare i16 @external() section "fn-external:i16"
+declare i16 @external()
; ModuleID = 'external.st'
source_filename = "external.st"
-@x = external global i16
-@y = external global i16
+@x = external global i16, section "var-$RUSTY$x:i16"
+@y = external global i16, section "var-$RUSTY$y:i16"
-declare i16 @external() section "fn-external:i16"
+declare i16 @external()
diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap
index d66f8c80d5..970c44a292 100644
--- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap
+++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap
@@ -7,9 +7,9 @@ source_filename = "app/file1.st"
%mainProg = type {}
-@mainProg_instance = external global %mainProg, !dbg !0
+@mainProg_instance = external global %mainProg, section "var-$RUSTY$mainProg_instance:r0", !dbg !0
-define i16 @main() section "fn-main:i16" !dbg !10 {
+define i16 @main() !dbg !10 {
entry:
%main = alloca i16, align 2, !dbg !14
call void @llvm.dbg.declare(metadata i16* %main, metadata !15, metadata !DIExpression()), !dbg !17
@@ -19,7 +19,7 @@ entry:
ret i16 %main_ret, !dbg !14
}
-declare !dbg !18 void @mainProg(%mainProg*) section "fn-mainProg:v"
+declare !dbg !18 void @mainProg(%mainProg*)
; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
declare void @llvm.dbg.declare(metadata, metadata, metadata) #0
@@ -54,9 +54,9 @@ source_filename = "lib/file2.st"
%mainProg = type {}
-@mainProg_instance = global %mainProg zeroinitializer, !dbg !0
+@mainProg_instance = global %mainProg zeroinitializer, section "var-$RUSTY$mainProg_instance:r0", !dbg !0
-define void @mainProg(%mainProg* %0) section "fn-mainProg:v" !dbg !10 {
+define void @mainProg(%mainProg* %0) !dbg !10 {
entry:
call void @llvm.dbg.declare(metadata %mainProg* %0, metadata !13, metadata !DIExpression()), !dbg !14
ret void, !dbg !14
diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap
index 10ea84d0cd..d2ed9d7822 100644
--- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap
+++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap
@@ -7,9 +7,9 @@ source_filename = "file1.st"
%mainProg = type {}
-@mainProg_instance = external global %mainProg, !dbg !0
+@mainProg_instance = external global %mainProg, section "var-$RUSTY$mainProg_instance:r0", !dbg !0
-define i16 @main() section "fn-main:i16" !dbg !10 {
+define i16 @main() !dbg !10 {
entry:
%main = alloca i16, align 2, !dbg !14
call void @llvm.dbg.declare(metadata i16* %main, metadata !15, metadata !DIExpression()), !dbg !17
@@ -19,7 +19,7 @@ entry:
ret i16 %main_ret, !dbg !14
}
-declare !dbg !18 void @mainProg(%mainProg*) section "fn-mainProg:v"
+declare !dbg !18 void @mainProg(%mainProg*)
; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
declare void @llvm.dbg.declare(metadata, metadata, metadata) #0
@@ -54,9 +54,9 @@ source_filename = "file2.st"
%mainProg = type {}
-@mainProg_instance = global %mainProg zeroinitializer, !dbg !0
+@mainProg_instance = global %mainProg zeroinitializer, section "var-$RUSTY$mainProg_instance:r0", !dbg !0
-define void @mainProg(%mainProg* %0) section "fn-mainProg:v" !dbg !10 {
+define void @mainProg(%mainProg* %0) !dbg !10 {
entry:
call void @llvm.dbg.declare(metadata %mainProg* %0, metadata !13, metadata !DIExpression()), !dbg !14
ret void, !dbg !14
diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap
index 9d92283477..ae46444677 100644
--- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap
+++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap
@@ -7,9 +7,9 @@ source_filename = "external_file1.st"
%mainProg = type {}
-@mainProg_instance = external global %mainProg
+@mainProg_instance = external global %mainProg, section "var-$RUSTY$mainProg_instance:r0"
-define i16 @main() section "fn-main:i16" {
+define i16 @main() {
entry:
%main = alloca i16, align 2
store i16 0, i16* %main, align 2
@@ -18,16 +18,16 @@ entry:
ret i16 %main_ret
}
-declare void @mainProg(%mainProg*) section "fn-mainProg:v"
+declare void @mainProg(%mainProg*)
; ModuleID = 'external_file2.st'
source_filename = "external_file2.st"
%mainProg = type {}
-@mainProg_instance = global %mainProg zeroinitializer
+@mainProg_instance = global %mainProg zeroinitializer, section "var-$RUSTY$mainProg_instance:r0"
-define void @mainProg(%mainProg* %0) section "fn-mainProg:v" {
+define void @mainProg(%mainProg* %0) {
entry:
ret void
}
diff --git a/compiler/section_mangler/Cargo.toml b/compiler/section_mangler/Cargo.toml
index f3386a21e0..f0cfaf92a4 100644
--- a/compiler/section_mangler/Cargo.toml
+++ b/compiler/section_mangler/Cargo.toml
@@ -4,3 +4,4 @@ version = "0.0.1"
edition = "2021"
[dependencies]
+nom = "7.1"
diff --git a/compiler/section_mangler/src/lib.rs b/compiler/section_mangler/src/lib.rs
index 4e00658c69..759c5cdbf6 100644
--- a/compiler/section_mangler/src/lib.rs
+++ b/compiler/section_mangler/src/lib.rs
@@ -44,25 +44,51 @@
use std::fmt;
+mod parser;
+
/// The main builder type of this crate. Use it to create mangling contexts, in
/// order to encode and decode binary type information.
// TODO: Add example code for using this builder
+#[derive(Debug, PartialEq, Clone)]
pub enum SectionMangler {
Function(FunctionMangler),
Variable(VariableMangler),
}
+#[derive(Debug, PartialEq, Clone)]
pub struct FunctionMangler {
name: String,
parameters: Vec,
return_type: Option,
}
+#[derive(Debug, PartialEq, Clone)]
pub struct VariableMangler {
name: String,
ty: Type,
}
+pub const RUSTY_PREFIX: &str = "$RUSTY$";
+
+// TODO: How to encode variadics?
+fn mangle_function(FunctionMangler { name, parameters, return_type }: FunctionMangler) -> String {
+ /* FIXME: Is that correct? */
+ let return_type = return_type.unwrap_or(Type::Void);
+
+ let mangled = match parameters.as_slice() {
+ [] => format!("{return_type}[]"),
+ parameters => {
+ parameters.iter().fold(return_type.to_string(), |mangled, arg| format!("{mangled}[{arg}]"))
+ }
+ };
+
+ format!("{name}:{mangled}")
+}
+
+fn mangle_variable(VariableMangler { name, ty }: VariableMangler) -> String {
+ format!("{name}:{ty}")
+}
+
impl SectionMangler {
pub fn function>(name: S) -> SectionMangler {
SectionMangler::Function(FunctionMangler { name: name.into(), parameters: vec![], return_type: None })
@@ -72,6 +98,13 @@ impl SectionMangler {
SectionMangler::Variable(VariableMangler { name: name.into(), ty })
}
+ pub fn name(&self) -> &str {
+ match self {
+ SectionMangler::Function(FunctionMangler { name, .. })
+ | SectionMangler::Variable(VariableMangler { name, .. }) => name,
+ }
+ }
+
pub fn with_parameter(self, param: FunctionArgument) -> SectionMangler {
match self {
SectionMangler::Function(f) => {
@@ -84,9 +117,11 @@ impl SectionMangler {
}
}
- pub fn with_return_type(self, return_type: Option) -> SectionMangler {
+ pub fn with_return_type(self, return_type: Type) -> SectionMangler {
match self {
- SectionMangler::Function(f) => SectionMangler::Function(FunctionMangler { return_type, ..f }),
+ SectionMangler::Function(f) => {
+ SectionMangler::Function(FunctionMangler { return_type: Some(return_type), ..f })
+ }
SectionMangler::Variable(_) => unreachable!("global variables do not have a return type."),
}
}
@@ -97,7 +132,7 @@ impl SectionMangler {
SectionMangler::Variable(v) => ("var", mangle_variable(v)),
};
- format!("{prefix}-{content}")
+ format!("{RUSTY_PREFIX}{prefix}-{content}")
}
}
@@ -111,6 +146,7 @@ impl SectionMangler {
// NOTE: This is called `variable_linkage` in the `MemberInfo` struct.
/// We have to encode this because if it changes, the function needs to be reloaded - this is an ABI breakage
+#[derive(Debug, PartialEq, Clone)]
pub enum FunctionArgument {
ByValue(Type),
ByRef(Type),
@@ -127,6 +163,7 @@ impl fmt::Display for FunctionArgument {
}
// TODO: Do we have to encode this? Does that affect ABI? Probably
+#[derive(Debug, PartialEq, Clone)]
pub enum StringEncoding {
// TODO: Should we encode this differently? this could cause problems compared to encoding unsigned types
/// Encoded as `8u`
@@ -146,6 +183,7 @@ impl fmt::Display for StringEncoding {
// This maps directly to the [`DataTypeInformation`] enum in RuSTy - we simply remove some fields and add the ability to encode/decode serialize/deserialize
// TODO: Do we have to handle Generic?
+#[derive(Debug, PartialEq, Clone)]
pub enum Type {
/// Encoded as `v`
Void,
@@ -158,7 +196,9 @@ pub enum Type {
semantic_size: Option,
},
/// Encoded as `f`
- Float { size: u32 },
+ Float {
+ size: u32,
+ },
/// Encoded as `s`
String {
size: usize, // FIXME: Is that okay? will all the constant expressions be folded at that point? Can we have TypeSize::Undetermined still?
@@ -170,23 +210,12 @@ pub enum Type {
// TODO: Is changing the `auto_deref` mode an ABI break?
// auto_deref: bool,
},
-
- // --- UNIMPLEMENTED
-
- // FIXME: Do we need any info here? How are structs codegened?
Struct {
- // name: TypeId,
- // members: Vec,
- // source: StructSource,
+ members: Vec,
},
-
- // FIXME: Same here
Enum {
- // name: TypeId,
- // referenced_type: TypeId,
- // // TODO: Would it make sense to store `VariableIndexEntry`s similar to how the `Struct` variant does?
- // // This would allow us to pattern match in the index `find_member` method
- // elements: Vec,
+ referenced_type: Box,
+ elements: usize,
},
Array {
inner: Box,
@@ -224,27 +253,20 @@ impl fmt::Display for Type {
Type::Float { size } => write!(f, "f{size}"),
Type::String { size, encoding } => write!(f, "s{encoding}{size}",),
Type::Pointer { inner } => write!(f, "p{}", inner),
+ Type::Struct { members } => {
+ write!(
+ f,
+ "r{}{}",
+ members.len(),
+ members.iter().fold(String::new(), |acc, m| format!("{acc}{m}"))
+ )
+ }
+ Type::Enum { referenced_type, elements } => write!(f, "e{elements}{referenced_type}"),
+ Type::Array { inner } => write!(f, "a{inner}"),
// -- Unimplemented
- Type::Struct {} => todo!(),
- Type::Enum {} => todo!(),
- Type::Array { .. } => todo!(),
Type::SubRange {} => todo!(),
Type::Alias {} => todo!(),
Type::Generic {} => todo!(),
}
}
}
-
-// TODO: How to encode variadics?
-fn mangle_function(FunctionMangler { name, parameters, return_type }: FunctionMangler) -> String {
- let mangled = parameters
- .into_iter()
- /* FIXME: Is that correct? */
- .fold(return_type.unwrap_or(Type::Void).to_string(), |mangled, arg| format!("{mangled}[{arg}]"));
-
- format!("{name}:{mangled}")
-}
-
-fn mangle_variable(VariableMangler { name, ty }: VariableMangler) -> String {
- format!("{name}:{ty}")
-}
diff --git a/compiler/section_mangler/src/parser.rs b/compiler/section_mangler/src/parser.rs
new file mode 100644
index 0000000000..7379ca0303
--- /dev/null
+++ b/compiler/section_mangler/src/parser.rs
@@ -0,0 +1,323 @@
+use crate::{FunctionArgument, SectionMangler, StringEncoding, Type};
+
+use std::str;
+
+use nom::branch::alt;
+use nom::bytes::complete::{tag, take_until1};
+use nom::character::complete::{char, digit1};
+use nom::combinator::map_res;
+use nom::multi::{many0, many_m_n};
+use nom::sequence::delimited;
+use nom::{IResult, Parser};
+
+type ParseResult<'i, O> = IResult<&'i str, O>;
+
+enum Prefix {
+ Fn,
+ Var,
+}
+
+fn parse_prefix(input: &str) -> ParseResult {
+ let fn_prefix = tag("fn").map(|_| Prefix::Fn);
+ let var_prefix = tag("var").map(|_| Prefix::Var);
+
+ let (input, _) = tag(crate::RUSTY_PREFIX)(input)?;
+ let (input, prefix) = alt((fn_prefix, var_prefix))(input)?;
+
+ Ok((input, prefix))
+}
+
+fn parse_entity_name(input: &str) -> ParseResult<&str> {
+ delimited(char('-'), take_until1(":"), char(':'))(input)
+}
+
+fn type_void(input: &str) -> ParseResult {
+ char('v').map(|_| Type::Void).parse(input)
+}
+
+fn number(input: &str) -> ParseResult {
+ map_res(digit1, str::parse)(input)
+}
+
+fn type_integer(input: &str) -> ParseResult {
+ fn parse_signedness(input: &str) -> ParseResult {
+ let signed = char('i').map(|_| true);
+ let unsigned = char('u').map(|_| false);
+
+ alt((signed, unsigned))(input)
+ }
+
+ parse_signedness
+ .and(number::)
+ .map(|(signed, size)| Type::Integer { signed, size, semantic_size: None })
+ .parse(input)
+}
+
+fn type_float(input: &str) -> ParseResult {
+ char('f').and(number::).map(|(_, size)| Type::Float { size }).parse(input)
+}
+
+fn type_pointer(input: &str) -> ParseResult {
+ char('p').and(parse_type).map(|(_, inner)| Type::Pointer { inner: Box::new(inner) }).parse(input)
+}
+
+fn type_struct(input: &str) -> ParseResult {
+ let (input, (_, n)) = char('r').and(number::).parse(input)?;
+
+ many_m_n(n, n, parse_type).map(|members| Type::Struct { members }).parse(input)
+}
+
+fn type_enum(input: &str) -> ParseResult {
+ char('e')
+ .and(number::)
+ .and(parse_type)
+ .map(|((_, elements), ty)| Type::Enum { referenced_type: Box::new(ty), elements })
+ .parse(input)
+}
+
+fn string_encoding(input: &str) -> ParseResult {
+ let utf8 = tag("8u").map(|_| StringEncoding::Utf8);
+ let utf16 = tag("16u").map(|_| StringEncoding::Utf16);
+
+ alt((utf8, utf16))(input)
+}
+
+fn type_string(input: &str) -> ParseResult {
+ char('s')
+ .and(string_encoding)
+ .and(number::)
+ .map(|((_, encoding), size)| Type::String { size, encoding })
+ .parse(input)
+}
+
+fn type_array(input: &str) -> ParseResult {
+ char('a').and(parse_type).map(|(_, inner_ty)| Type::Array { inner: Box::new(inner_ty) }).parse(input)
+}
+
+fn parse_type(input: &str) -> ParseResult {
+ alt((type_void, type_integer, type_float, type_pointer, type_struct, type_enum, type_string, type_array))(
+ input,
+ )
+}
+
+fn parse_var_content<'i>(input: &'i str, name: &str) -> ParseResult<'i, SectionMangler> {
+ let (input, ty) = parse_type(input)?;
+
+ Ok((input, SectionMangler::variable(name, ty)))
+}
+
+fn parse_fn_content<'i>(input: &'i str, name: &str) -> ParseResult<'i, SectionMangler> {
+ let (input, return_type) = parse_type(input)?;
+ let (input, parameters) = delimited(char('['), many0(parse_type), char(']'))(input)?;
+
+ // TODO: Do not always encode parameters as ByValue
+ let mangler = parameters
+ .into_iter()
+ .fold(SectionMangler::function(name).with_return_type(return_type), |mangler, param| {
+ mangler.with_parameter(FunctionArgument::ByValue(param))
+ });
+
+ // TODO: Would it be better for the function to encode the number of arguments it has?
+ // or just parse what is in between `[]` like we do currently?
+
+ Ok((input, mangler))
+}
+
+// We don't need to handle any kind of errors, because an invalid mangled string can only be
+// caused by a programming error or a mismatch in versions
+impl From<&str> for SectionMangler {
+ fn from(input: &str) -> SectionMangler {
+ let (input, prefix) = parse_prefix(input).unwrap();
+ let (input, name) = parse_entity_name(input).unwrap();
+
+ match prefix {
+ Prefix::Var => parse_var_content(input, name).unwrap().1,
+ Prefix::Fn => parse_fn_content(input, name).unwrap().1,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn parse_prefix_valid() {
+ assert!(parse_prefix("$RUSTY$fn").is_ok());
+ assert!(parse_prefix("$RUSTY$var").is_ok());
+
+ assert!(parse_prefix("$RUSTY$random").is_err());
+ assert!(parse_prefix("fn").is_err());
+ assert!(parse_prefix("var").is_err());
+
+ assert!(parse_prefix("").is_err());
+ assert!(parse_prefix("a random prefix").is_err());
+ }
+
+ #[test]
+ fn parse_name_valid() {
+ assert_eq!(parse_entity_name("-foo:").unwrap().1, "foo");
+
+ // empty name
+ assert!(parse_entity_name("-:").is_err());
+ }
+
+ #[test]
+ fn parse_integer() {
+ assert!(type_integer("i15").is_ok());
+ assert!(type_integer("u49").is_ok());
+ assert!(type_integer("i0").is_ok());
+
+ assert!(type_integer("i").is_err());
+ assert!(type_integer("b49").is_err());
+ }
+
+ #[test]
+ fn parse_void() {
+ assert!(type_void("v").is_ok());
+
+ assert!(type_void("i15").is_err());
+ }
+
+ #[test]
+ fn parse_float() {
+ assert!(type_float("f15").is_ok());
+ assert!(type_float("f2560").is_ok());
+
+ assert!(type_float("f").is_err());
+ assert!(type_float("i0").is_err());
+ }
+
+ #[test]
+ fn parse_ptr() {
+ assert!(type_pointer("pf15").is_ok());
+ assert!(type_pointer("pv").is_ok());
+ assert!(type_pointer("pi45").is_ok());
+ assert!(type_pointer("pppppppi45").is_ok());
+
+ assert!(type_pointer("p").is_err());
+ assert!(type_pointer("i0").is_err());
+ }
+
+ #[test]
+ fn parse_struct() {
+ assert!(type_struct("r1u8").is_ok());
+ assert!(type_struct("r1r2u8u49").is_ok());
+ assert!(type_struct("r5pu8r1u8u32u32pv").is_ok());
+
+ // these are fine - we parse a struct which is valid, but we have remaining input.
+ // this needs to be handled by the toplevel parse function
+ assert!(type_struct("r0u8u8").is_ok());
+ assert!(type_struct("r1u8u8").is_ok());
+ assert!(type_struct("r5s8u1025s8u2049s8u3u64s8u3").is_ok());
+
+ // invalid number of elements
+ assert!(type_struct("r15u8").is_err());
+ assert!(type_struct("r1").is_err());
+ assert!(type_struct("r2r1u8").is_err());
+ }
+
+ #[test]
+ fn parse_enum() {
+ assert!(type_enum("e15u8").is_ok());
+ assert!(type_enum("e12pv").is_ok());
+
+ assert!(type_enum("e1").is_err());
+ assert!(type_enum("eu8").is_err());
+ }
+
+ #[test]
+ fn parse_variable() {
+ let _ = SectionMangler::from("$RUSTY$var-name:u8");
+ }
+
+ #[test]
+ fn parse_function() {
+ let _ = SectionMangler::from("$RUSTY$fn-foo:u8[]");
+ let _ = SectionMangler::from("$RUSTY$fn-foo:v[]");
+ let _ = SectionMangler::from("$RUSTY$fn-foo:v[pvu8]");
+ let _ = SectionMangler::from("$RUSTY$fn-foo:e156u394[pvu8r1e12u8]");
+ }
+
+ #[test]
+ #[should_panic]
+ fn parse_function_invalid_no_return_type() {
+ let _ = SectionMangler::from("$RUSTY$fn-no_return_type:[]");
+ }
+
+ #[test]
+ #[should_panic]
+ fn parse_function_invalid_no_arguments() {
+ let _ = SectionMangler::from("$RUSTY$fn-no_arguments:u16u8");
+ }
+
+ #[test]
+ fn parse_qualified_var_name() {
+ let mangled = SectionMangler::from("$RUSTY$var-Color.red:e4i32");
+
+ assert_eq!(mangled.name(), "Color.red");
+ }
+
+ #[test]
+ fn parse_complex1() {
+ let mangled = SectionMangler::from("$RUSTY$var-__File__init:r5s8u1025s8u2049s8u3u64s8u3");
+
+ assert_eq!(mangled.name(), "__File__init");
+ }
+
+ #[test]
+ fn parse_complex2() {
+ let inputs = [
+ "$RUSTY$var-__CosineSignal__init:r4f64i32f64f64",
+ "$RUSTY$var-__File__init:r5s8u1025s8u2049s8u3u64s8u3",
+ "$RUSTY$var-__SineSignal__init:r4f64i32f64f64",
+ "$RUSTY$var-__mainProg_state.Init:e3i32",
+ "$RUSTY$var-__mainProg_state.Running:e3i32",
+ "$RUSTY$var-__mainProg_state.Stopped:e3i32",
+ "$RUSTY$var-__SR__init:r3u8u8u8",
+ "$RUSTY$var-__RS__init:r3u8u8u8",
+ "$RUSTY$var-__CTU__init:r6u8u8i16u8i16u8",
+ "$RUSTY$var-__CTU_INT__init:r6u8u8i16u8i16u8",
+ "$RUSTY$var-__CTU_DINT__init:r6u8u8i32u8i32u8",
+ "$RUSTY$var-__CTU_UDINT__init:r6u8u8u32u8u32u8",
+ "$RUSTY$var-__CTU_LINT__init:r6u8u8i64u8i64u8",
+ "$RUSTY$var-__CTU_ULINT__init:r6u8u8u64u8u64u8",
+ "$RUSTY$var-__CTD__init:r6u8u8i16u8i16u8",
+ "$RUSTY$var-__CTD_INT__init:r6u8u8i16u8i16u8",
+ "$RUSTY$var-__CTD_DINT__init:r6u8u8i32u8i32u8",
+ "$RUSTY$var-__CTD_UDINT__init:r6u8u8u32u8u32u8",
+ "$RUSTY$var-__CTD_LINT__init:r6u8u8i64u8i64u8",
+ "$RUSTY$var-__CTD_ULINT__init:r6u8u8u64u8u64u8",
+ "$RUSTY$var-__CTUD__init:r10u8u8u8u8i16u8u8i16u8u8",
+ "$RUSTY$var-__CTUD_INT__init:r10u8u8u8u8i16u8u8i16u8u8",
+ "$RUSTY$var-__CTUD_DINT__init:r10u8u8u8u8i32u8u8i32u8u8",
+ "$RUSTY$var-__CTUD_UDINT__init:r10u8u8u8u8u32u8u8u32u8u8",
+ "$RUSTY$var-__CTUD_LINT__init:r10u8u8u8u8i64u8u8i64u8u8",
+ "$RUSTY$var-__CTUD_ULINT__init:r10u8u8u8u8u64u8u8u64u8u8",
+ "$RUSTY$var-__R_TRIG__init:r3u8u8u8",
+ "$RUSTY$var-__F_TRIG__init:r3u8u8u8",
+ "$RUSTY$var-__TP__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TP_TIME__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TP_LTIME__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TON__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TON_TIME__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TON_LTIME__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TOF__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TOF_TIME__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$var-__TOF_LTIME__init:r7u8i64u8i64u8u8au8",
+ "$RUSTY$fn-CosineSignal:v[f64][i32][f64]",
+ "$RUSTY$fn-File:v[s8u1025][s8u2049][s8u3]",
+ "$RUSTY$fn-File.Open:v",
+ "$RUSTY$fn-File.Write:v",
+ "$RUSTY$fn-File.Close:v",
+ "$RUSTY$fn-File.Clear:v",
+ "$RUSTY$fn-SineSignal:v[f64][i32][f64]",
+ "$RUSTY$fn-mainProg:v",
+ "$RUSTY$var-mainProg:r7i32r4f64i32f64f64r4f64i32f64f64e3i32r5s8u1025s8u2049s8u3u64s8u3r5s8u1025s8u2049s8u3u64s8u3r5s8u1025s8u2049s8u3u64s8u3"
+ ];
+
+ inputs.into_iter().for_each(|input| {
+ let _ = SectionMangler::from(dbg!(input));
+ });
+ }
+}
diff --git a/libs/stdlib/tests/counters_tests.rs b/libs/stdlib/tests/counters_tests.rs
index d9793768b7..431f6ac253 100644
--- a/libs/stdlib/tests/counters_tests.rs
+++ b/libs/stdlib/tests/counters_tests.rs
@@ -28,11 +28,11 @@ fn ctu() {
END_VAR
// count up until Q_res true and then reset CV_res
IF Q_res THEN
- ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q => Q_res, CV => CV_res);
ELSE
- ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res);
// input CU evaluated by R_EDGE, this call will enable to count up again
- ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res);
END_IF
END_PROGRAM
"#;
@@ -70,11 +70,11 @@ fn ctu_int() {
END_VAR
// count up until Q_res true and then reset CV_res
IF Q_res THEN
- ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q => Q_res, CV => CV_res);
ELSE
- ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res);
// input CU evaluated by R_EDGE, this call will enable to count up again
- ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res);
END_IF
END_PROGRAM
"#;
@@ -112,11 +112,11 @@ fn ctu_dint() {
END_VAR
// count up until Q_res true and then reset CV_res
IF Q_res THEN
- ctu_inst(CU:= TRUE, R:= TRUE, PV:= DINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= TRUE, PV:= DINT#3, Q => Q_res, CV => CV_res);
ELSE
- ctu_inst(CU:= TRUE, R:= FALSE, PV:= DINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= FALSE, PV:= DINT#3, Q => Q_res, CV => CV_res);
// input CU evaluated by R_EDGE, this call will enable to count up again
- ctu_inst(CU:= FALSE, R:= FALSE, PV:= DINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= FALSE, R:= FALSE, PV:= DINT#3, Q => Q_res, CV => CV_res);
END_IF
END_PROGRAM
"#;
@@ -154,11 +154,11 @@ fn ctu_udint() {
END_VAR
// count up until Q_res true and then reset CV_res
IF Q_res THEN
- ctu_inst(CU:= TRUE, R:= TRUE, PV:= UDINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= TRUE, PV:= UDINT#3, Q => Q_res, CV => CV_res);
ELSE
- ctu_inst(CU:= TRUE, R:= FALSE, PV:= UDINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= FALSE, PV:= UDINT#3, Q => Q_res, CV => CV_res);
// input CU evaluated by R_EDGE, this call will enable to count up again
- ctu_inst(CU:= FALSE, R:= FALSE, PV:= UDINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= FALSE, R:= FALSE, PV:= UDINT#3, Q => Q_res, CV => CV_res);
END_IF
END_PROGRAM
"#;
@@ -196,11 +196,11 @@ fn ctu_lint() {
END_VAR
// count up until Q_res true and then reset CV_res
IF Q_res THEN
- ctu_inst(CU:= TRUE, R:= TRUE, PV:= LINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= TRUE, PV:= LINT#3, Q => Q_res, CV => CV_res);
ELSE
- ctu_inst(CU:= TRUE, R:= FALSE, PV:= LINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= FALSE, PV:= LINT#3, Q => Q_res, CV => CV_res);
// input CU evaluated by R_EDGE, this call will enable to count up again
- ctu_inst(CU:= FALSE, R:= FALSE, PV:= LINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= FALSE, R:= FALSE, PV:= LINT#3, Q => Q_res, CV => CV_res);
END_IF
END_PROGRAM
"#;
@@ -238,11 +238,11 @@ fn ctu_ulint() {
END_VAR
// count up until Q_res true and then reset CV_res
IF Q_res THEN
- ctu_inst(CU:= TRUE, R:= TRUE, PV:= ULINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= TRUE, PV:= ULINT#3, Q => Q_res, CV => CV_res);
ELSE
- ctu_inst(CU:= TRUE, R:= FALSE, PV:= ULINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= TRUE, R:= FALSE, PV:= ULINT#3, Q => Q_res, CV => CV_res);
// input CU evaluated by R_EDGE, this call will enable to count up again
- ctu_inst(CU:= FALSE, R:= FALSE, PV:= ULINT#3, Q:= Q_res, CV:= CV_res);
+ ctu_inst(CU:= FALSE, R:= FALSE, PV:= ULINT#3, Q => Q_res, CV => CV_res);
END_IF
END_PROGRAM
"#;
@@ -290,12 +290,12 @@ fn ctd() {
END_VAR
// load PV value
IF load THEN
- ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res);
load := FALSE;
END_IF
- ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res);
// input CD evaluated by R_EDGE, this call will enable to count down again
- ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res);
END_PROGRAM
"#;
@@ -333,12 +333,12 @@ fn ctd_int() {
END_VAR
// load PV value
IF load THEN
- ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res);
load := FALSE;
END_IF
- ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res);
// input CD evaluated by R_EDGE, this call will enable to count down again
- ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res);
END_PROGRAM
"#;
@@ -376,12 +376,12 @@ fn ctd_dint() {
END_VAR
// load PV value
IF load THEN
- ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q => Q_res, CV => CV_res);
load := FALSE;
END_IF
- ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q => Q_res, CV => CV_res);
// input CD evaluated by R_EDGE, this call will enable to count down again
- ctd_inst(CD:= FALSE, LD:= load, PV:= DINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= FALSE, LD:= load, PV:= DINT#3, Q => Q_res, CV => CV_res);
END_PROGRAM
"#;
@@ -419,12 +419,12 @@ fn ctd_udint() {
END_VAR
// load PV value
IF load THEN
- ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q => Q_res, CV => CV_res);
load := FALSE;
END_IF
- ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q => Q_res, CV => CV_res);
// input CD evaluated by R_EDGE, this call will enable to count down again
- ctd_inst(CD:= FALSE, LD:= load, PV:= UDINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= FALSE, LD:= load, PV:= UDINT#3, Q => Q_res, CV => CV_res);
END_PROGRAM
"#;
@@ -462,12 +462,12 @@ fn ctd_lint() {
END_VAR
// load PV value
IF load THEN
- ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q => Q_res, CV => CV_res);
load := FALSE;
END_IF
- ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q => Q_res, CV => CV_res);
// input CD evaluated by R_EDGE, this call will enable to count down again
- ctd_inst(CD:= FALSE, LD:= load, PV:= LINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= FALSE, LD:= load, PV:= LINT#3, Q => Q_res, CV => CV_res);
END_PROGRAM
"#;
@@ -505,12 +505,12 @@ fn ctd_ulint() {
END_VAR
// load PV value
IF load THEN
- ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q => Q_res, CV => CV_res);
load := FALSE;
END_IF
- ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q => Q_res, CV => CV_res);
// input CD evaluated by R_EDGE, this call will enable to count down again
- ctd_inst(CD:= FALSE, LD:= load, PV:= ULINT#3, Q:= Q_res, CV:= CV_res);
+ ctd_inst(CD:= FALSE, LD:= load, PV:= ULINT#3, Q => Q_res, CV => CV_res);
END_PROGRAM
"#;
@@ -561,31 +561,31 @@ fn ctud() {
END_VAR
// 1st call, load PV value
IF load THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
load := FALSE;
END_IF
// 2nd call, CU/CD both true, nothing should happen
IF i = 1 THEN
- ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 3rd call, count down
IF i = 2 THEN
// input CD evaluated by R_EDGE, this call will enable count down again
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
- ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
+ ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 4th call, count up
IF i = 3 THEN
// input CU evaluated by R_EDGE, third call enabled count up again
- ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 5th call, reset
IF i = 4 THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
i := i + 1;
END_PROGRAM
@@ -636,31 +636,31 @@ fn ctud_int() {
END_VAR
// 1st call, load PV value
IF load THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
load := FALSE;
END_IF
// 2nd call, CU/CD both true, nothing should happen
IF i = 1 THEN
- ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 3rd call, count down
IF i = 2 THEN
// input CD evaluated by R_EDGE, this call will enable count down again
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
- ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
+ ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 4th call, count up
IF i = 3 THEN
// input CU evaluated by R_EDGE, third call enabled count up again
- ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 5th call, reset
IF i = 4 THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
i := i + 1;
END_PROGRAM
@@ -711,31 +711,31 @@ fn ctud_dint() {
END_VAR
// 1st call, load PV value
IF load THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
load := FALSE;
END_IF
// 2nd call, CU/CD both true, nothing should happen
IF i = 1 THEN
- ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 3rd call, count down
IF i = 2 THEN
// input CD evaluated by R_EDGE, this call will enable count down again
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
- ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
+ ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 4th call, count up
IF i = 3 THEN
// input CU evaluated by R_EDGE, third call enabled count up again
- ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 5th call, reset
IF i = 4 THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
i := i + 1;
END_PROGRAM
@@ -786,31 +786,31 @@ fn ctud_udint() {
END_VAR
// 1st call, load PV value
IF load THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
load := FALSE;
END_IF
// 2nd call, CU/CD both true, nothing should happen
IF i = 1 THEN
- ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 3rd call, count down
IF i = 2 THEN
// input CD evaluated by R_EDGE, this call will enable count down again
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
- ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
+ ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 4th call, count up
IF i = 3 THEN
// input CU evaluated by R_EDGE, third call enabled count up again
- ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 5th call, reset
IF i = 4 THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
i := i + 1;
END_PROGRAM
@@ -861,31 +861,31 @@ fn ctud_lint() {
END_VAR
// 1st call, load PV value
IF load THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
load := FALSE;
END_IF
// 2nd call, CU/CD both true, nothing should happen
IF i = 1 THEN
- ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 3rd call, count down
IF i = 2 THEN
// input CD evaluated by R_EDGE, this call will enable count down again
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
- ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
+ ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 4th call, count up
IF i = 3 THEN
// input CU evaluated by R_EDGE, third call enabled count up again
- ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 5th call, reset
IF i = 4 THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
i := i + 1;
END_PROGRAM
@@ -936,31 +936,31 @@ fn ctud_ulint() {
END_VAR
// 1st call, load PV value
IF load THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
load := FALSE;
END_IF
// 2nd call, CU/CD both true, nothing should happen
IF i = 1 THEN
- ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 3rd call, count down
IF i = 2 THEN
// input CD evaluated by R_EDGE, this call will enable count down again
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
- ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
+ ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 4th call, count up
IF i = 3 THEN
// input CU evaluated by R_EDGE, third call enabled count up again
- ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
// 5th call, reset
IF i = 4 THEN
- ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res);
+ ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res);
END_IF
i := i + 1;
END_PROGRAM
diff --git a/scripts/build.sh b/scripts/build.sh
index 14ece3a910..ab431fc90f 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -8,6 +8,7 @@ check_style=0
build=0
doc=0
test=0
+lit=0
coverage=0
release=0
debug=0
@@ -140,6 +141,19 @@ function run_check_style() {
cargo fmt -- --check
}
+function run_lit_test() {
+ # We need a binary as well as the stdlib and its *.so file before running lit tests
+ run_build
+ run_std_build
+ run_package_std
+
+ if [[ $release -eq 0 ]]; then
+ lit -v -DLIB=$project_location/output -DCOMPILER=$project_location/target/debug/plc tests/lit/
+ else
+ lit -v -DLIB=$project_location/output -DCOMPILER=$project_location/target/release/plc tests/lit/
+ fi
+}
+
function run_test() {
CARGO_OPTIONS=$(set_cargo_options)
log "Running cargo test"
@@ -191,6 +205,8 @@ function set_offline() {
function run_package_std() {
cc=$(get_compiler)
+ OUTPUT_DIR=$project_location/output
+ make_dir "$OUTPUT_DIR"
log "Packaging Standard functions"
log "Removing previous output folder"
rm -rf $OUTPUT_DIR
@@ -291,6 +307,9 @@ function run_in_container() {
if [[ $test -ne 0 ]]; then
params="$params --test"
fi
+ if [[ $lit -ne 0 ]]; then
+ params="$params --lit"
+ fi
if [[ $junit -ne 0 ]]; then
params="$params --junit"
fi
@@ -330,7 +349,7 @@ function run_in_container() {
set -o errexit -o pipefail -o noclobber -o nounset
OPTIONS=sorbvc
-LONGOPTS=sources,offline,release,check,check-style,build,doc,test,junit,verbose,container,linux,container-name:,coverage,package,target:
+LONGOPTS=sources,offline,release,check,check-style,build,doc,lit,test,junit,verbose,container,linux,container-name:,coverage,package,target:
check_env
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
@@ -382,6 +401,9 @@ while true; do
--test)
test=1
;;
+ --lit)
+ lit=1
+ ;;
--junit)
junit=1
;;
@@ -417,11 +439,6 @@ if [[ $container -ne 0 ]]; then
exit 0
fi
-if [[ $package -ne 0 ]]; then
- OUTPUT_DIR=$project_location/output
- make_dir "$OUTPUT_DIR"
-fi
-
if [[ $vendor -ne 0 ]]; then
generate_sources
exit 0
@@ -457,6 +474,10 @@ if [[ $test -ne 0 ]]; then
run_test
fi
+if [[ $lit -ne 0 ]]; then
+ run_lit_test
+fi
+
if [[ $doc -ne 0 ]]; then
run_doc
fi
diff --git a/src/builtins.rs b/src/builtins.rs
index 4deca18bb1..9f5da9f6f9 100644
--- a/src/builtins.rs
+++ b/src/builtins.rs
@@ -92,14 +92,14 @@ lazy_static! {
}),
validation: Some(|validator, operator, parameters, _, _| {
let Some(params) = parameters else {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(1, 0, operator.get_location()));
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(1, 0, operator.get_location()));
return;
};
let params = flatten_expression_list(params);
if params.len() > 1 {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(1, params.len(), operator.get_location()));
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(1, params.len(), operator.get_location()));
}
}),
generic_name_resolver: no_generic_name_resolver,
@@ -579,7 +579,7 @@ fn validate_builtin_symbol_parameter_count(
operation: Operator,
) {
let Some(params) = parameters else {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(2, 0, operator.get_location()));
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 0, operator.get_location()));
return;
};
@@ -588,7 +588,7 @@ fn validate_builtin_symbol_parameter_count(
// non-extensible operators
Operator::Minus | Operator::Division | Operator::NotEqual => {
if count != 2 {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(
2,
count,
operator.get_location(),
@@ -597,7 +597,7 @@ fn validate_builtin_symbol_parameter_count(
}
_ => {
if count < 2 {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(
2,
count,
operator.get_location(),
@@ -746,7 +746,7 @@ fn validate_variable_length_array_bound_function(
index: &Index,
) {
let Some(parameters) = parameters else {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(2, 0, operator.get_location()));
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 0, operator.get_location()));
// no params, nothing to validate
return;
};
@@ -754,7 +754,7 @@ fn validate_variable_length_array_bound_function(
let params = ast::flatten_expression_list(parameters);
if params.len() > 2 {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(
2,
params.len(),
operator.get_location(),
@@ -798,7 +798,7 @@ fn validate_variable_length_array_bound_function(
};
}
(Some(_), None) => {
- validator.push_diagnostic(Diagnostic::invalid_parameter_count(2, 1, operator.get_location()))
+ validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 1, operator.get_location()))
}
_ => unreachable!(),
}
@@ -846,20 +846,6 @@ fn generate_variable_length_array_bound_function<'ink>(
let offset = if is_lower { (value - 1) as u64 * 2 } else { (value - 1) as u64 * 2 + 1 };
llvm.i32_type().const_int(offset, false)
}
- AstStatement::CastStatement(data) => {
- let ExpressionValue::RValue(value) = generator.generate_expression_value(&data.target)? else {
- unreachable!()
- };
-
- if !value.is_int_value() {
- return Err(Diagnostic::codegen_error(
- format!("Expected INT value, found {}", value.get_type()),
- location,
- ));
- };
-
- value.into_int_value()
- }
// e.g. LOWER_BOUND(arr, idx + 3)
_ => {
let expression_value = generator.generate_expression(params[1])?;
diff --git a/src/codegen.rs b/src/codegen.rs
index b141c368ef..2636ffaee8 100644
--- a/src/codegen.rs
+++ b/src/codegen.rs
@@ -1,8 +1,10 @@
// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder
use std::{
cell::RefCell,
+ collections::HashMap,
ops::Deref,
path::{Path, PathBuf},
+ sync::Mutex,
};
/// module to generate llvm intermediate representation for a CompilationUnit
@@ -19,7 +21,7 @@ use self::{
use crate::{
output::FormatOption,
resolver::{AstAnnotations, Dependency, StringLiterals},
- DebugLevel, OptimizationLevel, Target,
+ ConfigFormat, DebugLevel, OnlineChange, OptimizationLevel, Target,
};
use super::index::*;
@@ -29,11 +31,14 @@ use inkwell::{
execution_engine::{ExecutionEngine, JitFunction},
memory_buffer::MemoryBuffer,
types::BasicType,
+ values::BasicValue,
+ AddressSpace,
};
use inkwell::{
module::Module,
passes::PassBuilderOptions,
targets::{CodeModel, FileType, InitializationConfig, RelocMode},
+ types::BasicTypeEnum,
};
use plc_ast::ast::{CompilationUnit, LinkageType};
use plc_diagnostics::diagnostics::Diagnostic;
@@ -70,6 +75,10 @@ pub struct CodeGen<'ink> {
pub module: Module<'ink>,
/// the debugging module creates debug information at appropriate locations
pub debug: DebugBuilderEnum<'ink>,
+ /// Whether we are generating a hot-reloadable binary or not
+ pub online_change: OnlineChange,
+
+ pub got_layout_file: (String, ConfigFormat),
pub module_location: String,
}
@@ -88,13 +97,21 @@ impl<'ink> CodeGen<'ink> {
context: &'ink CodegenContext,
root: Option<&Path>,
module_location: &str,
+ got_layout_file: (String, ConfigFormat),
optimization_level: OptimizationLevel,
debug_level: DebugLevel,
+ online_change: OnlineChange,
) -> CodeGen<'ink> {
let module = context.create_module(module_location);
module.set_source_file_name(module_location);
let debug = debug::DebugBuilderEnum::new(context, &module, root, optimization_level, debug_level);
- CodeGen { module, debug, module_location: module_location.to_string() }
+ CodeGen {
+ module,
+ debug,
+ got_layout_file,
+ module_location: module_location.to_string(),
+ online_change,
+ }
}
pub fn generate_llvm_index(
@@ -104,6 +121,7 @@ impl<'ink> CodeGen<'ink> {
literals: &StringLiterals,
dependencies: &FxIndexSet,
global_index: &Index,
+ got_layout: &Mutex>,
) -> Result, Diagnostic> {
let llvm = Llvm::new(context, context.create_builder());
let mut index = LlvmTypedIndex::default();
@@ -117,14 +135,103 @@ impl<'ink> CodeGen<'ink> {
)?;
index.merge(llvm_type_index);
- let mut variable_generator =
- VariableGenerator::new(&self.module, &llvm, global_index, annotations, &index, &mut self.debug);
+ let mut variable_generator = VariableGenerator::new(
+ &self.module,
+ &llvm,
+ global_index,
+ annotations,
+ &index,
+ &mut self.debug,
+ );
//Generate global variables
let llvm_gv_index =
variable_generator.generate_global_variables(dependencies, &self.module_location)?;
index.merge(llvm_gv_index);
+ // Build our GOT layout here. We need to find all the names for globals, programs, and
+ // functions and assign them indices in the GOT, taking into account prior indices.
+ let program_globals =
+ global_index.get_program_instances().into_iter().fold(Vec::new(), |mut acc, p| {
+ acc.push(p.get_name().to_owned());
+ acc.push(p.get_qualified_name().to_owned());
+ acc.push(format!("{}_instance", p.get_name()));
+ acc
+ });
+
+ let functions = global_index.get_pous().values().filter_map(|p| match p {
+ PouIndexEntry::Function { name, linkage: LinkageType::Internal, is_generated: false, .. }
+ | PouIndexEntry::FunctionBlock { name, linkage: LinkageType::Internal, .. } => {
+ Some(String::from(name))
+ }
+ _ => None,
+ });
+ let all_names = global_index
+ .get_globals()
+ .values()
+ .map(VariableIndexEntry::get_qualified_name)
+ .map(String::from)
+ .chain(program_globals)
+ .chain(functions)
+ .map(|s| s.to_lowercase())
+ .map(|s| (crate::index::get_initializer_name(&s), s))
+ .fold(Vec::new(), |mut acc, (s, s1)| {
+ acc.push(s);
+ acc.push(s1);
+ acc
+ });
+
+
+ if self.online_change == OnlineChange::Enabled {
+ let got_entries = &mut *got_layout.lock().unwrap();
+
+ let mut new_symbols = Vec::new();
+ let mut new_got_entries = HashMap::new();
+ let mut new_got = HashMap::new();
+
+ for name in all_names {
+ if let Some(idx) = got_entries.get(&name.to_string()) {
+ new_got_entries.insert(name.to_string(), *idx);
+ index.associate_got_index(&name, *idx)?;
+ new_got.insert(*idx, name.to_string());
+ } else {
+ new_symbols.push(name.to_string());
+ }
+ }
+
+ // Put any names that weren't there last time in any free space in the GOT.
+ let mut idx: u64 = 0;
+ for name in &new_symbols {
+ while new_got.contains_key(&idx) {
+ idx += 1;
+ }
+ new_got_entries.insert(name.to_string(), idx);
+ index.associate_got_index(name, idx)?;
+ new_got.insert(idx, name.to_string());
+ }
+
+ // Construct our GOT as a new global array. We initialise this array in the loader code.
+ let got_size: u32 = new_got
+ .keys()
+ .max()
+ .map_or(0, |m| *m + 1)
+ .try_into()
+ .expect("the computed custom GOT size is too large");
+
+ let ptr_ty = llvm.context.i8_type().ptr_type(AddressSpace::default());
+ let empty_got = ptr_ty
+ .const_array(vec![ptr_ty.const_null(); got_size as usize].as_slice())
+ .as_basic_value_enum();
+ let custom_got_ty =
+ BasicTypeEnum::ArrayType(Llvm::get_array_type(BasicTypeEnum::PointerType(ptr_ty), got_size));
+
+ let custom_got = llvm.create_global_variable(&self.module, "__custom_got", custom_got_ty);
+ custom_got.set_linkage(inkwell::module::Linkage::WeakODR);
+ custom_got.set_initial_value(Some(empty_got), custom_got_ty);
+
+ *got_entries = new_got_entries;
+ }
+
//Generate opaque functions for implementations and associate them with their types
let llvm = Llvm::new(context, context.create_builder());
let llvm_impl_index = pou_generator::generate_implementation_stubs(
@@ -135,6 +242,7 @@ impl<'ink> CodeGen<'ink> {
annotations,
&index,
&mut self.debug,
+ self.online_change,
)?;
let llvm = Llvm::new(context, context.create_builder());
index.merge(llvm_impl_index);
@@ -193,11 +301,12 @@ impl<'ink> CodeGen<'ink> {
unit: &CompilationUnit,
annotations: &AstAnnotations,
global_index: &Index,
- llvm_index: &LlvmTypedIndex,
+ llvm_index: LlvmTypedIndex,
) -> Result, Diagnostic> {
//generate all pous
let llvm = Llvm::new(context, context.create_builder());
- let pou_generator = PouGenerator::new(llvm, global_index, annotations, llvm_index);
+ let pou_generator =
+ PouGenerator::new(llvm, global_index, annotations, &llvm_index, self.online_change);
//Generate the POU stubs in the first go to make sure they can be referenced.
for implementation in &unit.implementations {
diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs
index 404526afd6..6e88c4e83b 100644
--- a/src/codegen/generators/expression_generator.rs
+++ b/src/codegen/generators/expression_generator.rs
@@ -1,29 +1,17 @@
// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder
-use crate::{
- codegen::{
- debug::{Debug, DebugBuilderEnum},
- llvm_index::LlvmTypedIndex,
- llvm_typesystem::{cast_if_needed, get_llvm_int_type},
- },
- index::{
- const_expressions::ConstId, ArgumentType, ImplementationIndexEntry, Index, PouIndexEntry,
- VariableIndexEntry, VariableType,
- },
- resolver::{AnnotationMap, AstAnnotations, StatementAnnotation},
- typesystem::{
- is_same_type_class, DataType, DataTypeInformation, DataTypeInformationProvider, Dimension,
- StringEncoding, VarArgs, DINT_TYPE, INT_SIZE, INT_TYPE, LINT_TYPE,
- },
-};
+
use inkwell::{
builder::Builder,
- types::{BasicType, BasicTypeEnum},
+ types::{BasicType, BasicTypeEnum, FunctionType},
values::{
- ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, IntValue, PointerValue,
- StructValue, VectorValue,
+ ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue, CallableValue,
+ FloatValue, IntValue, PointerValue, StructValue, VectorValue,
},
AddressSpace, FloatPredicate, IntPredicate,
};
+use rustc_hash::FxHashSet;
+
+use plc_ast::ast::Assignment;
use plc_ast::{
ast::{
flatten_expression_list, AstFactory, AstNode, AstStatement, DirectAccessType, Operator,
@@ -34,10 +22,27 @@ use plc_ast::{
use plc_diagnostics::diagnostics::{Diagnostic, INTERNAL_LLVM_ERROR};
use plc_source::source_location::SourceLocation;
use plc_util::convention::qualified_name;
-use rustc_hash::FxHashSet;
-use std::vec;
+
+use crate::{
+ codegen::{
+ debug::{Debug, DebugBuilderEnum},
+ llvm_index::LlvmTypedIndex,
+ llvm_typesystem::{cast_if_needed, get_llvm_int_type},
+ },
+ index::{
+ const_expressions::ConstId, ArgumentType, ImplementationIndexEntry, Index, PouIndexEntry,
+ VariableIndexEntry, VariableType,
+ },
+ resolver::{AnnotationMap, AstAnnotations, StatementAnnotation},
+ typesystem,
+ typesystem::{
+ is_same_type_class, DataType, DataTypeInformation, DataTypeInformationProvider, Dimension,
+ StringEncoding, VarArgs, DINT_TYPE, INT_SIZE, INT_TYPE, LINT_TYPE,
+ },
+};
use super::{llvm::Llvm, statement_generator::FunctionContext, ADDRESS_SPACE_CONST, ADDRESS_SPACE_GENERIC};
+
/// the generator for expressions
pub struct ExpressionCodeGenerator<'a, 'b> {
pub llvm: &'b Llvm<'a>,
@@ -62,7 +67,7 @@ pub struct ExpressionCodeGenerator<'a, 'b> {
#[derive(Debug)]
struct CallParameterAssignment<'a, 'b> {
/// the assignmentstatement in the call-argument list (a:=3)
- assignment_statement: &'b AstNode,
+ assignment: &'b AstNode,
/// the name of the function we're calling
function_name: &'b str,
/// the position of the argument in the POU's argument's list
@@ -282,6 +287,68 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
self.generate_expression_value(const_expression)
}
+ /// Generate an access to the appropriate GOT entry to achieve an access to the given base
+ /// lvalue.
+ pub fn generate_got_access(
+ &self,
+ context: &AstNode,
+ llvm_type: &BasicTypeEnum<'ink>,
+ ) -> Result