From b9adafc81ce6bb531b65176ad5a8e542d516e05f Mon Sep 17 00:00:00 2001 From: jmeaster30 Date: Sun, 21 Jan 2024 00:18:48 -0500 Subject: [PATCH] Started working on adding the API. Needing to write custom serializers/deserializers and questioning my choices in this strategy --- libvore/bytecode.go | 4 + libvore/converter.go | 419 +++++++++++++++++++++++++++++++++++++ libvore/vore.go | 8 + libvorejs/demo/index.html | 4 +- libvorejs/demo/index.js | 34 +++ libvorejs/src/libvore.d.ts | 3 +- libvorejs/src/libvore.js | 4 + libvorejs/src/main.go | 142 ++++++++++--- 8 files changed, 589 insertions(+), 29 deletions(-) create mode 100644 libvore/converter.go diff --git a/libvore/bytecode.go b/libvore/bytecode.go index 3ababa2..c8caa20 100644 --- a/libvore/bytecode.go +++ b/libvore/bytecode.go @@ -2,6 +2,7 @@ package libvore type Command interface { execute(string, *VReader, ReplaceMode) Matches + ToMap() map[string]any } type FindCommand struct { @@ -152,6 +153,7 @@ func (c SetCommand) execute(filename string, reader *VReader, mode ReplaceMode) type SetCommandBody interface { execute(state *GlobalState, id string) *GlobalState + ToMap() map[string]any } type SetCommandExpression struct { @@ -184,10 +186,12 @@ func (s SetCommandTransform) execute(state *GlobalState, id string) *GlobalState type SearchInstruction interface { execute(*SearchEngineState) *SearchEngineState adjust(offset int, state *GenState) SearchInstruction + ToMap() map[string]any } type ReplaceInstruction interface { execute(*ReplacerState) *ReplacerState + ToMap() map[string]any } type MatchLiteral struct { diff --git a/libvore/converter.go b/libvore/converter.go new file mode 100644 index 0000000..531b702 --- /dev/null +++ b/libvore/converter.go @@ -0,0 +1,419 @@ +package libvore + +import ( + "fmt" + "strconv" +) + +type NodeName = uint32 + +const ( + NodeFindCommand NodeName = iota + NodeReplaceCommand + NodeSetCommand + NodeSetCommandExpression + NodeSetCommandMatches + NodeSetCommandTransform + NodeMatchLiteral + NodeMatchCharClass + NodeMatchVariable + NodeMatchRange + NodeCallSubroutine + NodeBranch + NodeStartNotIn + NodeFailNotIn + NodeEndNotIn + NodeStartLoop + NodeStopLoop + NodeStartVarDec + NodeEndVarDec + NodeStartSubroutine + NodeEndSubroutine + NodeJump + NodeReplaceString + NodeReplaceVariable + NodeReplaceProcess +) + +func ToMap(commands []Command) []map[string]any { + var results []map[string]any + for _, c := range commands { + results = append(results, c.ToMap()) + } + return results +} + +func CommandsFromMap(commands []map[string]any) ([]Command, error) { + var results []Command + for _, c := range commands { + com, err := CommandFromMap(c) + if err != nil { + return nil, err + } + results = append(results, com) + } + return results, nil +} + +func CommandFromMap(command map[string]any) (Command, error) { + nodeTypeInt, err := strconv.ParseInt(command["node"].(string), 10, 32) + if err != nil { + return nil, err + } + nodeType := NodeName(nodeTypeInt) + switch nodeType { + case NodeFindCommand: + return FindCommandFromMap(command) + case NodeReplaceCommand: + case NodeSetCommand: + default: + } + //log.Panicf("'%v' is not a Command node", nodeType) + return nil, fmt.Errorf("'%v' is not a Command node", nodeType) +} + +func SearchInstructionsFromMap(searchInstructions []any) ([]SearchInstruction, error) { + var results []SearchInstruction + for _, c := range searchInstructions { + instruction, ok := c.(map[string]any) + if !ok { + return nil, fmt.Errorf("can't assert type %+v to map[string]any", c) + } + inst, err := SearchInstructionFromMap(instruction) + if err != nil { + return nil, err + } + results = append(results, inst) + } + return results, nil +} + +func SearchInstructionFromMap(command map[string]any) (SearchInstruction, error) { + nodeTypeInt, err := strconv.ParseInt(command["node"].(string), 10, 32) + if err != nil { + return nil, err + } + nodeType := NodeName(nodeTypeInt) + switch nodeType { + case NodeMatchLiteral: + return MatchLiteralFromMap(command) + case NodeMatchCharClass: + return MatchCharClassFromMap(command) + case NodeMatchVariable: + case NodeMatchRange: + case NodeCallSubroutine: + case NodeBranch: + case NodeStartNotIn: + case NodeFailNotIn: + case NodeEndNotIn: + case NodeStartLoop: + case NodeStopLoop: + case NodeStartVarDec: + case NodeEndVarDec: + case NodeStartSubroutine: + case NodeEndSubroutine: + case NodeJump: + default: + } + //log.Panicf("'%v' is not a SearchInstruction node", nodeType) + return nil, fmt.Errorf("'%v' is not a SearchInstruction node", nodeType) +} + +func (c FindCommand) ToMap() map[string]any { + var body []map[string]any + for _, b := range c.body { + body = append(body, b.ToMap()) + } + + return map[string]any{ + "node": strconv.FormatInt(int64(NodeFindCommand), 10), + "all": strconv.FormatBool(c.all), + "skip": strconv.FormatInt(int64(c.skip), 10), + "take": strconv.FormatInt(int64(c.take), 10), + "last": strconv.FormatInt(int64(c.last), 10), + "body": body, + } +} + +func FindCommandFromMap(obj map[string]any) (*FindCommand, error) { + all, err := strconv.ParseBool(obj["all"].(string)) + if err != nil { + return nil, err + } + + skip, err := strconv.ParseInt(obj["skip"].(string), 10, 32) + if err != nil { + return nil, err + } + + take, err := strconv.ParseInt(obj["take"].(string), 10, 32) + if err != nil { + return nil, err + } + + last, err := strconv.ParseInt(obj["last"].(string), 10, 32) + if err != nil { + return nil, err + } + + insts, ok := obj["body"].([]any) + if !ok { + return nil, fmt.Errorf("bad type assertion: %+v to []map[string]any", obj["body"]) + } + //return nil, fmt.Errorf("insts asserted %+v to any", insts) + body, err := SearchInstructionsFromMap(insts) + if err != nil { + return nil, err + } + + return &FindCommand{ + all: all, + skip: int(skip), + take: int(take), + last: int(last), + body: body, + }, nil +} + +func (c ReplaceCommand) ToMap() map[string]any { + var body []map[string]any + for _, b := range c.body { + body = append(body, b.ToMap()) + } + + var replacer []map[string]any + for _, c := range c.replacer { + replacer = append(replacer, c.ToMap()) + } + + return map[string]any{ + "node": NodeReplaceCommand, + "all": c.all, + "skip": c.skip, + "take": c.take, + "last": c.last, + "body": body, + "replacer": replacer, + } +} + +func (c SetCommand) ToMap() map[string]any { + return map[string]any{ + "node": NodeSetCommand, + "id": c.id, + "body": c.body.ToMap(), + } +} + +func (s SetCommandExpression) ToMap() map[string]any { + var instructions []map[string]any + for _, b := range s.instructions { + instructions = append(instructions, b.ToMap()) + } + return map[string]any{ + "node": NodeSetCommandExpression, + "instructions": instructions, + } +} + +func (s SetCommandMatches) ToMap() map[string]any { + return map[string]any{ + "node": NodeSetCommandMatches, + "command": s.command.ToMap(), + } +} + +func (s SetCommandTransform) ToMap() map[string]any { + return map[string]any{ + "node": NodeSetCommandTransform, + "statements": []map[string]any{}, + } +} + +func (i MatchLiteral) ToMap() map[string]any { + return map[string]any{ + "node": strconv.FormatInt(int64(NodeMatchLiteral), 10), + "not": strconv.FormatBool(i.not), + "toFind": i.toFind, + "caseless": strconv.FormatBool(i.caseless), + } +} + +func MatchLiteralFromMap(obj map[string]any) (*MatchLiteral, error) { + notFlag, err := strconv.ParseBool(obj["not"].(string)) + if err != nil { + return nil, err + } + + toFind := obj["toFind"].(string) + + caseless, err := strconv.ParseBool(obj["caseless"].(string)) + if err != nil { + return nil, err + } + + return &MatchLiteral{ + not: notFlag, + toFind: toFind, + caseless: caseless, + }, nil +} + +func (i MatchCharClass) ToMap() map[string]any { + return map[string]any{ + "node": NodeMatchCharClass, + "not": i.not, + "class": i.class, + } +} + +func MatchCharClassFromMap(obj map[string]any) (*MatchCharClass, error) { + notFlag, err := strconv.ParseBool(obj["not"].(string)) + if err != nil { + return nil, err + } + + class, err := strconv.ParseInt(obj["class"].(string), 10, 32) + if err != nil { + return nil, err + } + + return &MatchCharClass{ + not: notFlag, + class: AstCharacterClassType(class), + }, nil +} + +func (i MatchVariable) ToMap() map[string]any { + return map[string]any{ + "node": NodeMatchVariable, + "name": i.name, + } +} + +func (i MatchRange) ToMap() map[string]any { + return map[string]any{ + "node": NodeMatchRange, + "not": i.not, + "from": i.from, + "to": i.to, + } +} + +func (i CallSubroutine) ToMap() map[string]any { + return map[string]any{ + "node": NodeCallSubroutine, + "name": i.name, + "toPC": i.toPC, + } +} + +func (i Branch) ToMap() map[string]any { + return map[string]any{ + "node": NodeBranch, + "branches": i.branches, + } +} + +func (i StartNotIn) ToMap() map[string]any { + return map[string]any{ + "node": NodeStartNotIn, + "nextCheckpointPC": i.nextCheckpointPC, + } +} + +func (i FailNotIn) ToMap() map[string]any { + return map[string]any{ + "node": NodeFailNotIn, + } +} + +func (i EndNotIn) ToMap() map[string]any { + return map[string]any{ + "node": NodeEndNotIn, + "maxSize": i.maxSize, + } +} + +func (i StartLoop) ToMap() map[string]any { + return map[string]any{ + "node": NodeStartLoop, + "id": i.id, + "minLoops": i.minLoops, + "maxLoops": i.maxLoops, + "fewest": i.fewest, + "exitLoop": i.exitLoop, + "name": i.name, + } +} + +func (i StopLoop) ToMap() map[string]any { + return map[string]any{ + "node": NodeStopLoop, + "id": i.id, + "minLoops": i.minLoops, + "maxLoops": i.maxLoops, + "fewest": i.fewest, + "startLoop": i.startLoop, + "name": i.name, + } +} + +func (i StartVarDec) ToMap() map[string]any { + return map[string]any{ + "node": NodeStartVarDec, + "name": i.name, + } +} + +func (i EndVarDec) ToMap() map[string]any { + return map[string]any{ + "node": NodeEndVarDec, + "name": i.name, + } +} + +func (i StartSubroutine) ToMap() map[string]any { + return map[string]any{ + "node": NodeStartSubroutine, + "id": i.id, + "name": i.name, + "endOffset": i.endOffset, + } +} + +func (i EndSubroutine) ToMap() map[string]any { + return map[string]any{ + "node": NodeEndSubroutine, + "name": i.name, + "validate": []map[string]any{}, + } +} + +func (i Jump) ToMap() map[string]any { + return map[string]any{ + "node": NodeJump, + "newProgramCounter": i.newProgramCounter, + } +} + +func (i ReplaceString) ToMap() map[string]any { + return map[string]any{ + "node": NodeReplaceString, + "value": i.value, + } +} + +func (i ReplaceVariable) ToMap() map[string]any { + return map[string]any{ + "node": NodeReplaceVariable, + "name": i.name, + } +} + +func (i ReplaceProcess) ToMap() map[string]any { + return map[string]any{ + "node": NodeReplaceProcess, + "process": []map[string]any{}, + } +} diff --git a/libvore/vore.go b/libvore/vore.go index bd60f18..9f36933 100644 --- a/libvore/vore.go +++ b/libvore/vore.go @@ -315,6 +315,14 @@ type Vore struct { bytecode []Command } +func Build(bytecode []Command) *Vore { + return &Vore{bytecode: bytecode} +} + +func (v *Vore) Bytecode() []Command { + return v.bytecode +} + func Compile(command string) (*Vore, error) { return compile("source", strings.NewReader(command)) } diff --git a/libvorejs/demo/index.html b/libvorejs/demo/index.html index 15f07cb..f6125ee 100644 --- a/libvorejs/demo/index.html +++ b/libvorejs/demo/index.html @@ -9,7 +9,9 @@ - + + +
diff --git a/libvorejs/demo/index.js b/libvorejs/demo/index.js index bd1b8c4..6bedeac 100644 --- a/libvorejs/demo/index.js +++ b/libvorejs/demo/index.js @@ -10,4 +10,38 @@ const doVoreSearch = () => { console.log("ERROR ERROR ERROR"); document.getElementById("results").innerHTML = JSON.stringify(err, null, 4); }); +} + +let compiledSource = null; + +const doVoreCompile = () => { + const sourceCode = document.getElementById("sourceCode").value; + libvorejs.compile(sourceCode) + .then(value => { + console.log(value); + compiledSource = value; + document.getElementById("results").innerHTML = JSON.stringify(compiledSource, null, 4); + }) + .catch(err => { + console.log("ERROR ERROR ERROR"); + document.getElementById("results").innerHTML = JSON.stringify(err, null, 4); + }) +} + +const doVoreCompiledSearch = () => { + const searchText = document.getElementById("searchText").value; + if (compiledSource == null) { + console.log("source not compiled :("); + return; + } + + libvorejs.search(compiledSource, searchText) + .then(value => { + console.log(value); + document.getElementById("results").innerHTML = JSON.stringify(value, null, 4); + }) + .catch(err => { + console.log("ERROR ERROR ERROR"); + document.getElementById("results").innerHTML = JSON.stringify(err, null, 4); + }); } \ No newline at end of file diff --git a/libvorejs/src/libvore.d.ts b/libvorejs/src/libvore.d.ts index 19dbaae..96a9dc1 100644 --- a/libvorejs/src/libvore.d.ts +++ b/libvorejs/src/libvore.d.ts @@ -1,2 +1,3 @@ export as namespace libvorejs; -export function search(source: string, text: string): Promise; \ No newline at end of file +export function search(source: string|object, text: string): Promise; +export function compile(source: string): Promise; \ No newline at end of file diff --git a/libvorejs/src/libvore.js b/libvorejs/src/libvore.js index e836701..96d3cbe 100644 --- a/libvorejs/src/libvore.js +++ b/libvorejs/src/libvore.js @@ -3,3 +3,7 @@ import wasm from 'libvorejs'; export function search(source, text) { return wasm.voreSearch(source, text); } + +export function compile(source) { + return wasm.voreCompile(source); +} \ No newline at end of file diff --git a/libvorejs/src/main.go b/libvorejs/src/main.go index 7469b4c..ea70a25 100644 --- a/libvorejs/src/main.go +++ b/libvorejs/src/main.go @@ -1,6 +1,9 @@ package main import ( + "encoding/json" + "errors" + "fmt" "syscall/js" "github.com/jmeaster30/vore/libvore" @@ -10,27 +13,25 @@ func main() { done := make(chan struct{}, 0) root := js.Global().Get("__libvore__") root.Set("voreSearch", js.FuncOf(voreSearch)) + root.Set("voreCompile", js.FuncOf(voreCompile)) <-done } -// TODO I would like to add the "compile" and "run" functions so you don't have to compile the source each search -// TODO I would also like to use promises for asynchronous code and a little better error handling - -func buildError(err *libvore.VoreError) map[string]interface{} { - return map[string]interface{}{ - "error": map[string]interface{}{ +func buildError(err *libvore.VoreError) map[string]any { + return map[string]any{ + "error": map[string]any{ "message": err.Message, "token": err.Token.Lexeme, "tokenType": err.Token.TokenType.PP(), - "line": map[string]interface{}{ + "line": map[string]any{ "start": err.Token.Line.Start, "end": err.Token.Line.End, }, - "column": map[string]interface{}{ + "column": map[string]any{ "start": err.Token.Column.Start, "end": err.Token.Column.End, }, - "offset": map[string]interface{}{ + "offset": map[string]any{ "start": err.Token.Offset.Start, "end": err.Token.Offset.End, }, @@ -39,18 +40,18 @@ func buildError(err *libvore.VoreError) map[string]interface{} { } func buildMatch(match libvore.Match) map[string]interface{} { - result := map[string]interface{}{ + result := map[string]any{ "filename": match.Filename, "matchNumber": match.MatchNumber, - "offset": map[string]interface{}{ + "offset": map[string]any{ "start": match.Offset.Start, "end": match.Offset.End, }, - "line": map[string]interface{}{ + "line": map[string]any{ "start": match.Line.Start, "end": match.Line.End, }, - "column": map[string]interface{}{ + "column": map[string]any{ "start": match.Column.Start, "end": match.Column.End, }, @@ -62,8 +63,8 @@ func buildMatch(match libvore.Match) map[string]interface{} { return result } -func buildMatches(input string, matches libvore.Matches) map[string]interface{} { - convertedMatches := []interface{}{} +func buildMatches(input string, matches libvore.Matches) map[string]any { + var convertedMatches []any // I think the libvore.Vore.Run function should ultimately return the resulting string but not quite sure if I like that resultString := "" @@ -78,10 +79,10 @@ func buildMatches(input string, matches libvore.Matches) map[string]interface{} } if inputIndex < len(input) { - resultString += input[inputIndex:len(input)] + resultString += input[inputIndex:] } - return map[string]interface{}{ + return map[string]any{ "input": input, "output": resultString, "matches": convertedMatches, @@ -89,14 +90,83 @@ func buildMatches(input string, matches libvore.Matches) map[string]interface{} } func voreSearch(this js.Value, args []js.Value) any { - source := args[0].String() + source := args[0] input := args[1].String() resolve := args[2] reject := args[3] defer func() { if r := recover(); r == nil { - reject.Invoke(js.ValueOf(map[string]interface{}{ - "error": "Aw man :( ... Go paniced", + reject.Invoke(js.ValueOf(map[string]any{ + "error": "Aw man :( ... Go panicked", + })) + } + }() + + var vore *libvore.Vore + if source.Type() == js.TypeString { + var err error + vore, err = libvore.Compile(source.String()) + if err != nil { + // TODO pass in resolve and reject promises so if there is an error we can reject and use the "catch" syntax + var detailedErr *libvore.VoreError + if errors.As(err, &detailedErr) { + reject.Invoke(js.ValueOf(buildError(detailedErr))) + } + return nil + } + } else if source.Type() == js.TypeObject { + bytecodeBytes := source.Get("bytecode") + if bytecodeBytes.IsUndefined() || bytecodeBytes.IsNull() { + reject.Invoke(js.ValueOf(map[string]any{ + "error": "source's bytecode was null or undefined :(", + })) + return nil + } + + //js.Global().Get("console").Call("log", fmt.Sprintf("STRING: %s", bytecodeBytes.String())) + + buf := []byte(bytecodeBytes.String()) + //transform the byte array to map + var bytecodeCommands []map[string]any + err := json.Unmarshal(buf, &bytecodeCommands) + if err != nil { + //js.Global().Get("console").Call("log", fmt.Sprintf("ERROR: %+v", err)) + reject.Invoke(js.ValueOf(map[string]any{ + "error": "Failed to unmarshal json object :(", + })) + return nil + } + + js.Global().Get("console").Call("log", fmt.Sprintf("Getting commands... %+v", bytecodeCommands)) + commands, err := libvore.CommandsFromMap(bytecodeCommands) + if err != nil { + js.Global().Get("console").Call("log", fmt.Sprintf("ERROR: %+v", err)) + reject.Invoke(js.ValueOf(map[string]any{ + "error": "Failed to unmarshal json object :(", + })) + return nil + } + js.Global().Get("console").Call("log", fmt.Sprintf("Commands: %+v", commands)) + + vore = libvore.Build(commands) + } + + js.Global().Get("console").Call("log", fmt.Sprintf("Vore: %+v", vore)) + matches := vore.Run(input) + resolve.Invoke(js.ValueOf(buildMatches(input, matches))) + + return nil +} + +func voreCompile(this js.Value, args []js.Value) any { + // return an object with a member called "bytecode" that is a uint8array + source := args[0].String() + resolve := args[1] + reject := args[2] + defer func() { + if r := recover(); r == nil { + reject.Invoke(js.ValueOf(map[string]any{ + "error": "Aw man :( ... Go panicked", })) } }() @@ -104,16 +174,34 @@ func voreSearch(this js.Value, args []js.Value) any { vore, err := libvore.Compile(source) if err != nil { // TODO pass in resolve and reject promises so if there is an error we can reject and use the "catch" syntax - if detailedErr, ok := err.(*libvore.VoreError); ok { + var detailedErr *libvore.VoreError + if errors.As(err, &detailedErr) { reject.Invoke(js.ValueOf(buildError(detailedErr))) - } else { - reject.Invoke(js.ValueOf(map[string]interface{}{ - "error": err.Error(), - })) } return nil } - matches := vore.Run(input) - resolve.Invoke(js.ValueOf(buildMatches(input, matches))) + + //js.Global().Get("console").Call("log", fmt.Sprintf("VORE: %+v", vore)) + + var serializedBytecode []map[string]any + for _, command := range vore.Bytecode() { + serializedBytecode = append(serializedBytecode, command.ToMap()) + } + + //js.Global().Get("console").Call("log", fmt.Sprintf("Serialized: %+v", serializedBytecode)) + + bytecodeBytes, err := json.Marshal(serializedBytecode) + if err != nil { + reject.Invoke(js.ValueOf(map[string]any{ + "error": "Failed to marshal GO object :(", + })) + return nil + } + + //js.Global().Get("console").Call("log", fmt.Sprintf("BYTES: %s", bytecodeBytes)) + + resolve.Invoke(js.ValueOf(map[string]any{ + "bytecode": string(bytecodeBytes[:]), + })) return nil }