diff --git a/Package.swift b/Package.swift index 8bfd863..9e466b4 100644 --- a/Package.swift +++ b/Package.swift @@ -19,12 +19,12 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0"), - .package(url: "https://github.com/mipalgu/VHDLMachines", from: "1.2.4"), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"), + .package(url: "https://github.com/mipalgu/VHDLMachines", from: "2.0.0"), .package(url: "https://github.com/mipalgu/VHDLParsing", from: "2.4.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), - .package(url: "https://github.com/mipalgu/VHDLKripkeStructureGenerator.git", from: "0.1.2"), - .package(url: "https://github.com/mipalgu/swift_helpers.git", from: "2.0.0") + .package(url: "https://github.com/mipalgu/VHDLKripkeStructureGenerator.git", from: "0.2.0"), + .package(url: "https://github.com/CPSLabGU/SwiftUtils.git", from: "0.1.0") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -41,7 +41,7 @@ let package = Package( .product(name: "VHDLMachines", package: "VHDLMachines"), .product(name: "VHDLParsing", package: "VHDLParsing"), .product(name: "VHDLKripkeStructureGenerator", package: "VHDLKripkeStructureGenerator"), - .product(name: "IO", package: "swift_helpers") + .product(name: "SwiftUtils", package: "SwiftUtils") ] ), .target( @@ -63,7 +63,7 @@ let package = Package( .product(name: "VHDLParsing", package: "VHDLParsing"), .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "VHDLKripkeStructureGenerator", package: "VHDLKripkeStructureGenerator"), - .product(name: "IO", package: "swift_helpers") + .product(name: "SwiftUtils", package: "SwiftUtils") ] ), .testTarget( diff --git a/Sources/MachineGenerator/Generate.swift b/Sources/MachineGenerator/Generate.swift index 0dd007b..dd5cdce 100644 --- a/Sources/MachineGenerator/Generate.swift +++ b/Sources/MachineGenerator/Generate.swift @@ -143,7 +143,7 @@ struct Generate: ParsableCommand { @inlinable func createMachine() throws { let model = try decoder.decode(MachineModel.self, from: model) - guard let machine = Machine(model: model, path: pathURL) else { + guard let machine = Machine(model: model) else { throw GenerationError.invalidGeneration(message: "Cannot create valid machine from model.") } let data = try encoder.encode(machine) diff --git a/Sources/MachineGenerator/VHDLGenerator.swift b/Sources/MachineGenerator/VHDLGenerator.swift index 1273c8a..2385b39 100644 --- a/Sources/MachineGenerator/VHDLGenerator.swift +++ b/Sources/MachineGenerator/VHDLGenerator.swift @@ -56,14 +56,11 @@ import ArgumentParser import Foundation +import SwiftUtils import VHDLKripkeStructureGenerator import VHDLMachines import VHDLParsing -#if os(Linux) -import IO -#endif - /// A sub-command that generates VHDL source files from LLFSM definitions. struct VHDLGenerator: ParsableCommand { @@ -87,9 +84,20 @@ struct VHDLGenerator: ParsableCommand { .appendingPathComponent("machine.json", isDirectory: false) let data = try Data(contentsOf: path) let machine = try JSONDecoder().decode(Machine.self, from: data) - guard let representation = MachineRepresentation(machine: machine) else { + let nameRaw = self.options.pathURL.lastPathComponent + guard + nameRaw.hasSuffix(".machine"), + nameRaw != ".machine", + let name = VariableName(rawValue: String(nameRaw.dropLast(8))) + else { + throw GenerationError.invalidFormat( + message: "The machine specified is invalid. " + + "Please make sure you specify a machine with the .machine extension and valid name." + ) + } + guard let representation = MachineRepresentation(machine: machine, name: name) else { throw GenerationError.invalidGeneration( - message: "Failed to generate VHDL for \(machine.name.rawValue)." + message: "Failed to generate VHDL for \(name.rawValue)." ) } let machinePath = URL(fileURLWithPath: options.path, isDirectory: true) @@ -98,7 +106,7 @@ struct VHDLGenerator: ParsableCommand { let file = VHDLFile(representation: representation) let vhdlFolder = buildFolder.appendingPathComponent("vhdl", isDirectory: true) let vhdlPath = vhdlFolder.appendingPathComponent( - "\(machine.name.rawValue).vhd", isDirectory: false + "\(representation.entity.name.rawValue).vhd", isDirectory: false ) try FileManager.default.createDirectory(at: vhdlFolder, withIntermediateDirectories: true) try (file.rawValue + "\n").write(to: vhdlPath, atomically: true, encoding: .utf8) @@ -106,7 +114,7 @@ struct VHDLGenerator: ParsableCommand { } guard let files = VHDLKripkeStructureGenerator().generateAll(representation: representation) else { throw GenerationError.invalidGeneration( - message: "Failed to generate Kripke Structure for \(machine.name.rawValue)." + message: "Failed to generate Kripke Structure for \(name.rawValue)." ) } try files.write(to: buildFolder, options: .atomic, originalContentsURL: nil) diff --git a/Sources/VHDLMachineTransformations/Machine+jsInit.swift b/Sources/VHDLMachineTransformations/Machine+jsInit.swift index 03377f8..54f5f56 100644 --- a/Sources/VHDLMachineTransformations/Machine+jsInit.swift +++ b/Sources/VHDLMachineTransformations/Machine+jsInit.swift @@ -70,7 +70,7 @@ extension Machine { /// - model: The model representing this machine. /// - path: The path where the machine is located. @inlinable - public init?(model: MachineModel, path: URL? = nil) { + public init?(model: MachineModel) { let actionNames = Set(["OnEntry", "OnExit", "Internal"]) .union(model.states.flatMap { $0.actions.map(\.name) }) let variableNames = actionNames.compactMap(VariableName.init(rawValue:)) @@ -94,11 +94,6 @@ extension Machine { guard machineSignalsRaw.count == machineSignals.count else { return nil } - let name = path?.deletingPathExtension().lastPathComponent ?? "Machine0" - let machinePath = path ?? URL(fileURLWithPath: "/tmp/Machine0.machine", isDirectory: true) - guard let machineName = VariableName(rawValue: name) else { - return nil - } let externalsRaw = getStatements(model.externalVariables) let externalSignals = externalsRaw.compactMap(PortSignal.init(rawValue:)) guard externalsRaw.count == externalSignals.count else { @@ -134,20 +129,17 @@ extension Machine { + actionVariableNames let signalNames: [VariableName] = externalSignals.map(\.name) + machineSignals.map(\.name) + clocks.map(\.name) - let allNames: [VariableName] = stateNames + signalNames + [machineName] + let allNames: [VariableName] = stateNames + signalNames // Check for duplicate names. guard Set(allNames).count == allNames.count else { return nil } self.init( actions: actionVariableNames, - name: machineName, - path: machinePath, includes: parsedIncludes, externalSignals: externalSignals, clocks: clocks, drivingClock: 0, - dependentMachines: [:], machineSignals: machineSignals, isParameterised: false, parameterSignals: [], diff --git a/Tests/MachineGeneratorTests/Machine0.swift b/Tests/MachineGeneratorTests/Machine0.swift index 27a089a..803c7c2 100644 --- a/Tests/MachineGeneratorTests/Machine0.swift +++ b/Tests/MachineGeneratorTests/Machine0.swift @@ -64,16 +64,12 @@ import VHDLParsing /// Create `Machine0`. extension Machine { - // swiftlint:disable function_body_length + // swiftlint:disable closure_body_length /// Create Machine0 in the given folder. - init?(machine0LocatedInFolder path: URL) { - let machine0Path = path.appendingPathComponent("Machine0.machine", isDirectory: true) - guard var machine = Machine.initial(path: machine0Path) else { - return nil - } + static let machine0 = { + var machine = Machine.initialSuspensible machine.actions = [.internal, .onEntry, .onExit] - machine.name = .machine0 machine.externalSignals = [ PortSignal(type: .stdLogic, name: .x, mode: .input), PortSignal(type: .stdLogic, name: .y, mode: .output) @@ -132,11 +128,10 @@ extension Machine { .all ])!) ] - machine.path = machine0Path - self = machine - } + return machine + }() - // swiftlint:enable function_body_length + // swiftlint:enable closure_body_length } diff --git a/Tests/MachineGeneratorTests/Machine0Tests.swift b/Tests/MachineGeneratorTests/Machine0Tests.swift index e9b89e3..9983fcd 100644 --- a/Tests/MachineGeneratorTests/Machine0Tests.swift +++ b/Tests/MachineGeneratorTests/Machine0Tests.swift @@ -74,27 +74,12 @@ final class Machine0Tests: XCTestCase { /// Test generation is correct. func testGeneration() { - let path = URL(fileURLWithPath: pathRaw, isDirectory: true) - guard - let machine = Machine(machine0LocatedInFolder: path), - let result = Machine( - model: .machine0, path: path.appendingPathComponent("Machine0.machine", isDirectory: true) - ) - else { - XCTFail("Failed to create machine and model.") - return - } - XCTAssertEqual(result, machine) + XCTAssertEqual(Machine(model: .machine0), Machine.machine0) } /// Test the model generation is correct. func testModelGeneration() { - guard - let machine = Machine(machine0LocatedInFolder: URL(fileURLWithPath: pathRaw, isDirectory: true)) - else { - XCTFail("Failed to create machine.") - return - } + let machine = Machine.machine0 let model = MachineModel.machine0 let result = MachineModel( machine: machine, diff --git a/Tests/MachineGeneratorTests/MachineTester.swift b/Tests/MachineGeneratorTests/MachineTester.swift index e83b91a..5e84efc 100644 --- a/Tests/MachineGeneratorTests/MachineTester.swift +++ b/Tests/MachineGeneratorTests/MachineTester.swift @@ -115,9 +115,9 @@ class MachineTester: XCTestCase { override func setUp() { let createDir: ()? = try? manager .createDirectory(at: machine0Path, withIntermediateDirectories: true) + let machine = Machine.machine0 guard createDir != nil, - let machine = Machine(machine0LocatedInFolder: machinesFolder), let data = try? encoder.encode(machine), let modelData = try? encoder.encode(MachineModel.machine0) else { diff --git a/Tests/MachineGeneratorTests/VHDLGeneratorTests.swift b/Tests/MachineGeneratorTests/VHDLGeneratorTests.swift index 455b5a3..1e82fe2 100644 --- a/Tests/MachineGeneratorTests/VHDLGeneratorTests.swift +++ b/Tests/MachineGeneratorTests/VHDLGeneratorTests.swift @@ -56,15 +56,12 @@ import Foundation @testable import MachineGenerator +import SwiftUtils import VHDLKripkeStructureGenerator import VHDLMachines import VHDLParsing import XCTest -#if os(Linux) -import IO -#endif - /// Test class for ``VHDLGenerator``. final class VHDLGeneratorTests: MachineTester { @@ -73,13 +70,10 @@ final class VHDLGeneratorTests: MachineTester { /// Test the main method generates the `VHDL` file correctly. func testRunGeneratesVHDL() throws { - guard let machine = Machine(machine0LocatedInFolder: self.machine0Path) else { - XCTFail("Failed to create machine.") - return - } + let machine = Machine.machine0 VHDLGenerator.main([pathRaw]) let vhdlPath = machine0Path.appendingPathComponent("build/vhdl/Machine0.vhd", isDirectory: false) - guard let representation = MachineRepresentation(machine: machine) else { + guard let representation = MachineRepresentation(machine: machine, name: .machine0) else { XCTFail("Failed to create VHDL for machine.") return } @@ -89,10 +83,8 @@ final class VHDLGeneratorTests: MachineTester { /// Test that the main method throws the correct error for an invalid machine. func testRunThrowsErrorForInvalidMachine() throws { - guard - var machine = Machine(machine0LocatedInFolder: self.machine0Path), - let onEntry = VariableName(rawValue: "OnEntry") - else { + var machine = Machine.machine0 + guard let onEntry = VariableName(rawValue: "OnEntry") else { XCTFail("Failed to create machine.") return } @@ -109,15 +101,77 @@ final class VHDLGeneratorTests: MachineTester { } } + /// Test the correct error is thrown for paths without the correct extension. + func testPathWithoutExtension() throws { + let invalidPath = self.machinesFolder.appendingPathComponent("invalid", isDirectory: true) + try self.manager.copyItem(at: self.machine0Path, to: invalidPath) + defer { _ = try? self.manager.removeItem(at: invalidPath) } + var command = try VHDLGenerator.parse([invalidPath.path]) + XCTAssertThrowsError(try command.run()) { + guard let error = $0 as? GenerationError else { + XCTFail("Thrown incorrect error.") + return + } + XCTAssertEqual( + error, + .invalidFormat( + message: "The machine specified is invalid. " + + "Please make sure you specify a machine with the .machine extension and valid name." + ) + ) + } + } + + /// Test correct errors are thrown for paths with only the machine extension. + func testEmptyMachinePath() throws { + let path = self.machinesFolder.appendingPathComponent(".machine", isDirectory: true) + try self.manager.copyItem(at: self.machine0Path, to: path) + defer { _ = try? self.manager.removeItem(at: path) } + var command = try VHDLGenerator.parse([path.path]) + XCTAssertThrowsError(try command.run()) { + print($0.localizedDescription) + fflush(stdout) + guard let error = $0 as? GenerationError else { + XCTFail("Thrown incorrect error.") + return + } + XCTAssertEqual( + error, + .invalidFormat( + message: "The machine specified is invalid. " + + "Please make sure you specify a machine with the .machine extension and valid name." + ) + ) + } + } + + /// Test that VHDL keyword in machine name throw an error. + func testVHDLNameInPath() throws { + let path = self.machinesFolder.appendingPathComponent("signal.machine", isDirectory: true) + try self.manager.copyItem(at: self.machine0Path, to: path) + defer { _ = try? self.manager.removeItem(at: path) } + var command = try VHDLGenerator.parse([path.path]) + XCTAssertThrowsError(try command.run()) { + guard let error = $0 as? GenerationError else { + XCTFail("Thrown incorrect error.") + return + } + XCTAssertEqual( + error, + .invalidFormat( + message: "The machine specified is invalid. " + + "Please make sure you specify a machine with the .machine extension and valid name." + ) + ) + } + } + /// Test the VHDL generator creates the correct Kripke structure. func testRunGeneratesKripkeStructure() throws { - guard let machine = Machine(machine0LocatedInFolder: self.machine0Path) else { - XCTFail("Failed to create machine.") - return - } + let machine = Machine.machine0 VHDLGenerator.main(["--include-kripke-structure", pathRaw]) guard - let representation = MachineRepresentation(machine: machine), + let representation = MachineRepresentation(machine: machine, name: .machine0), let files = generator.generateAll(representation: representation) else { XCTFail("Failed to create VHDL for machine.") diff --git a/Tests/VHDLMachineTransformationsTests/MachineModelTests.swift b/Tests/VHDLMachineTransformationsTests/MachineModelTests.swift index 14c4d68..c0fe3ad 100644 --- a/Tests/VHDLMachineTransformationsTests/MachineModelTests.swift +++ b/Tests/VHDLMachineTransformationsTests/MachineModelTests.swift @@ -122,8 +122,6 @@ final class MachineModelTests: XCTestCase { /// The expected machine. lazy var machine = Machine( actions: [VariableName(rawValue: "OnEntry")!, VariableName(rawValue: "OnExit")!], - name: VariableName(rawValue: "Machine0")!, - path: URL(fileURLWithPath: "/tmp/Machine0.machine", isDirectory: true), includes: [ .library(value: VariableName(rawValue: "IEEE")!), .include( @@ -135,7 +133,6 @@ final class MachineModelTests: XCTestCase { externalSignals: [PortSignal(type: .stdLogic, name: VariableName(rawValue: "y")!, mode: .output)], clocks: clocks, drivingClock: 0, - dependentMachines: [:], machineSignals: [LocalSignal(type: .stdLogic, name: VariableName(rawValue: "x")!)], isParameterised: false, parameterSignals: [], @@ -190,8 +187,6 @@ final class MachineModelTests: XCTestCase { override func setUp() { machine = Machine( actions: [VariableName(rawValue: "OnEntry")!, VariableName(rawValue: "OnExit")!], - name: VariableName(rawValue: "Machine0")!, - path: URL(fileURLWithPath: "/tmp/Machine0.machine", isDirectory: true), includes: [ .library(value: VariableName(rawValue: "IEEE")!), .include( @@ -203,7 +198,6 @@ final class MachineModelTests: XCTestCase { externalSignals: [PortSignal(type: .stdLogic, name: VariableName(rawValue: "y")!, mode: .output)], clocks: clocks, drivingClock: 0, - dependentMachines: [:], machineSignals: [LocalSignal(type: .stdLogic, name: VariableName(rawValue: "x")!)], isParameterised: false, parameterSignals: [], diff --git a/Tests/VHDLMachineTransformationsTests/MachineTests.swift b/Tests/VHDLMachineTransformationsTests/MachineTests.swift index 5844d8f..ab8b2b1 100644 --- a/Tests/VHDLMachineTransformationsTests/MachineTests.swift +++ b/Tests/VHDLMachineTransformationsTests/MachineTests.swift @@ -126,8 +126,6 @@ final class MachineTests: XCTestCase { VariableName(rawValue: "OnEntry")!, VariableName(rawValue: "OnExit")! ], - name: VariableName(rawValue: "Machine0")!, - path: URL(fileURLWithPath: "/tmp/Machine0.machine", isDirectory: true), includes: [ .library(value: VariableName(rawValue: "IEEE")!), .include(statement: UseStatement(rawValue: "use std_logic_1164.all;")!) @@ -135,7 +133,6 @@ final class MachineTests: XCTestCase { externalSignals: [PortSignal(type: .stdLogic, name: VariableName(rawValue: "y")!, mode: .output)], clocks: [Clock(name: VariableName(rawValue: "clk")!, frequency: 100, unit: .MHz)], drivingClock: 0, - dependentMachines: [:], machineSignals: [LocalSignal(type: .stdLogic, name: VariableName(rawValue: "x")!)], isParameterised: false, parameterSignals: [], @@ -193,8 +190,6 @@ final class MachineTests: XCTestCase { VariableName(rawValue: "OnEntry")!, VariableName(rawValue: "OnExit")! ], - name: VariableName(rawValue: "Machine0")!, - path: URL(fileURLWithPath: "/tmp/Machine0.machine", isDirectory: true), includes: [ .library(value: VariableName(rawValue: "IEEE")!), .include(statement: UseStatement(rawValue: "use std_logic_1164.all;")!) @@ -202,7 +197,6 @@ final class MachineTests: XCTestCase { externalSignals: [PortSignal(type: .stdLogic, name: VariableName(rawValue: "y")!, mode: .output)], clocks: [Clock(name: VariableName(rawValue: "clk")!, frequency: 100, unit: .MHz)], drivingClock: 0, - dependentMachines: [:], machineSignals: [LocalSignal(type: .stdLogic, name: VariableName(rawValue: "x")!)], isParameterised: false, parameterSignals: [], @@ -248,11 +242,6 @@ final class MachineTests: XCTestCase { /// Test the model is converted correctly. func testConversionInit() { XCTAssertEqual(Machine(model: model), expected) - let path = URL(fileURLWithPath: "/tmp/subdir/NewMachine.machine", isDirectory: true) - // swiftlint:disable:next force_unwrapping - expected.name = VariableName(rawValue: "NewMachine")! - expected.path = path - XCTAssertEqual(Machine(model: model, path: path), expected) } /// Test for incorrect actions. @@ -285,13 +274,6 @@ final class MachineTests: XCTestCase { XCTAssertEqual(Machine(model: model), expected) } - /// Test invalid path. - func testInvalidPath() { - XCTAssertNil( - Machine(model: model, path: URL(fileURLWithPath: "/tmp/invalid path", isDirectory: true)) - ) - } - /// Test invalid external variables are detected. func testInvalidExternals() { model.externalVariables += "\ninvalid code;" @@ -361,8 +343,6 @@ final class MachineTests: XCTestCase { /// Test for unique names. func testUniqueNames() { - model.clocks[0].name = "Machine0" - XCTAssertNil(Machine(model: model)) model.clocks[0].name = "OnEntry" XCTAssertNil(Machine(model: model)) model.clocks[0].name = "x"