1
0
mirror of git://jb55.com/damus synced 2024-09-29 16:30:44 +00:00

nscript: load script view

This allows you to open and run scripts for testing purposes, but only
from external links such as nostr:nscript...
This commit is contained in:
William Casarin 2023-07-04 11:42:16 -07:00
parent 640fbf23ea
commit a04a401292
10 changed files with 261 additions and 25 deletions

View File

@ -753,15 +753,6 @@ static char *instr_name(enum instr_tag tag)
return unk;
}
static INLINE int was_section_parsed(struct module *module,
enum section_tag section)
{
if (section == section_custom)
return module->custom_sections > 0;
return module->parsed & (1 << section);
}
static INLINE int was_name_section_parsed(struct module *module,
enum name_subsection_tag subsection)
{
@ -1322,7 +1313,7 @@ static int parse_valtype(struct wasm_parser *p, enum valtype *valtype)
}
if (unlikely(!is_valtype((unsigned char)*valtype))) {
cursor_print_around(&p->cur, 10);
//cursor_print_around(&p->cur, 10);
p->cur.p = start;
return parse_err(p, "0x%02x is not a valid valtype tag", *valtype);
}
@ -1684,7 +1675,7 @@ static int parse_reftype(struct wasm_parser *p, enum reftype *reftype)
}
if (!is_valid_reftype(tag)) {
cursor_print_around(&p->cur, 10);
//cursor_print_around(&p->cur, 10);
parse_err(p, "invalid reftype: 0x%02x", tag);
return 0;
}
@ -2176,6 +2167,7 @@ static int parse_const_expr(struct expr_parser *p, struct expr *expr)
}
if (unlikely(!is_const_instr(tag))) {
//cursor_print_around(p->code, 20);
return note_error(p->errs, p->code,
"invalid const expr instruction: '%s'",
instr_name(tag));
@ -2551,7 +2543,7 @@ static int parse_wdata(struct wasm_parser *p, struct wdata *data)
}
if (tag > 2) {
cursor_print_around(&p->cur, 10);
//cursor_print_around(&p->cur, 10);
return parse_err(p, "invalid datasegment tag: 0x%x", tag);
}

View File

@ -837,4 +837,14 @@ static INLINE struct callframe *top_callframes(struct cursor *cur, int top)
return (struct callframe*)cursor_topn(cur, sizeof(struct callframe), top);
}
static INLINE int was_section_parsed(struct module *module,
enum section_tag section)
{
if (section == section_custom)
return module->custom_sections > 0;
return module->parsed & (1 << section);
}
#endif /* PROTOVERSE_WASM_H */

View File

@ -46,6 +46,7 @@
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; };
4C190F222A53950D00027FD5 /* bool_setting.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C190F212A53950D00027FD5 /* bool_setting.wasm */; };
4C190F252A547D2000027FD5 /* LoadScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F242A547D2000027FD5 /* LoadScript.swift */; };
4C198DEF29F88C6B004C165C /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */; };
4C198DF029F88C6B004C165C /* Readme.md in Resources */ = {isa = PBXBuildFile; fileRef = 4C198DEC29F88C6B004C165C /* Readme.md */; };
4C198DF129F88C6B004C165C /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4C198DED29F88C6B004C165C /* License.txt */; };
@ -470,6 +471,7 @@
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
4C190F212A53950D00027FD5 /* bool_setting.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = bool_setting.wasm; path = nostrscript/bool_setting.wasm; sourceTree = SOURCE_ROOT; };
4C190F242A547D2000027FD5 /* LoadScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadScript.swift; sourceTree = "<group>"; };
4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = "<group>"; };
4C198DEC29F88C6B004C165C /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = "<group>"; };
4C198DED29F88C6B004C165C /* License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = License.txt; sourceTree = "<group>"; };
@ -974,6 +976,14 @@
path = Zaps;
sourceTree = "<group>";
};
4C190F232A547D1700027FD5 /* NostrScript */ = {
isa = PBXGroup;
children = (
4C190F242A547D2000027FD5 /* LoadScript.swift */,
);
path = NostrScript;
sourceTree = "<group>";
};
4C198DEA29F88C6B004C165C /* BlurHash */ = {
isa = PBXGroup;
children = (
@ -1048,6 +1058,7 @@
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
4C190F232A547D1700027FD5 /* NostrScript */,
4C7D09692A0AEA0400943473 /* CodeScanner */,
4C7D095A2A098C5C00943473 /* Wallet */,
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
@ -1753,6 +1764,7 @@
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */,
4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */,
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */,
4C190F252A547D2000027FD5 /* LoadScript.swift in Sources */,
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */,
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */,

View File

@ -224,6 +224,12 @@ struct ContentView: View {
navigationCoordinator.push(route: Route.Wallet(wallet: damus_state!.wallet))
}
func open_script(_ script: [UInt8]) {
print("pushing script nav")
let model = ScriptModel(data: script, state: .not_loaded)
navigationCoordinator.push(route: Route.Script(script: model))
}
func open_profile(id: String) {
let profile_model = ProfileModel(pubkey: id, damus: damus_state!)
let followers = FollowersModel(damus_state: damus_state!, target: id)
@ -331,7 +337,9 @@ struct ContentView: View {
case .filter(let filt): self.open_search(filt: filt)
case .profile(let id): self.open_profile(id: id)
case .event(let ev): self.open_event(ev: ev)
case .wallet_connect(let nwc): self.open_wallet(nwc: nwc)}
case .wallet_connect(let nwc): self.open_wallet(nwc: nwc)
case .script(let data): self.open_script(data)
}
}
}
.onReceive(handle_notify(.compose)) { notif in
@ -946,6 +954,7 @@ enum OpenResult {
case filter(NostrFilter)
case event(NostrEvent)
case wallet_connect(WalletConnectURL)
case script([UInt8])
}
func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) -> Void) {
@ -973,5 +982,9 @@ func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) ->
result(.filter(filt))
break
// TODO: handle filter searches?
case .script(let script):
result(.script(script))
break
}
}

