Skip to content

Commit

Permalink
engine(lua): Restructure Lua integration
Browse files Browse the repository at this point in the history
Up until now, we had one big cloe table that we patched
from several different places. This is not very maintainable
as the library grows and so another approach was needed.
Hopefully, this is the last large set of breaking changes.

- Tests can make use of expect_OP and assert_OP methods
  to provide better report generation.

  For example: z:expect_gt(left, right) will actually
  provide useful messages in the report, especially when
  something isn't as expected.

- Tests can use `expect_OP` methods to make assertions
  without terminating the test when they fail.

- At the end of cloe.schedule_test, it will automatically terminate
  the simulation with the appropriate result, depending on whether
  any expect / assert failed or not.

  If multiple tests were scheduled, the last one terminates the
  simulation.

  The results are written to the report.

  To disable this, set terminate = false in the schedule_test:

      cloe.schedule_test {
        id = "test",
        on = "start",
        run = function() end,
        terminate = false,
      }

BREAKING CHANGES:
- The Lua cloe library has been split into the pure Lua library,
  cloe, and the pure C++ library, cloe-engine.

- The engine now exports Lua state and functions to the cloe-engine Lua
  library, which is not loaded into the global namespace anymore.

  To access them, use:
  - `require("cloe-engine")`
  - `require("cloe-engine.fs")`
  - `require("cloe-engine.types")`

  These should not be used directly by users, instead functions
  and mechanisms in cloe should be used.

- The `cloe` global is no longer automatically set in the global
  namespace anymore:

  - This allows configuration of libraries before they are
    required by cloe.
  - The cloe library pulls in most of the cloe-engine Lua library.

- The files in the `cloe` library have reorganized.
  Require statements may need to be rewritten, if anything other
  than cloe was required.

- Signals table and requires is not directly accessible, instead
  you should use the functions provided:

      cloe.require_signals(table) => table
      cloe.require_signals_enum(table) => table
      cloe.signals() => table
      cloe.signal(string) => any

  For example, before and after:

      cloe.require_signals = {...}  ->  cloe.require_signals({ ... })
      cloe.signals["string"]        ->  cloe.signal("string")

  or alternatively, thanks to Lua syntax goodness:

      cloe.require_signals = {...}  ->  cloe.require_signals { ... }
      cloe.signals["string"]        ->  cloe.signal "string"

- The TestFixture class replaces the table previously used for
  the same purpose.

  - The methods need to be called with a colon, since this automatically
    passes the self to the function (which is needed):

        z.printf()         ->  z:printf()
        z.wait_duration()  ->  z:wait_duration()
        ...

  - `z:expect()` can be used to assert without terminating the
    simulation.

- Typecheck is used to replace `cloe.validate` for most function
  argument validation, as this provides much better error messages.

  If you have used cloe.validate, you need to use the new format
  or use `require("cloe.luax").validate` instead.

- Functions previously exported directly to the cloe table that
  were utility functions are now delegated to a separate namespace,
  in order to keep everything in cloe related.

  Thus, the `cloe.utils` has been renamed `cloe.luax`, since they
  are Lua extensions. They may be replaced in the future with a
  larger set of libraries, but for now they will stay.

- The table `cloe.report` has been moved to `cloe.system` and is
  no longer automatically included in cloe. You need to require it:

      local sys = require("cloe.system")

- The function `cloe.system` has been moved to the `system` table
  and renamed to `exec`. See previous point.
  • Loading branch information
