Skip to content

Commit c7498db

Browse files
committed
Provide new experimental library (lispkit system os) for invoking external binaries.
1 parent 483fca7 commit c7498db

9 files changed

+192
-8
lines changed

LispKit.xcodeproj/project.pbxproj

+6-2
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@
356356
CCC62D921CA9ED6A00E2C43F /* TrackedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC62D911CA9ED6A00E2C43F /* TrackedObject.swift */; };
357357
CCC9516F2426B3E900FC915F /* 162.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCC9516E2426B1E100FC915F /* 162.sld */; };
358358
CCC951712426B6B000FC915F /* SRFI-128.scm in Copy tests */ = {isa = PBXBuildFile; fileRef = CCC951702426B54600FC915F /* SRFI-128.scm */; };
359+
CCCACB5C248D753800F012AC /* SystemOSLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCCACB5B248D753800F012AC /* SystemOSLibrary.swift */; };
359360
CCCEB3FF1DF2DAA9009BF66B /* FileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCCEB3FE1DF2DAA9009BF66B /* FileHandler.swift */; };
360361
CCD6C3E91F35F9F10002F7D4 /* enum.sld in Copy pre-installed LispKit libraries */ = {isa = PBXBuildFile; fileRef = CCD6C3E81F35F95D0002F7D4 /* enum.sld */; };
361362
CCD81AD21CE75823009EB087 /* Checkpointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD81AD11CE75823009EB087 /* Checkpointer.swift */; };
@@ -1282,6 +1283,7 @@
12821283
CCC937491C4F031700A5608F /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
12831284
CCC9516E2426B1E100FC915F /* 162.sld */ = {isa = PBXFileReference; lastKnownFileType = text; path = 162.sld; sourceTree = "<group>"; };
12841285
CCC951702426B54600FC915F /* SRFI-128.scm */ = {isa = PBXFileReference; lastKnownFileType = text; path = "SRFI-128.scm"; sourceTree = "<group>"; };
1286+
CCCACB5B248D753800F012AC /* SystemOSLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemOSLibrary.swift; sourceTree = "<group>"; };
12851287
CCCAF5AE23B7A71400FE2C60 /* HtmlColors.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = HtmlColors.plist; sourceTree = "<group>"; };
12861288
CCCAF92D1F941A43006692E9 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; path = Package.swift; sourceTree = "<group>"; tabWidth = 2; };
12871289
CCCEB3FE1DF2DAA9009BF66B /* FileHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHandler.swift; sourceTree = "<group>"; };
@@ -2032,6 +2034,7 @@
20322034
CC6A3B5F1C52EBD700E962E2 /* StringLibrary.swift */,
20332035
CC7A125C21EA7C5E00ABEA17 /* RegexpLibrary.swift */,
20342036
CC4E2D361D09691000D77159 /* PortLibrary.swift */,
2037+
CCCACB5B248D753800F012AC /* SystemOSLibrary.swift */,
20352038
CC4575C520F00C4400116F0F /* DrawingLibrary.swift */,
20362039
CCA6C3D023610674007D2AF0 /* MarkdownLibrary.swift */,
20372040
CC08964D244090D1004503B1 /* SQLiteLibrary.swift */,
@@ -2378,6 +2381,7 @@
23782381
CC6A3B5C1C52E71F00E962E2 /* ListLibrary.swift in Sources */,
23792382
CC5696791CBD48E700F6803E /* Global.swift in Sources */,
23802383
CC35FD0F1C711FEA00C8B992 /* BindingGroup.swift in Sources */,
2384+
CCCACB5C248D753800F012AC /* SystemOSLibrary.swift in Sources */,
23812385
CCE2C47A1D9203DE0047E229 /* Environment.swift in Sources */,
23822386
CC76312120E9518F005F27CE /* Drawing.swift in Sources */,
23832387
CC8EDE6E1D07263D004E0636 /* BinaryInput.swift in Sources */,
@@ -2885,8 +2889,8 @@
28852889
isa = XCRemoteSwiftPackageReference;
28862890
repositoryURL = "https://github.com/objecthub/swift-sqliteexpress.git";
28872891
requirement = {
2888-
branch = master;
2889-
kind = branch;
2892+
kind = upToNextMajorVersion;
2893+
minimumVersion = 1.0.2;
28902894
};
28912895
};
28922896
CC5E53012355AAD400C72CE2 /* XCRemoteSwiftPackageReference "swift-numberkit" */ = {

Package.resolved

+11-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,17 @@
2424
"repositoryURL": "https://github.com/objecthub/swift-numberkit.git",
2525
"state": {
2626
"branch": null,
27-
"revision": "5f9c41eb20777801a0d7ecb5bafe83e5f490a42a",
28-
"version": "2.3.4"
27+
"revision": "a29903f82329df499e0a422f7c4824f9093e5df8",
28+
"version": "2.3.7"
29+
}
30+
},
31+
{
32+
"package": "SQLiteExpress",
33+
"repositoryURL": "https://github.com/objecthub/swift-sqliteexpress.git",
34+
"state": {
35+
"branch": null,
36+
"revision": "ca29f8d89e37f9288721addc8272d063b257aa7a",
37+
"version": "1.0.2"
2938
}
3039
}
3140
]

