From bed0e712deda5038f52e495bacae003098df7a55 Mon Sep 17 00:00:00 2001
From: Charlie Stanton <charlie@shtanton.xyz>
Date: Fri, 21 Jul 2023 16:42:49 +0100
Subject: Reimplements inserting basic values using subexes

---
 subex/parse.go      | 128 ++++++++++++++++++++++++++--------------------------
 subex/subexast.go   |   9 +++-
 subex/subexstate.go |  43 ++++++++++++++----
 3 files changed, 105 insertions(+), 75 deletions(-)

diff --git a/subex/parse.go b/subex/parse.go
index 6c19df4..2392b22 100644
--- a/subex/parse.go
+++ b/subex/parse.go
@@ -27,54 +27,47 @@ func isNumericRune(r rune) bool {
 }
 
 // Having just parsed a `, read until the next ` and parse the contents into a list of non-string atoms
-func parseNonStringLiteral(l RuneReader) (literals []walk.Scalar) {
-	for {
-		r := l.Next()
-		if isNumericRune(r) {
-			var builder strings.Builder
+func parseScalarLiteral(l RuneReader) (walk.Scalar, bool) {
+	r := l.Next()
+	if isNumericRune(r) {
+		var builder strings.Builder
+		builder.WriteRune(r)
+		for {
+			r := l.Next()
+			if !isNumericRune(r) {
+				l.Rewind()
+				break
+			}
 			builder.WriteRune(r)
-			for {
-				r := l.Next()
-				if !isNumericRune(r) {
-					l.Rewind()
-					break
-				}
-				builder.WriteRune(r)
+		}
+		numberString := builder.String()
+		number, err := strconv.ParseFloat(numberString, 64)
+		if err != nil {
+			panic("Invalid number literal")
+		}
+		return walk.NumberScalar(number), true
+	}
+	switch r {
+		case 'n':
+			if accept(l, "u") && accept(l, "l") && accept(l, "l") {
+				return walk.NullScalar{}, true
+			} else {
+				panic("Invalid literal")
 			}
-			numberString := builder.String()
-			number, err := strconv.ParseFloat(numberString, 64)
-			if err != nil {
-				panic("Invalid number literal")
+		case 't':
+			if accept(l, "r") && accept(l, "u") && accept(l, "e") {
+				return walk.BoolScalar(true), true
+			} else {
+				panic("Invalid literal")
 			}
-			literals = append(literals, walk.NumberScalar(number))
-			continue
-		}
-		switch r {
-			case '`', '~':
-				return literals
-			case ' ', '\t':
-				continue
-			case 'n':
-				if accept(l, "u") && accept(l, "l") && accept(l, "l") {
-					literals = append(literals, walk.NullScalar{})
-				} else {
-					panic("Invalid literal")
-				}
-			case 't':
-				if accept(l, "r") && accept(l, "u") && accept(l, "e") {
-					literals = append(literals, walk.BoolScalar(true))
-				} else {
-					panic("Invalid literal")
-				}
-			case 'f':
-				if accept(l, "a") && accept(l, "l") && accept(l, "s") && accept(l, "e") {
-					literals = append(literals, walk.BoolScalar(false))
-				} else {
-					panic("Invalid literal")
-				}
-			default:
+		case 'f':
+			if accept(l, "a") && accept(l, "l") && accept(l, "s") && accept(l, "e") {
+				return walk.BoolScalar(false), true
+			} else {
 				panic("Invalid literal")
-		}
+			}
+		default:
+			panic("Invalid literal")
 	}
 }
 
@@ -140,14 +133,16 @@ func parseRepeatRange(l RuneReader) (output []ConvexRange) {
 	return output
 }
 
-func parseReplacement(l RuneReader) (output []OutputContentAST) {
+// TODO: Consider if it's worth making better use of the go type system to enforce output being all runes or all values
+func parseReplacement(l RuneReader, runic bool) (output []OutputContentAST) {
 	// TODO escaping
+	// TODO add arrays, maps and strings
 	loop: for {
 		r := l.Next()
 		switch r {
 			case eof:
-				panic("Missing closing \"")
-			case '=', '^', ':':
+				panic("Missing closing `")
+			case '`':
 				break loop
 			case '$':
 				slot := l.Next()
@@ -155,14 +150,17 @@ func parseReplacement(l RuneReader) (output []OutputContentAST) {
 					panic("Missing slot character")
 				}
 				output = append(output, OutputLoadAST{slot: slot})
-			case '`':
-				literals := parseNonStringLiteral(l)
-				for _, literal := range literals {
-					output = append(output, OutputValueLiteralAST {literal})
-				}
 			default:
-				panic("Invalid value to insert")
-				//output = append(output, OutputValueLiteralAST{atom: walk.NewAtomStringRune(r)})
+				if runic {
+					output = append(output, OutputRuneLiteralAST {walk.StringRuneAtom(r)})
+				} else {
+					l.Rewind()
+					scalar, ok := parseScalarLiteral(l)
+					if !ok {
+						panic("Invalid scalar literal")
+					}
+					output = append(output, OutputValueLiteralAST {scalar})
+				}
 		}
 	}
 	return output
@@ -265,15 +263,9 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST {
 		case ')', ']', '"', '|', ';', '{', '+', '-', '*', '/', '!', '$':
 			l.Rewind()
 			return SubexASTEmpty{}
-		case '=':
-			replacement := parseReplacement(l)
-			lhs = SubexASTOutput{replacement}
-		case '`':
-			literals := parseNonStringLiteral(l)
-			lhs = SubexASTEmpty{}
-			for _, literal := range literals {
-				lhs = SubexASTConcat {lhs, SubexASTCopyScalar {literal}}
-			}
+		// case '=':
+		// 	replacement := parseReplacement(l)
+		// 	lhs = SubexASTOutput{replacement}
 		// case '^':
 		// 	replacement := parseReplacement(l)
 		// 	replacement = append(
@@ -307,6 +299,8 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST {
 					panic("Missing matching ]")
 				}
 			}
+		case '`':
+			lhs = SubexASTOutput {parseReplacement(l, runic)}
 		case '~':
 			if runic {
 				lhs = SubexASTCopyRune {'~'}
@@ -339,8 +333,12 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST {
 			if runic {
 				lhs = SubexASTCopyRune {r}
 			} else {
-				// TODO: Allow whitespace outside of runic sections
-				panic("Tried to match rune outside of string")
+				l.Rewind()
+				scalar, ok := parseScalarLiteral(l)
+				if !ok {
+					panic("Invalid subex")
+				}
+				lhs = SubexASTCopyScalar {scalar}
 			}
 	}
 	loop: for {
diff --git a/subex/subexast.go b/subex/subexast.go
index 31c77ba..b1ac931 100644
--- a/subex/subexast.go
+++ b/subex/subexast.go
@@ -246,7 +246,14 @@ type OutputValueLiteralAST struct {
 	atom walk.Value
 }
 func (ast OutputValueLiteralAST) compile(slotMap *SlotMap) OutputContent {
-	return OutputAtomLiteral {ast.atom}
+	return OutputValueLiteral {ast.atom}
+}
+
+type OutputRuneLiteralAST struct {
+	rune walk.StringRuneAtom
+}
+func (ast OutputRuneLiteralAST) compile(slotMap *SlotMap) OutputContent {
+	return OutputRuneLiteral {ast.rune}
 }
 
 // Output a series of Atoms without reading anything from input
diff --git a/subex/subexstate.go b/subex/subexstate.go
index 7ffd592..0b21c93 100644
--- a/subex/subexstate.go
+++ b/subex/subexstate.go
@@ -121,29 +121,54 @@ func (state SubexStoreEndState) accepting(aux auxiliaryState) []OutputStack {
 
 // A part of an output literal, either an Atom or a slot from which to load
 type OutputContent interface {
-	// Given the current store, return the []Atom produced by the TransducerOutput
-	build(Store) walk.ValueList
+	// Given the current store, return the ValueList produced by the TransducerOutput
+	buildValues(Store) walk.ValueList
+	// Given the current store, return the RuneList produced by the TransducerOutput
+	buildRunes(Store) walk.RuneList
 }
 
-// An OutputContent which is just an Atom literal
-type OutputAtomLiteral struct {
-	atom walk.Value
+// An OutputContent which is just a Value literal
+type OutputValueLiteral struct {
+	value walk.Value
 }
-func (replacement OutputAtomLiteral) build(store Store) walk.ValueList {
-	return walk.ValueList{replacement.atom}
+func (replacement OutputValueLiteral) buildValues(store Store) walk.ValueList {
+	return walk.ValueList{replacement.value}
+}
+func (replacement OutputValueLiteral) buildRunes(store Store) walk.RuneList {
+	// TODO: serialise to JSON
+	panic("Unimplemented!")
+}
+
+// An OutputContent which is just a rune literal
+type OutputRuneLiteral struct {
+	rune walk.StringRuneAtom
+}
+func (replacement OutputRuneLiteral) buildValues(store Store) walk.ValueList {
+	// TODO: Try to deserialise
+	panic("Unimplemented!")
+}
+func (replacement OutputRuneLiteral) buildRunes(store Store) walk.RuneList {
+	return walk.RuneList {replacement.rune}
 }
 
 // An OutputContent which is a slot that is loaded from
 type OutputLoad struct {
 	slot int
 }
-func (replacement OutputLoad) build(store Store) walk.ValueList {
+func (replacement OutputLoad) buildValues(store Store) walk.ValueList {
 	values, isValues := store[replacement.slot].(walk.ValueList)
 	if !isValues {
 		panic("Tried to output non-values list")
 	}
 	return values
 }
+func (replacement OutputLoad) buildRunes(store Store) walk.RuneList {
+	runes, isRunes := store[replacement.slot].(walk.RuneList)
+	if !isRunes {
+		panic("Tried to output non-runes as runes")
+	}
+	return runes
+}
 
 // Don't read in anything, just output the series of data and slots specified
 type SubexOutputState struct {
@@ -155,7 +180,7 @@ type SubexOutputState struct {
 func (state SubexOutputState) build(store Store) walk.ValueList {
 	var result walk.ValueList
 	for _, part := range state.content {
-		result = append(result, part.build(store)...)
+		result = append(result, part.buildValues(store)...)
 	}
 	return result
 }
-- 
cgit v1.2.3