View File

@ -11,6 +11,7 @@ import Foundation
enum NostrLink: Equatable {
case ref(ReferencedId)
case filter(NostrFilter)
case script([UInt8])
}
func encode_pubkey_uri(_ ref: ReferencedId) -> String {
@ -105,6 +106,8 @@ func decode_nostr_bech32_uri(_ s: String) -> NostrLink? {
return .ref(ReferencedId(ref_id: pubkey, relay_id: nil, key: "p"))
case .note(let id):
return .ref(ReferencedId(ref_id: id, relay_id: nil, key: "e"))
case .nscript(let data):
return .script(data)
}
}

View File

@ -12,6 +12,7 @@ enum Bech32Object {
case nsec(String)
case npub(String)
case note(String)
case nscript([UInt8])
static func parse(_ str: String) -> Bech32Object? {
guard let decoded = try? bech32_decode(str) else {
@ -24,6 +25,8 @@ enum Bech32Object {
return .nsec(hex_encode(decoded.data))
} else if decoded.hrp == "note" {
return .note(hex_encode(decoded.data))
} else if decoded.hrp == "nscript" {
return .nscript(decoded.data.bytes)
}
return nil

View File

@ -16,6 +16,7 @@ enum Route: Hashable {
case Following(following: FollowingModel)
case MuteList(users: [String])
case RelayConfig
case Script(script: ScriptModel)
case Bookmarks
case Config
case EditMetadata
@ -105,6 +106,8 @@ enum Route: Hashable {
WalletScannerView(result: walletScanResult)
case .FollowersYouKnow(let friendedFollowers, let followers):
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers, followers: followers)
case .Script(let load_model):
LoadScript(pool: damusState.pool, model: load_model)
}
}
@ -172,8 +175,10 @@ enum Route: Hashable {
return true
case (.FollowersYouKnow(_, _), .FollowersYouKnow(_, _)):
return true
case (.Script(_), .Script(_)):
return true
default:
return false
return true
}
}
@ -259,6 +264,9 @@ enum Route: Hashable {
hasher.combine("followersYouKnow")
hasher.combine(friendedFollowers)
hasher.combine(followers.sub_id)
case .Script(let model):
hasher.combine("script")
hasher.combine(model.data.count)
}
}
}

View File

@ -0,0 +1,163 @@
//
// LoadScript.swift
// damus
//
// Created by William Casarin on 2023-07-04.
//
import SwiftUI
struct ScriptLoaded {
let script: NostrScript
let state: LoadedState
}
enum LoadedState {
case loaded
case running
case ran(NostrScriptRunResult)
}
enum LoadScriptState {
case not_loaded
case loading
case loaded(ScriptLoaded)
case failed(NostrScriptLoadErr)
static func loaded(script: NostrScript) -> LoadScriptState {
return .loaded(ScriptLoaded(script: script, state: .loaded))
}
}
class ScriptModel: ObservableObject {
var data: [UInt8]
@Published var state: LoadScriptState
init(data: [UInt8], state: LoadScriptState) {
self.data = data
self.state = state
}
func run() async {
guard case .loaded(let script) = state else {
return
}
self.state = .loaded(.init(script: script.script, state: .running))
let t = Task.detached {
return script.script.run()
}
let res = await t.value
self.state = .loaded(.init(script: script.script, state: .ran(res)))
}
@MainActor
func load(pool: RelayPool) async {
guard case .not_loaded = state else {
return
}
self.state = .loading
let script = NostrScript(pool: pool, data: self.data)
let t = Task.detached {
print("loading script")
return script.load()
}
let load_err = await t.value
let t2 = Task { @MainActor in
if let load_err {
self.state = .failed(load_err)
return
}
self.state = .loaded(script: script)
}
await t2.value
}
}
struct LoadScript: View {
let pool: RelayPool
@ObservedObject var model: ScriptModel
func ScriptView(_ script: ScriptLoaded) -> some View {
ScrollView {
VStack {
let imports = script.script.imports()
(Text(verbatim: "\(imports.count)") +
Text(" Imports"))
.font(.title)
ForEach(imports.indices, id: \.self) { ind in
Text(imports[ind])
}
switch script.state {
case .loaded:
BigButton("Run") {
Task {
await model.run()
}
}
case .running:
Text("Running...")
case .ran(let result):
switch result {
case .runtime_err(let errs):
Text("Runtime error")
.font(.title2)
ForEach(errs.indices, id: \.self) { ind in
Text(verbatim: errs[ind])
}
case .suspend:
Text("Ran to suspension.")
case .finished(let code):
Text("Executed successfuly, returned with code \(code)")
}
}
}
}
}
var body: some View {
Group {
switch self.model.state {
case .not_loaded:
ProgressView()
.progressViewStyle(.circular)
case .loading:
ProgressView()
.progressViewStyle(.circular)
case .loaded(let loaded):
ScriptView(loaded)
case .failed(let load_err):
VStack(spacing: 20) {
Text("NostrScript Error")
.font(.title)
switch load_err {
case .parse:
Text("Failed to parse")
case .module_init:
Text("Failed to initialize")
}
}
}
}
.task {
await model.load(pool: self.pool)
}
.navigationTitle("NostrScript")
}
}
/*
#Preview {
LoadScript()
}
*/