Package.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ let package = Package(
5151
.package(url: "https://github.com/objecthub/swift-markdownkit.git",
5252
.upToNextMajor(from: "0.2.2")),
5353
.package(url: "https://github.com/objecthub/swift-commandlinekit.git",
54-
.upToNextMajor(from: "0.3.2"))
54+
.upToNextMajor(from: "0.3.2")),
55+
.package(url: "https://github.com/objecthub/swift-sqliteexpress.git",
56+
.upToNextMajor(from: "1.0.2"))
5557
],
5658
targets: [
5759
.target(name: "LispKit",
58-
dependencies: ["NumberKit", "MarkdownKit"]),
60+
dependencies: ["NumberKit", "MarkdownKit", "SQLiteExpress"]),
5961
.target(name: "LispKitTools",
6062
dependencies: ["LispKit", "CommandLineKit"]),
6163
.target(name: "LispKitRepl",

Sources/LispKit/Primitives/DrawingLibrary.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import MarkdownKit
2828
public final class DrawingLibrary: NativeLibrary {
2929

3030
/// Imported native library
31-
private var systemLibrary: SystemLibrary!
31+
private unowned var systemLibrary: SystemLibrary!
3232

3333
/// Exported parameter objects
3434
public let drawingParam: Procedure

Sources/LispKit/Primitives/LibraryRegistry.swift

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public struct LibraryRegistry {
4646
RegexpLibrary.self,
4747
PortLibrary.self,
4848
BaseLibrary.self,
49+
SystemOSLibrary.self,
4950
DrawingLibrary.self,
5051
InternalLibrary.self,
5152
MarkdownLibrary.self,

Sources/LispKit/Primitives/PortLibrary.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import Foundation
2626
public final class PortLibrary: NativeLibrary {
2727

2828
/// Imported native library
29-
private var systemLibrary: SystemLibrary!
29+
private unowned var systemLibrary: SystemLibrary!
3030

3131
/// Exported parameter objects
3232
public let outputPortParam: Procedure
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//
2+
// SystemOSLibrary.swift
3+
// LispKit
4+
//
5+
// Created by Matthias Zenger on 07/06/2020.
6+
// Copyright © 2020 ObjectHub. All rights reserved.
7+
//
8+
// Licensed under the Apache License, Version 2.0 (the "License");
9+
// you may not use this file except in compliance with the License.
10+
// You may obtain a copy of the License at
11+
//
12+
// http://www.apache.org/licenses/LICENSE-2.0
13+
//
14+
// Unless required by applicable law or agreed to in writing, software
15+
// distributed under the License is distributed on an "AS IS" BASIS,
16+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
// See the License for the specific language governing permissions and
18+
// limitations under the License.
19+
//
20+
21+
import Foundation
22+
23+
///
24+
/// System OS library
25+
///
26+
public final class SystemOSLibrary: NativeLibrary {
27+
28+
/// Imported native library
29+
private unowned var systemLibrary: SystemLibrary!
30+
private unowned var portLibrary: PortLibrary!
31+
32+
/// Initialize port library, in particular its parameter objects.
33+
public required init(in context: Context) throws {
34+
try super.init(in: context)
35+
}
36+
37+
/// Name of the library.
38+
public override class var name: [String] {
39+
return ["lispkit", "system", "os"]
40+
}
41+
42+
/// Dependencies of the library.
43+
public override func dependencies() {
44+
self.`import`(from: ["lispkit", "system"], "current-directory")
45+
self.`import`(from: ["lispkit", "port"], "current-output-port")
46+
}
47+
48+
/// Declarations of the library.
49+
public override func declarations() {
50+
self.define(Procedure("system-call", self.systemCall))
51+
}
52+
53+
public override func initializations() {
54+
self.systemLibrary = self.nativeLibrary(SystemLibrary.self)
55+
self.portLibrary = self.nativeLibrary(PortLibrary.self)
56+
}
57+
58+
private func systemCall(expr: Expr,
59+
arguments: Expr,
60+
args: Arguments) throws -> Expr {
61+
guard let (e, op, ipt) = args.optional(.false,
62+
.port(portLibrary.outputPort ?? self.context.outputPort),
63+
.false) else {
64+
throw RuntimeError.argumentCount(of: "system-call",
65+
min: 2,
66+
max: 5,
67+
args: .pair(expr, .pair(arguments, .makeList(args))))
68+
}
69+
return try self.systemCall(binary: expr,
70+
arguments: arguments,
71+
environment: e,
72+
outport: op,
73+
input: ipt)
74+
}
75+
76+
private func systemCall(binary expr: Expr,
77+
arguments: Expr,
78+
environment e: Expr,
79+
outport op: Expr,
80+
input ipt: Expr) throws -> Expr {
81+
let proc = Process()
82+
proc.currentDirectoryURL = URL(fileURLWithPath: self.systemLibrary.currentDirectoryPath,
83+
isDirectory: true)
84+
proc.executableURL =
85+
URL(fileURLWithPath: try expr.asPath(),
86+
relativeTo: URL(fileURLWithPath: self.systemLibrary.currentDirectoryPath,
87+
isDirectory: true))
88+
var args: [String] = []
89+
var lst = arguments
90+
while case .pair(let car, let cdr) = lst {
91+
args.append(car.unescapedDescription)
92+
lst = cdr
93+
}
94+
proc.arguments = args
95+
if e.isTrue {
96+
var env: [String : String] = [:]
97+
lst = e
98+
while case .pair(let car, let cdr) = lst {
99+
guard case .pair(let name, let value) = car else {
100+
throw RuntimeError.type(car, expected: [.pairType])
101+
}
102+
env[try name.asString()] = value.unescapedDescription
103+
lst = cdr
104+
}
105+
proc.environment = env
106+
}
107+
let condition = NSCondition()
108+
proc.terminationHandler = { _ in
109+
condition.lock()
110+
condition.signal()
111+
condition.unlock()
112+
}
113+
let outputPipe = Pipe()
114+
let textOutput = op.isTrue ? try portLibrary.textOutputFrom(op) : nil
115+
if op.isTrue {
116+
outputPipe.fileHandleForReading.readabilityHandler = { handle in
117+
condition.lock()
118+
let data = handle.availableData
119+
if data.count > 0 {
120+
_ = textOutput?.writeString(String(data: data, encoding: .utf8) ?? "")
121+
}
122+
condition.unlock()
123+
}
124+
proc.standardOutput = outputPipe
125+
proc.standardError = outputPipe
126+
} else {
127+
proc.standardOutput = nil
128+
proc.standardError = nil
129+
}
130+
let inputPipe = Pipe()
131+
if ipt.isTrue {
132+
if let data = try ipt.asString().data(using: .utf8) {
133+
inputPipe.fileHandleForWriting.write(data)
134+
}
135+
}
136+
inputPipe.fileHandleForWriting.closeFile()
137+
proc.standardInput = inputPipe
138+
condition.lock()
139+
defer {
140+
if proc.isRunning {
141+
proc.terminate()
142+
}
143+
outputPipe.fileHandleForReading.readabilityHandler = nil
144+
inputPipe.fileHandleForWriting.writeabilityHandler = nil
145+
condition.unlock()
146+
}
147+
try proc.run()
148+
while proc.isRunning && !self.context.machine.isAbortionRequested() {
149+
condition.wait(until: Date(timeInterval: 0.7, since: Date()))
150+
}
151+
inputPipe.fileHandleForWriting.writeabilityHandler = nil
152+
outputPipe.fileHandleForReading.readabilityHandler = nil
153+
if op.isTrue {
154+
let restdata = outputPipe.fileHandleForReading.availableData
155+
if restdata.count > 0 {
156+
_ = textOutput?.writeString(String(data: restdata, encoding: .utf8) ?? "")
157+
}
158+
}
159+
return .fixnum(Int64(proc.isRunning ? -1 : proc.terminationStatus))
160+
}
161+
}

Sources/LispKit/Runtime/ContextDelegate.swift

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public protocol ContextDelegate: TextInputSource, TextOutputTarget {
5454

5555
/// This is called whenever garbage collection was called
5656
func garbageCollected(objectPool: ManagedObjectPool, time: Double, objectsBefore: Int)
57+
58+
/// This is called when the execution of the virtual machine got aborted.
59+
func aborted()
5760

5861
/// This is called by the `exit` function of LispKit.
5962
func emergencyExit(obj: Expr?)
@@ -153,6 +156,9 @@ public extension ContextDelegate {
153156

154157
func garbageCollected(objectPool: ManagedObjectPool, time: Double, objectsBefore: Int) {
155158
}
159+
160+
func aborted() {
161+
}
156162

157163
func emergencyExit(obj: Expr?) {
158164
NSApplication.shared.terminate(self)

Sources/LispKit/Runtime/VirtualMachine.swift

+1
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ public final class VirtualMachine: TrackedObject {
236236
/// Requests abortion of the machine evaluator.
237237
public func abort() {
238238
self.abortionRequested = true
239+
self.context.delegate.aborted()
239240
}
240241

241242
/// Returns true if an abortion was requested.

0 commit comments

Comments
 (0)