cassava committed Oct 19, 2023
1 parent be19007 commit 35bf417
Show file tree
Hide file tree
Showing 37 changed files with 2,031 additions and 1,205 deletions.
8 changes: 8 additions & 0 deletions .luarc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"workspace.library": ["engine/lua"],
"runtime.version": "Lua 5.4",
"completion.displayContext": 1,
"diagnostics.globals": ["cloe"],
"hint.enable": true
}
1 change: 0 additions & 1 deletion engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ add_library(cloe-lualib
src/lua_api.cpp
src/lua_setup.hpp
src/lua_setup.cpp
src/lua_setup_builtin.cpp
src/lua_setup_duration.cpp
src/lua_setup_fs.cpp
src/lua_setup_stack.cpp
Expand Down
73 changes: 53 additions & 20 deletions engine/lua/cloe/fs.lua → engine/lua/cloe-engine/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,16 @@
--

---
--- @meta
--- @meta cloe-engine.fs
---
--- This file contains the type annotations of the `cloe.fs` table,
--- defined in `src/lua_setup_fs.cpp` as of this writing.
--- This file contains the type annotations of the `cloe-engine.fs` module,
--- which are exported by the cloe-engine executable.
---

local cloe = cloe or {}
if cloe.fs then
return cloe.fs
end

local fs = {}

local unavailable = require("cloe-engine").unavailable

--- Return the basename of the filepath.
---
--- Examples:
Expand All @@ -39,7 +36,10 @@ local fs = {}
---
--- @param path string filepath
--- @return string # basename of file without parent
function fs.basename(path) end
--- @nodiscard
function fs.basename(path)
return unavailable("fs.path", path)
end

--- Return the parent of the filepath.
---
Expand All @@ -52,7 +52,10 @@ function fs.basename(path) end
---
--- @param path string filepath
--- @return string # parent of file without basename
function fs.dirname(path) end
--- @nodiscard
function fs.dirname(path)
return unavailable("fs.dirname", path)
end

--- Return the normalized filepath.
---
Expand All @@ -63,7 +66,10 @@ function fs.dirname(path) end
---
--- @param path string filepath
--- @return string # normalized file
function fs.normalize(path) end
--- @nodiscard
function fs.normalize(path)
return unavailable("fs.normalize", path)
end

--- Return the true filepath, resolving symlinks and normalizing.
--- If the file does not exist, an empty string is returned.
Expand All @@ -75,26 +81,38 @@ function fs.normalize(path) end
---
--- @param path string filepath
--- @return string # real path of file
function fs.realpath(path) end
--- @nodiscard
function fs.realpath(path)
return unavailable("fs.realpath", path)
end

--- Return the left and right arguments joined together.
---
--- @param left string filepath
--- @param right string filepath
--- @return string # filepaths joined as "left/right"
function fs.join(left, right) end
--- @nodiscard
function fs.join(left, right)
return unavailable("fs.join", left, right)
end

--- Return whether path is an absolute path.
---
--- @param path string filepath to check
--- @return boolean # true if path is absolute
function fs.is_absolute(path) end
--- @nodiscard
function fs.is_absolute(path)
return unavailable("fs.is_absolute", path)
end

--- Return whether path is a relative path.
---
--- @param path string filepath to check
--- @return boolean # true if path is relative
function fs.is_relative(path) end
--- @nodiscard
function fs.is_relative(path)
return unavailable("fs.is_relative", path)
end

--- Return whether path refers to an existing directory.
---
Expand All @@ -103,7 +121,10 @@ function fs.is_relative(path) end
---
--- @param file string filepath to check
--- @return boolean # true if path exists and is a directory
function fs.is_dir(file) end
--- @nodiscard
function fs.is_dir(file)
return unavailable("fs.is_dir", file)
end

--- Return whether path refers to an existing normal file.
---
Expand All @@ -114,13 +135,19 @@ function fs.is_dir(file) end
---
--- @param file string filepath to check
--- @return boolean # true if path exists and is a normal file
function fs.is_file(file) end
--- @nodiscard
function fs.is_file(file)
return unavailable("fs.is_file", file)
end

--- Return whether path refers to an existing symlink.
---
--- @param file string filepath to check
--- @return boolean # true if path exists and is a symlink
function fs.is_symlink(file) end
--- @nodiscard
function fs.is_symlink(file)
return unavailable("fs.is_symlink", file)
end

--- Return whether path refers to something that exists,
--- but is not a file, directory, or symlink.
Expand All @@ -129,13 +156,19 @@ function fs.is_symlink(file) end
---
--- @param file string filepath to check
--- @return boolean # true if path exists and is not a normal file, symlink, or directory
function fs.is_other(file) end
--- @nodiscard
function fs.is_other(file)
return unavailable("fs.is_other", file)
end

--- Return whether path refers to something that exists,
--- regardless what it is.
---
--- @param file string filepath to check
--- @return boolean # true if path exists
function fs.exists(file) end
--- @nodiscard
function fs.exists(file)
return unavailable("fs.is_other", file)
end

return fs
186 changes: 186 additions & 0 deletions engine/lua/cloe-engine/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
--
-- Copyright 2023 Robert Bosch GmbH
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- SPDX-License-Identifier: Apache-2.0
--

---
--- @meta cloe-engine
---
--- This file contains the type annotations of the `cloe-engine` module,
--- which are exported by the cloe-engine executable.
---
--- These methods should only be used by the cloe library.
---

local engine = {
-- Contains data that will be processed at simulation start,
-- but will not be considered afterward.
initial_input = {
triggers = {},
triggers_processed = 0,
signal_aliases = {},
signal_requires = {},
},

-- Contains engine state for a simulation.
state = {
features = {},
report = {},
scheduler = nil,
stack = nil,
current_script_file = nil,
current_script_dir = nil,
scripts_loaded = {},
},

plugins = {},

signals = {},
}

require("cloe-engine.types")

--- Fail with an error message that cloe-engine functionality not available.
---
--- @param fname string
--- @param ... any Consumed but not used
--- @return any
local function unavailable(fname, ...)
local inspect = require("inspect").inspect
local buf = "cloe-engine." .. fname .. "("
for i, v in ipairs(...) do
if i ~= 1 then
buf = buf .. ", "
end
buf = buf .. inspect(v)
end
buf = buf .. ")"
error(string.format("error: %s: implementation unavailable outside cloe-engine", buf))
end

--- Return two-character string representation of log-level.
---
--- @param level string
--- @return string
--- @nodiscard
local function log_level_format(level)
if level == "info" then
return "II"
elseif level == "debug" then
return "DD"
elseif level == "warn" then
return "WW"
elseif level == "error" then
return "EE"
elseif level == "critical" then
return "CC"
elseif level == "trace" then
return "TT"
else
return "??"
end
end

--- Return whether the engine is available.
---
--- This is not the case when a Lua script is being run with
--- another interpreter, a REPL, or a language server.
---
--- @return boolean
function engine.is_available()
return false
end

--- Return path to Lua file that the engine is currently merging,
--- or nil if no file is being loaded.
---
--- @return string|nil
function engine.get_script_file()
return engine.state.current_script_file
end

--- Return path to directory containing the Lua file that the engine is
--- currently merging, or nil if no file is being loaded.
---
--- @return string|nil
function engine.get_script_dir()
return engine.state.current_script_dir
end

--- Return the global Stack instance.
---
--- @return Stack
function engine.get_stack()
return unavailable("get_stack")
end

--- Return the simulation scheduler (aka. Coordinator) global instance.
---
--- @return Coordinator
function engine.get_scheduler()
return unavailable("get_scheduler")
end

--- Return the simulation report.
---
--- @return table
function engine.get_report()
return engine.state.report
end

--- Return a table of available features.
---
--- @return table
function engine.get_features()
return engine.state.features
end

--- Log a message.
---
--- @param level string
--- @param prefix string
--- @param message string
--- @return nil
function engine.log(level, prefix, message)
print(string.format("%s %s [%s] %s", log_level_format(level), os.date("%T"), prefix, message))
end

--- @class CommandSpecA
--- @field path string name or path of executable
--- @field args table list of arguments
--- @field mode? string execution mode (one of "sync", "async", "detach")
--- @field log_output? string output verbosity ("never", "on_error", "always")
--- @field ignore_failure? boolean whether to ignore failure

--- @class CommandSpecB
--- @field command string command or script to run with default shell
--- @field mode? string execution mode (one of "sync", "async", "detach")
--- @field log_output? string output verbosity ("never", "on_error", "always")
--- @field ignore_failure? boolean whether to ignore failure

--- @alias CommandSpecC string command or script to run with default shell

--- @alias CommandSpec (CommandSpecA | CommandSpecB | CommandSpecC)

--- Run a system command with the cloe executer.
---
--- @param spec CommandSpec
--- @return string,number
function engine.exec(spec)
return unavailable("exec", spec), 1
end

return engine
Loading

0 comments on commit 35bf417

Please sign in to comment.