View File

@ -38,13 +38,13 @@ final class NostrScriptTests: XCTestCase {
func test_bool_set() throws {
var data = try load_bool_set_test_wasm().bytes
let pool = RelayPool()
let script = NostrScript(pool: pool)
let script = NostrScript(pool: pool, data: data)
let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
UserSettingsStore.pubkey = pk
let key = pk_setting_key(pk, key: "nozaps")
UserDefaults.standard.set(true, forKey: key)
let load_err = script.load(wasm: &data)
let load_err = script.load()
XCTAssertNil(load_err)
let res = script.run()
@ -62,9 +62,9 @@ final class NostrScriptTests: XCTestCase {
func test_nostrscript() throws {
var data = try loadTestWasm().bytes
let pool = RelayPool()
let script = NostrScript(pool: pool)
let script = NostrScript(pool: pool, data: data)
let load_err = script.load(wasm: &data)
let load_err = script.load()
XCTAssertNil(load_err)
let res = script.run()

View File

@ -41,21 +41,29 @@ enum NostrScriptLoadResult {
case loaded(wasm_interp)
}
enum NostrScriptError: Error {
case not_loaded
}
class NostrScript {
private var interp: wasm_interp
private var parser: wasm_parser
var waiting_on: NScriptWaiting?
var loaded: Bool
var data: [UInt8]
private(set) var runstate: NostrScriptRunResult?
private(set) var pool: RelayPool
private(set) var event: NostrResponse?
init(pool: RelayPool) {
init(pool: RelayPool, data: [UInt8]) {
self.interp = wasm_interp()
self.parser = wasm_parser()
self.pool = pool
self.event = nil
self.runstate = nil
self.loaded = false
self.data = data
}
deinit {
@ -80,15 +88,37 @@ class NostrScript {
}
}
func test(_ str: String) {
print("hello from \(str)")
func imports() -> [String] {
guard self.loaded,
was_section_parsed(interp.module, section_import) > 0,
let module = maybe_pointee(interp.module)
else {
return []
}
var imports = [String]()
var i = 0
while i < module.import_section.num_imports {
let imp = module.import_section.imports[i]
imports.append(String(cString: imp.name))
i += 1
}
return imports
}
func load(wasm: inout [UInt8]) -> NostrScriptLoadErr? {
switch nscript_load(&parser, &interp, &wasm, UInt(wasm.count)) {
func load() -> NostrScriptLoadErr? {
guard !loaded else {
return nil
}
switch nscript_load(&parser, &interp, &self.data, UInt(data.count)) {
case NSCRIPT_LOADED:
print("load num_exports \(interp.module.pointee.export_section.num_exports)")
interp.context = Unmanaged.passUnretained(self).toOpaque()
self.loaded = true
return nil
case NSCRIPT_INIT_ERR:
return .module_init
@ -292,7 +322,9 @@ public func nscript_set_bool(interp: UnsafeMutablePointer<wasm_interp>?, setting
}
let key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: setting)
UserDefaults.standard.set(val > 0 ? true : false, forKey: key)
let b = val > 0 ? true : false
print("nscript setting bool setting \(setting) to \(b)")
UserDefaults.standard.set(b, forKey: key)
stack_push_i32(interp, 1);
return 1;
@ -316,7 +348,7 @@ public func nscript_pool_send_to(interp: UnsafeMutablePointer<wasm_interp>?, pre
}
func nscript_pool_send(script: NostrScript, req req_str: String) -> Int32 {
script.test("pool_send: '\(req_str)'")
//script.test("pool_send: '\(req_str)'")
DispatchQueue.main.sync {
script.pool.send_raw(.custom(req_str), skip_ephemeral: false)