{"id":28399173,"url":"https://github.com/roboplc/logicline","last_synced_at":"2026-02-23T22:32:06.132Z","repository":{"id":292733591,"uuid":"981765531","full_name":"roboplc/logicline","owner":"roboplc","description":"Logic processing and visualization engine for Rust","archived":false,"fork":false,"pushed_at":"2025-05-25T19:44:55.000Z","size":848,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-12T07:52:39.791Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roboplc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-11T20:42:32.000Z","updated_at":"2025-05-29T10:39:44.000Z","dependencies_parsed_at":"2025-05-11T21:39:54.927Z","dependency_job_id":null,"html_url":"https://github.com/roboplc/logicline","commit_stats":null,"previous_names":["roboplc/logicline"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/roboplc/logicline","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roboplc%2Flogicline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roboplc%2Flogicline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roboplc%2Flogicline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roboplc%2Flogicline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roboplc","download_url":"https://codeload.github.com/roboplc/logicline/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roboplc%2Flogicline/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262484027,"owners_count":23318382,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-01T07:13:32.910Z","updated_at":"2026-02-23T22:32:06.104Z","avatar_url":"https://github.com/roboplc.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch2\u003e\n  Logic Line - a logic processing engine for Rust\n  \u003ca href=\"https://crates.io/crates/logicline\"\u003e\u003cimg alt=\"crates.io page\" src=\"https://img.shields.io/crates/v/logicline.svg\"\u003e\u003c/img\u003e\u003c/a\u003e\n  \u003ca href=\"https://docs.rs/logicline\"\u003e\u003cimg alt=\"docs.rs page\" src=\"https://docs.rs/logicline/badge.svg\"\u003e\u003c/img\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/roboplc/logicline/actions/workflows/ci.yml\"\u003e\n    \u003cimg alt=\"GitHub Actions CI\" src=\"https://github.com/roboplc/logicline/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/img\u003e\n  \u003c/a\u003e\n\u003c/h2\u003e\n\n\u003cimg src=\"https://raw.githubusercontent.com/roboplc/logicline/main/pic/ll.gif\"\nwidth=\"800\" /\u003e\n\n## Introduction\n\nProcessing system state and events has got the following problems:\n\n* Visual programming languages are good for visualizing the flow of data, but\n  they are extremely limited in terms using them for large and complex logic\n  rule sets.\n\n* Text-based programming languages are good for writing complex logic, but they\n  lack the ability to visualize the flow of data, both for schema validation in\n  development and for flow visualization in debugging or production.\n\n* If the set contains a lot of rules, their notation must be as compact as\n  possible, otherwise it becomes unreadable and hard to maintain.\n\nLogic Line is a logic processing engine that combines the best of both worlds.\nInspired by [Ladder Logic](https://en.wikipedia.org/wiki/Ladder_logic),\n[Monads](https://en.wikipedia.org/wiki/Monad_(functional_programming)) and some\nothers, it allows to write chains of logic rules as a regular Rust code, but\nprovides built-in tools for state recording, debugging and visualization.\n\n## Architecture\n\nThe library has the following components:\n\n**Rack** → **Processor** → **Line** → **Step** → **Action**\n\nWhere:\n\n* **Rack** is a logic state either for the whole process or for a group of\n  rules. In the first case, a [`global`] module can be used which contains a\n  pre-defined process global instance. Rack can also act as an object factory\n  for [`Processor`] instances, in case if created from a [`Rack`] instance, the\n  processors have the common recording flag.\n\n```rust\nuse logicline::Rack;\n\nlet rack = Rack::new();\n```\n\n* **Processor** is a logic processor that processes a chain of rules. The\n  processor creates `lines`. The processors can share common state between\n  threads or other program parts.\n\n```rust\nuse logicline::Rack;\n\nlet rack = Rack::new();\nlet processor = rack.processor();\n```\n\n* **Line** is an instance which is used to structure logic as a sequence of\n  steps (monad-like objects). Each step includes a single or multiple action\n  objects which are [`std::ops::FnOnce`] instances, wrapped into a structure\n  which also contains the function name and its input parameter:\n\n    (M a) → (a → M b) → (M b)\n\n```rust,ignore\nuse logicline::{action, Rack};\n\nlet mut rack = Rack::new().with_recording_enabled();\nlet mut processor = rack.processor();\n\n// Some fan state\nlet mut fan = false;\n\n// A temperature sensor value\nlet temperature = 31.0;\n\nprocessor\n    // a sequence to turn on the fan on if the temperature is above 30 degrees\n    .line(\"fan_on\", temperature)\n    .then(action!(\"temp_high\", |t| (t \u003e 30.0).then_some(())))\n    .then(action!(\"fan_on\", |()| {\n        fan = true;\n        Some(())\n    }));\nprocessor\n    // a sequence to turn off the fan if the temperature is below 25 degrees\n    .line(\"fan_off\", temperature)\n    .then(action!(\"temp_low\", |t| (t \u003c 25.0).then_some(())))\n    .then(action!(\"fan_off\", |()| {\n        fan = false;\n        Some(())\n    }));\n\nrack.ingress(\u0026mut processor);\n```\n\nWhen recorded, the rack state can be printed in a human-readable format:\n\n```rust,ignore\nprintln!(\"{}\", rack);\n```\n\n```ignore\nfan_off: temp_low(31.0) ! -\u003e fan_off\nfan_on: temp_high(31.0) -\u003e fan_on\n```\n\nOr serialized, e.g. to JSON for web visualization:\n\n```rust,ignore\nlet serialized = serde_json::to_string_pretty(\u0026rack).unwrap();\n```\n\n```json\n{\n  \"lines\": {\n    \"fan_off\": {\n      \"name\": \"fan_off\",\n      \"steps\": [\n        {\n          \"name\": \"temp_low\",\n          \"input\": 31.0,\n          \"input_kind\": \"flow\",\n          \"passed\": false\n        },\n        {\n          \"name\": \"fan_off\",\n          \"input\": null,\n          \"input_kind\": \"flow\",\n          \"passed\": false\n        }\n      ]\n    },\n    \"fan_on\": {\n      \"name\": \"fan_on\",\n      \"steps\": [\n        {\n          \"name\": \"temp_high\",\n          \"input\": 31.0,\n          \"input_kind\": \"flow\",\n          \"passed\": true\n        },\n        {\n          \"name\": \"fan_on\",\n          \"input\": null,\n          \"input_kind\": \"flow\",\n          \"passed\": true\n        }\n      ]\n    }\n  }\n}\n```\n\nTechnically, [`Step`] repeats certain [`std::option::Option`] functionality,\nbut adds features to record and visualize the flow of data:\n\n* All lines and steps are named.\n\n* Step inputs are recorded.\n\nAdditionally, [`Step`] brings logical `OR` operation, which allows to combine\ntwo following closures in a single step. The closures must accept the same\ninput type and the input must implement `Clone` trait.\n\n```rust,ignore\nuse logicline::{action, Rack};\n\n// Here we use Env as a reference, so the `Clone` trait is not required for the\n// structure itself.\n#[derive(serde::Serialize)]\nstruct Env {\n    temperature: f32,\n    humidity: f32,\n}\n\nlet env = Env {\n    temperature: 25.0,\n    humidity: 40.0,\n};\n\nlet rack = Rack::new();\nlet mut processor = rack.processor();\n\nlet mut env_healthy = true;\n\nprocessor\n    .line(\"env_unhealthy\", \u0026env)\n    .then_any(\n        action!(\"temp_high\", |env: \u0026Env| (env.temperature \u003e 30.0).then_some(())),\n        action!(\"humidity_high\", |env: \u0026Env| (env.humidity \u003e 60.0).then_some(())),\n    )\n    .then(action!(\"set_unhealthy\", |()| {\n        env_healthy = false;\n        Some(())\n    }));\n\n```\n\nThe same example without the structure, where the second closure accepts an\nexternal variable which is recorded as an input:\n\n```rust\nuse logicline::{action, Rack};\n\nlet rack = Rack::new();\nlet mut processor = rack.processor();\n\nlet temperature = 25.0;\nlet humidity = 40.0;\n\nlet mut env_healthy = false;\n\nprocessor\n    // the temperature sensor value\n    .line(\"env_healthy\", temperature)\n    .then_any(\n        action!(\"temp_ok\", |t| (t \u003c 30.0).then_some(())),\n        // skip the chain input, the method `with_recorded_input` is used to\n        // record the real closure input\n        action!(\"humidity_ok\", |_| (humidity \u003c 60.0).then_some(()))\n            .with_recorded_input(\u0026humidity)\n    )\n    .then(action!(\"set_healthy\", |()| {\n        env_healthy = true;\n        Some(())\n    }));\n```\n\nAs Rust has got no exception, the way to break the chain is the same as for the\ntraditional combinators: return `None`.\n\n## Recording\n\nBy default `recording` feature is enabled. When disabled, no line state is\nrecorded, certain recording-specific methods are not available.\n\nThe state recording can be also enabled/disabled in runtime. By default, the\nruntime recording is disabled.\n\n## Ordering\n\nIn a classic logic rack, it is supposed that the order of the lines is\nunpredictable, as classic logic programming languages copy hardware relay-rack\nlogic.\n\nIn this library, the order of the lines is defined by the developer however it\nis strongly recommended to avoid creating conflicting lines to keep the overall\nstate clear and consistent.\n\nThe recorded lines are placed into a [`std::collections::BTreeMap`] and\nautomatically sorted by their names.\n\n## Performance\n\nWhen recording is disabled (either feature or the runtime state/processor\nflag), the logic lines bring almost no overhead in comparison to the\ntraditional combinators, such as similar methods of [`std::option::Option`] and\n[`std::result::Result`].\n\nWhen recording is enabled, it is recommended to clone rack instances before\nprocessing them in case if the instances are under Mutex or RwLock. This can be\nperformed either with [`Rack::clone`] with [`Rack::snapshot`] methods. The\nsecond variant returns a [`Snapshot`] instance which contains line states only.\n\n## Data visualization\n\nThe crate `exporter` feature provides a built-in exporter (HTTP server) for\n[`global`] process state.\n\n```rust,ignore\nlogicline::global::install_exporter().unwrap();\n```\n\nThe web server binds to the port `9001` and provides the global rack state\nsnapshots in JSON format at the `/state` endpoint. The server can be also\nconfigured to bind a specific address using [`global::install_exporter_on`]\nmethod.\n\nThe snapshots can be visualized using\n[`logicline-view`](https://github.com/roboplc/logicline/tree/main/logicline-view)\nTypeScript library which is a part of this project.\n\nIn case if a feature `exporter-ui` is enabled, the built-in web server also\nprovides a basic interface to visualize the global state snapshots. The\ninterface is available at the root (`http://host:9001/`) endpoint.\n\nFor custom programs, state snapshots can be serialized to any\n`serde`-compatible format and pushed/pulled in any required way.\n\nIn case of periodic processing, such as local/remote context/sensor analysis in\ntraditional [PLC](https://en.wikipedia.org/wiki/Programmable_logic_controller)\nlogic state is update on every iteration and the visualization always contains\nall the lines programmed.\n\nIn case of event-processing model, it is recommended to send dummy events at\nthe program start to fill the logic state with the initial chain values.\n\n## Data safety\n\nSome logic chains might contain sensitive data, which should be hidden either\nfrom everyone or accessible only for users with certain permissions.\n\nThe default exporter provides a built-in way to post-format data snapshots. Let\nus hide inputs for all steps with name starting with `voltage`:\n\n```rust,ignore\nstruct SensitiveDataFormatter {}\n\nimpl logicline::SnapshotFormatter for SensitiveDataFormatter {\n    fn format(\u0026self, mut snapshot: Snapshot) -\u003e Snapshot {\n        for line in snapshot.lines_mut().values_mut() {\n            for step in line.steps_mut() {\n                for i in step.info_mut() {\n                    // Step data is under Arc to let it be cloned without\n                    // overhead, so we need to replace the original step data\n                    // with the new one. In this example a helper method is used.\n                    if i.name().starts_with(\"voltage\") {\n                        *i = i.to_modified(\n                            None,\n                            Some(serde_json::Value::String(\"\u003chidden\u003e\".to_owned())),\n                            None,\n                            None,\n                        );\n                    }\n                }\n            }\n        }\n        snapshot\n    }\n}\n\n// then in the main function\nlogicline::global::set_snapshot_formatter(Box::new(SensitiveDataFormatter {}));\n```\n\nThe assigned formatter formats all snapshots requested by the exporter. To\napply a similar logic in own exporter (e.g. with authorization implemented),\nconsider using a similar way (with or without the provided trait).\n\n## Locking safety\n\nBy default, the crate (both the server and the client modules) uses\n[parking_lot](https://crates.io/crates/parking_lot) for locking. For real-time\napplications, the following features are available:\n\n* `locking-rt` - use [parking_lot_rt](https://crates.io/crates/parking_lot_rt)\n  crate which is a spin-free fork of parking_lot.\n\n* `locking-rt-safe` - use [rtsc](https://crates.io/crates/rtsc)\n  priority-inheritance locking, which is not affected by priority inversion\n  (Linux only).\n\nNote: to switch locking policy, disable the crate default features.\n\n## About\n\nLogic Line is a part of [RoboPLC](https://www.roboplc.com/) project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froboplc%2Flogicline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froboplc%2Flogicline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froboplc%2Flogicline/lists"}