diff --git a/examples/test.pf b/examples/test.pf index d811ea26..855aa39e 100644 --- a/examples/test.pf +++ b/examples/test.pf @@ -3,15 +3,17 @@ def foo(i int) : "foo" - -zort(t ... any?) : - foo + - troz(i, j, k int) : "troz" +newtype + +gorp = clone snippet + var +sn = gorp -- foo + T tuple = () U tuple = 1, 2, 3 V tuple = tuple(1) diff --git a/source/ast/ast.go b/source/ast/ast.go index 61c832e4..7ed430c6 100644 --- a/source/ast/ast.go +++ b/source/ast/ast.go @@ -378,6 +378,15 @@ func (rl *RuneLiteral) Children() []Node { return []Node{} } func (rl *RuneLiteral) GetToken() *token.Token { return &rl.Token } func (rl *RuneLiteral) String() string { return strconv.QuoteRune(rl.Value) } +type SnippetLiteral struct { + Token token.Token + Value string +} + +func (sl *SnippetLiteral) Children() []Node { return []Node{} } +func (sl *SnippetLiteral) GetToken() *token.Token { return &sl.Token } +func (sl *SnippetLiteral) String() string { return "---" + sl.Value } + type StringLiteral struct { Token token.Token Value string diff --git a/source/compiler/builtins.go b/source/compiler/builtins.go index faa750bd..06c346d0 100644 --- a/source/compiler/builtins.go +++ b/source/compiler/builtins.go @@ -27,6 +27,7 @@ var BUILTINS = map[string]functionAndReturnType{ "cast_to_map": {(*Compiler).btCastToMap, AltType(values.MAP)}, "cast_to_pair": {(*Compiler).btCastToPair, AltType(values.PAIR)}, "cast_to_set": {(*Compiler).btCastToSet, AltType(values.SET)}, + "cast_to_snippet": {(*Compiler).btCastToSnippet, AltType(values.SNIPPET)}, "cast_to_string": {(*Compiler).btCastToString, AltType(values.STRING)}, "codepoint": {(*Compiler).btCodepoint, AltType(values.INT)}, "divide_floats": {(*Compiler).btDivideFloats, AltType(values.ERROR, values.FLOAT)}, @@ -54,6 +55,7 @@ var BUILTINS = map[string]functionAndReturnType{ "len_list": {(*Compiler).btLenList, AltType(values.INT)}, "len_map": {(*Compiler).btLenMap, AltType(values.INT)}, "len_set": {(*Compiler).btLenSet, AltType(values.INT)}, + "len_snippet": {(*Compiler).btLenSnippet, AltType(values.INT)}, "len_string": {(*Compiler).btLenString, AltType(values.INT)}, "len_tuple": {(*Compiler).btLenTuple, AltType(values.INT)}, "list_with": {(*Compiler).btListWith, AltType(values.LIST)}, @@ -66,6 +68,7 @@ var BUILTINS = map[string]functionAndReturnType{ "make_map": {(*Compiler).btMakeMap, AltType(values.MAP)}, "make_pair": {(*Compiler).btMakePair, AltType(values.PAIR)}, "make_set": {(*Compiler).btMakeSet, AltType(values.SET)}, + "make_snippet": {(*Compiler).btMakeSnippet, AltType(values.SNIPPET)}, "map_with": {(*Compiler).btMapWith, AltType(values.MAP)}, "map_without": {(*Compiler).btMapWithout, AltType(values.MAP)}, "modulo_integers": {(*Compiler).btModuloIntegers, AltType(values.ERROR, values.INT)}, @@ -75,9 +78,7 @@ var BUILTINS = map[string]functionAndReturnType{ "multiply_integer_by_float": {(*Compiler).btMultiplyIntegerByFloat, AltType(values.FLOAT)}, "negate_float": {(*Compiler).btNegateFloat, AltType(values.FLOAT)}, "negate_integer": {(*Compiler).btNegateInteger, AltType(values.INT)}, - "post_html": {(*Compiler).btPostSpecialSnippet, AltType(values.SUCCESSFUL_VALUE, values.ERROR)}, "post_to_output": {(*Compiler).btPostToOutput, AltType(values.SUCCESSFUL_VALUE)}, - "post_sql": {(*Compiler).btPostSpecialSnippet, AltType(values.SUCCESSFUL_VALUE, values.ERROR)}, "post_to_terminal": {(*Compiler).btPostToTerminal, AltType(values.SUCCESSFUL_VALUE)}, "rune": {(*Compiler).btRune, AltType(values.RUNE)}, "string": {(*Compiler).btString, AltType(values.STRING)}, @@ -159,6 +160,10 @@ func (cp *Compiler) btCastToSet(tok *token.Token, dest uint32, args []uint32) { cp.Emit(vm.Cast, dest, args[0], uint32(values.SET)) } +func (cp *Compiler) btCastToSnippet(tok *token.Token, dest uint32, args []uint32) { + cp.Emit(vm.Cast, dest, args[0], uint32(values.SNIPPET)) +} + func (cp *Compiler) btCastToString(tok *token.Token, dest uint32, args []uint32) { cp.Emit(vm.Cast, dest, args[0], uint32(values.STRING)) } @@ -268,6 +273,10 @@ func (cp *Compiler) btLenSet(tok *token.Token, dest uint32, args []uint32) { cp.Emit(vm.LenS, dest, args[0]) } +func (cp *Compiler) btLenSnippet(tok *token.Token, dest uint32, args []uint32) { + cp.Emit(vm.LnSn, dest, args[0]) +} + func (cp *Compiler) btLenString(tok *token.Token, dest uint32, args []uint32) { cp.Emit(vm.Lens, dest, args[0]) } @@ -312,6 +321,10 @@ func (cp *Compiler) btMakePair(tok *token.Token, dest uint32, args []uint32) { cp.Emit(vm.Mkpr, dest, args[0], args[2]) } +func (cp *Compiler) btMakeSnippet(tok *token.Token, dest uint32, args []uint32) { + cp.Emit(vm.CoSn, dest, args[0], cp.reserveToken(tok)) +} + func (cp *Compiler) btMakeSet(tok *token.Token, dest uint32, args []uint32) { cp.Emit(vm.Mkst, dest, args[0], cp.reserveToken(tok)) } @@ -357,10 +370,6 @@ func (cp *Compiler) btPostToOutput(tok *token.Token, dest uint32, args []uint32) cp.Emit(vm.Asgm, dest, values.C_OK) } -func (cp *Compiler) btPostSpecialSnippet(tok *token.Token, dest uint32, args []uint32) { - cp.Emit(vm.Psnp, dest, args[0]) -} - func (cp *Compiler) btPostToTerminal(tok *token.Token, dest uint32, args []uint32) { cp.Emit(vm.Outt, args[0]) cp.Emit(vm.Asgm, dest, values.C_OK) diff --git a/source/compiler/compfcall.go b/source/compiler/compfcall.go index 13c4ad57..7e776096 100644 --- a/source/compiler/compfcall.go +++ b/source/compiler/compfcall.go @@ -373,8 +373,6 @@ func (cp *Compiler) generateBranch(b *bindle) AlternateType { } var TYPE_COMPARISONS = map[string]vm.Opcode{ - "snippet": vm.Qspt, - "snippet?": vm.Qspq, "any": vm.Qsng, "any?": vm.Qsnq, "struct": vm.Qstr, diff --git a/source/compiler/compiler.go b/source/compiler/compiler.go index eec83605..3ad80094 100644 --- a/source/compiler/compiler.go +++ b/source/compiler/compiler.go @@ -7,7 +7,6 @@ import ( "os" "reflect" "strconv" - "strings" "testing" "github.com/tim-hardcastle/Pipefish/source/ast" @@ -81,8 +80,8 @@ type CommonCompilerBindle struct { func NewCommonCompilerBindle() *CommonCompilerBindle { newBindle := &CommonCompilerBindle{ SharedTypenameToTypeList: map[string]AlternateType{ - "any": AltType(values.INT, values.BOOL, values.STRING, values.RUNE, values.TYPE, values.FUNC, values.PAIR, values.LIST, values.MAP, values.SET, values.LABEL), - "any?": AltType(values.NULL, values.INT, values.BOOL, values.STRING, values.RUNE, values.FLOAT, values.TYPE, values.FUNC, values.PAIR, values.LIST, values.MAP, values.SET, values.LABEL), + "any": AltType(values.INT, values.BOOL, values.STRING, values.RUNE, values.TYPE, values.FUNC, values.PAIR, values.LIST, values.MAP, values.SET, values.LABEL, values.SNIPPET), + "any?": AltType(values.NULL, values.INT, values.BOOL, values.STRING, values.RUNE, values.FLOAT, values.TYPE, values.FUNC, values.PAIR, values.LIST, values.MAP, values.SET, values.LABEL, values.SNIPPET), }, AnyTypeScheme: AlternateType{}, AnyTuple: AlternateType{}, @@ -396,6 +395,7 @@ NodeTypeSwitch: // Tuples, ditto. // Strings, ditto. // Pairs, by integers. + // Snippets, by integers. // Names of enum types, by integers. Query, add slice too? // Maps, by any value we can Compare with another value. // Structs, by a label, preferably an appropriate one. @@ -459,6 +459,17 @@ NodeTypeSwitch: } rtnTypes = cp.GetAlternateTypeFromTypeName("any?").Union(AltType(values.ERROR)) } + if containerType.isOnlyCloneOf(cp.Vm, values.SNIPPET) { + if indexType.isOnlyCloneOf(cp.Vm, values.INT) { + cp.put(vm.IxSn, container, index, errTok) + break + } + if indexType.cannotBeACloneOf(cp.Vm, values.INT) { + cp.P.Throw("comp/index/snippet", node.GetToken()) + break + } + rtnTypes = cp.GetAlternateTypeFromTypeName("any?").Union(AltType(values.ERROR)) + } if containerType.isOnly(values.TYPE) { if indexType.isOnlyCloneOf(cp.Vm, values.INT) { cp.put(vm.Idxt, container, index, errTok) @@ -912,6 +923,11 @@ NodeTypeSwitch: cp.Reserve(values.RUNE, node.Value, node.GetToken()) rtnTypes, rtnConst = AltType(values.RUNE), true break + case *ast.SnippetLiteral: + snF := cp.reserveSnippetFactory(env, node, ctxt) + cp.put(vm.MkSn, snF) + rtnTypes, rtnConst = AltType(values.SNIPPET), false + break case *ast.StringLiteral: cp.Reserve(values.STRING, node.Value, node.GetToken()) rtnTypes, rtnConst = AltType(values.STRING), true @@ -920,18 +936,6 @@ NodeTypeSwitch: panic("This is used only in the vmmaker and should never be compiled.") case *ast.SuffixExpression: resolvingCompiler := cp.getResolvingCompiler(node, node.Namespace, ac) - if node.GetToken().Type == token.EMDASH { - switch t := node.Args[0].(type) { - case *ast.TypeLiteral: - snF := cp.reserveSnippetFactory(t.Value, env, node, ctxt) - cp.put(vm.MkSn, snF) - rtnTypes, rtnConst = cp.TypeNameToTypeScheme[t.Value], false - break NodeTypeSwitch - default: - cp.P.Throw("comp/snippet/type", node.Args[0].GetToken()) // There is no reason why this should be a first-class value, that would just be confusing. Hence the error. - break NodeTypeSwitch - } - } if node.GetToken().Type == token.DOTDOTDOT { if len(node.Args) != 1 { cp.P.Throw("comp/splat/args", node.GetToken()) @@ -1859,17 +1863,10 @@ func (cp *Compiler) getLambdaStart() uint32 { // A function for making snippet factories. -func (cp *Compiler) reserveSnippetFactory(t string, env *Environment, fnNode *ast.SuffixExpression, ctxt Context) uint32 { - cp.Cm("Reserving snippet factory.", &fnNode.Token) - snF := &vm.SnippetFactory{SnippetType: cp.ConcreteTypeNow(t), SourceString: fnNode.Token.Literal} - csk := vm.VANILLA_SNIPPET - switch { - case t == "SQL": - csk = vm.SQL_SNIPPET - case t == "HTML": - csk = vm.HTML_SNIPPET - } - snF.Bindle = cp.compileSnippet(fnNode.GetToken(), env, csk, snF.SourceString, ctxt) +func (cp *Compiler) reserveSnippetFactory(env *Environment, node *ast.SnippetLiteral, ctxt Context) uint32 { + cp.Cm("Reserving snippet factory.", &node.Token) + snF := &vm.SnippetFactory{} + snF.Bindle = cp.compileSnippet(node.GetToken(), env, node.Token.Literal, ctxt) cp.Vm.SnippetFactories = append(cp.Vm.SnippetFactories, snF) return uint32(len(cp.Vm.SnippetFactories) - 1) } @@ -2155,79 +2152,35 @@ func (cp *Compiler) compileMappingOrFilter(lhsTypes AlternateType, lhsConst bool return AltType(values.LIST), lhsConst && rhsConst } -func (cp *Compiler) compileSnippet(tok *token.Token, newEnv *Environment, csk vm.CompiledSnippetKind, sText string, ctxt Context) *vm.SnippetBindle { +func (cp *Compiler) compileSnippet(tok *token.Token, newEnv *Environment, sText string, ctxt Context) *values.SnippetBindle { cp.Cm("Compile snippet", tok) - bindle := vm.SnippetBindle{Csk: csk} + bindle := values.SnippetBindle{} bits, ok := text.GetTextWithBarsAsList(sText) if !ok { cp.P.Throw("comp/snippet/form", tok) return &bindle } - var buf strings.Builder bindle.CodeLoc = cp.CodeTop() - c := 0 + if len(bits) > 0 && len(bits[0]) > 0 && bits[0][0] == '|' { + cp.Reserve(values.STRING, "", tok) + bindle.ValueLocs = append(bindle.ValueLocs, cp.That()) + } for _, bit := range bits { if len(bit) > 0 && bit[0] == '|' { node := cp.P.ParseLine(tok.Source, bit[1:len(bit)-1]) newContext := ctxt newContext.Env = newEnv - types, cst := cp.CompileNode(node, newContext) + types, _ := cp.CompileNode(node, newContext) val := cp.That() - - // Special sauce for the SQL snippets. - if types.isOnly(values.TYPE) && cst && csk == vm.SQL_SNIPPET { - typeNumbers := cp.Vm.Mem[cp.That()].V.(values.AbstractType).Types - if len(typeNumbers) == 1 && cp.Vm.ConcreteTypeInfo[typeNumbers[0]].IsStruct() { - sig, ok := cp.Vm.GetSqlSig(typeNumbers[0]) - if !ok { - cp.P.Throw("comp/snippet/sig", tok, cp.Vm.DescribeType(typeNumbers[0], vm.LITERAL)) - } - buf.WriteString(sig) - continue // ... the for loop. - } - } - // If it's a tuple of fixed length, we can split it and inject the values separately. - // If it's of indeterminate length then we need to throw an error. - numberOfInjectionSites := 1 // Default, if the type is any. if types.Contains(values.TUPLE) { - lengths := lengths(types) - if lengths.Contains(-1) || len(lengths) > 1 { // ... then we can't infer the length and must throw an error. - cp.P.Throw("comp/snippet/tuple", tok) - } - numberOfInjectionSites, _ = lengths.GetArbitraryElement() - for i := 0; i < numberOfInjectionSites; i++ { - cp.put(vm.IxTn, val, uint32(i)) - bindle.ValueLocs = append(bindle.ValueLocs, cp.That()) - } - } else { // We have a any element so we add it to the injectable values. - bindle.ValueLocs = append(bindle.ValueLocs, val) - } - sep := "" - for i := 0; i < numberOfInjectionSites; i++ { - buf.WriteString(sep) - switch csk { - case vm.SQL_SNIPPET: - buf.WriteString("$") - c++ - buf.WriteString(strconv.Itoa(c)) // The injection sites in SQL go $1 , $2 , $3 ... - case vm.HTML_SNIPPET: - buf.WriteString("{{index .Data ") - buf.WriteString(strconv.Itoa(c)) // The injection sites in HTML go {{index .Data 0}} , {{index .Data 1}} ... - buf.WriteString("}}") - c++ - case vm.VANILLA_SNIPPET: - buf.WriteString("%v") // We produce a Go format string. - } - sep = ", " + cp.P.Throw("comp/snippet/tuple", tok) } + bindle.ValueLocs = append(bindle.ValueLocs, val) } else { cp.Reserve(values.STRING, bit, tok) bindle.ValueLocs = append(bindle.ValueLocs, cp.That()) - buf.WriteString(bit) } } - cp.Reserve(values.STRING, buf.String(), tok) - bindle.ObjectStringLoc = cp.That() return &bindle } diff --git a/source/compiler/compiler_test.go b/source/compiler/compiler_test.go index 1d4d48c5..eff4240e 100644 --- a/source/compiler/compiler_test.go +++ b/source/compiler/compiler_test.go @@ -379,8 +379,7 @@ func TestClones(t *testing.T) { } func TestSnippet(t *testing.T) { tests := []test_helper.TestItem{ - {`makeSn 42`, `Foo with (text::"zort |x| troz", data::["zort ", 42, " troz"])`}, - {`post HTML --- zort |2 + 2| troz`, `OK`}, + } test_helper.RunTest(t, "snippets_test.pf", tests, test_helper.TestValues) } diff --git a/source/compiler/test-files/snippets_test.pf b/source/compiler/test-files/snippets_test.pf index ff23aed2..e69de29b 100644 --- a/source/compiler/test-files/snippets_test.pf +++ b/source/compiler/test-files/snippets_test.pf @@ -1,9 +0,0 @@ -newtype - -Foo = snippet - -def - -makeSn(x int) : - Foo --- zort |x| troz - diff --git a/source/compiler/typeschemes.go b/source/compiler/typeschemes.go index 7e53ed1c..3f373966 100644 --- a/source/compiler/typeschemes.go +++ b/source/compiler/typeschemes.go @@ -41,6 +41,7 @@ var INITIAL_TYPE_SCHEMES = map[string]AlternateType{ "map": AltType(values.MAP), "set": AltType(values.SET), "label": AltType(values.LABEL), + "snippet": AltType(values.SNIPPET), "func": AltType(values.FUNC), "int?": AltType(values.NULL, values.INT), "string?": AltType(values.NULL, values.STRING), @@ -53,6 +54,7 @@ var INITIAL_TYPE_SCHEMES = map[string]AlternateType{ "set?": AltType(values.NULL, values.SET), "label?": AltType(values.NULL, values.LABEL), "func?": AltType(values.NULL, values.FUNC), + "snippet?": AltType(values.NULL, values.SNIPPET), "null": AltType(values.NULL), } diff --git a/source/err/errorfile.go b/source/err/errorfile.go index 17f19e25..f85f9f67 100644 --- a/source/err/errorfile.go +++ b/source/err/errorfile.go @@ -590,7 +590,16 @@ var ErrorCreatorMap = map[string]ErrorCreator{ return "pair index is not of type" + emph("int") }, Explanation: func(errors Errors, pos int, tok *token.Token, args ...any) string { - return "A pair may be indexed only by the integers " + emph("0") + " and " + emph("1") + return "A pair may be indexed only by the integers " + emph("0") + " and " + emph("1") + }, + }, + + "comp/index/snippet": { + Message: func(tok *token.Token, args ...any) string { + return "snippet index is not of type" + emph("int") + }, + Explanation: func(errors Errors, pos int, tok *token.Token, args ...any) string { + return "A snippet may be indexed only by integers." }, }, @@ -2958,6 +2967,15 @@ var ErrorCreatorMap = map[string]ErrorCreator{ }, }, + "vm/index/s": { + Message: func(tok *token.Token, args ...any) string { + return fmt.Sprintf("index %v of snippet is out of range", emph(args[0])) + }, + Explanation: func(errors Errors, pos int, tok *token.Token, args ...any) string { + return fmt.Sprintf("The greatest value the index of a snippet can have is the length of the thing being indexed.") + }, + }, + "vm/label/exist": { Message: func(tok *token.Token, args ...any) string { return fmt.Sprintf("can't convert string %v to a label", emphStr(args[0])) diff --git a/source/hub/repl.go b/source/hub/repl.go index 7fc72995..697d807e 100644 --- a/source/hub/repl.go +++ b/source/hub/repl.go @@ -10,7 +10,7 @@ import ( func StartHub(hub *Hub, in io.Reader, out io.Writer) { var rline *readline.Instance - colonOrEmdash, _ := regexp.Compile(`.*[\w\s]*(:|---)[\s]*$`) + colonOrEmdash, _ := regexp.Compile(`.*[\w\s]*(:|--)[\s]*$`) for { // The hub's CurrentForm setting allows it to ask for information from the user instead of diff --git a/source/initializer/gogen.go b/source/initializer/gogen.go index 2e5581a0..0a6de1da 100644 --- a/source/initializer/gogen.go +++ b/source/initializer/gogen.go @@ -180,6 +180,7 @@ var goTypes = map[string]string{ "map": "map[any]any", "pair": "[2]any", "set": "map[any]struct{}", + "snippet": "!", "tuple": "[]any", "type": "!", } diff --git a/source/initializer/initializer.go b/source/initializer/initializer.go index 72a1f937..9d5088b5 100644 --- a/source/initializer/initializer.go +++ b/source/initializer/initializer.go @@ -222,12 +222,6 @@ func (iz *initializer) parseEverything(scriptFilepath, sourcecode string) { return } - iz.cmI("Creating snippet types, part 1.") - iz.createSnippetsPart1() - if iz.ErrorsExist() { - return - } - iz.cmI("Adding types to parser.") iz.addTypesToParser() if iz.ErrorsExist() { @@ -264,12 +258,6 @@ func (iz *initializer) parseEverything(scriptFilepath, sourcecode string) { return } - iz.cmI("Creating snippet types, part 2.") - iz.createSnippetTypesPart2() - if iz.ErrorsExist() { - return - } - // We want to ensure that no public type (whether a struct or abstract type) contains a private type. iz.cmI("Checking types for consistency of encapsulation.") iz.checkTypesForConsistency() @@ -802,7 +790,7 @@ func (iz *initializer) createClones() { typeToClone := typeToken.Literal parentTypeNo, ok := parser.ClonableTypes[typeToClone] if !ok { - iz.Throw("init/clone/type", &typeToken) + iz.Throw("init/clone/type", &typeToken, typeToClone) return } abType := typeToClone + "like" @@ -989,19 +977,6 @@ func (iz *initializer) makeCloneFunction(fnName string, sig ast.StringSig, built } } -// Phase 1G of compilation. We do a little work to create the snippet types, leaving the rest for later. -func (iz *initializer) createSnippetsPart1() { - for _, v := range iz.TokenizedDeclarations[snippetDeclaration] { - v.ToStart() - // Note that the first tokens should already have been validated by the createTypeSuffixes method as IDENT. - tok1 := v.NextToken() - name := tok1.Literal - iz.Snippets = append(iz.Snippets, name) - iz.p.AllFunctionIdents.Add(name) - iz.p.Functions.Add(name) - } -} - // Phase 1H of compilation. We declare all the types as suffixes for all the user-defined types. func (iz *initializer) addTypesToParser() { /// TODO --- some of this seems to replicate boilerplate in the parsing functions, so you should be able to remove the latter. for kindOfType := enumDeclaration; kindOfType <= cloneDeclaration; kindOfType++ { @@ -1255,36 +1230,6 @@ func (iz *initializer) addFieldsToStructs() { } } -// Phase 1N of compilation. We create a little more of the snippet types, though we won't finish up until -// `parseEverythingElse`. -func (iz *initializer) createSnippetTypesPart2() { - abTypes := []values.AbstractType{{[]values.ValueType{values.STRING}, DUMMY}, {[]values.ValueType{values.MAP}, DUMMY}} - for i, name := range iz.Snippets { - sig := ast.StringSig{ast.NameTypenamePair{VarName: "text", VarType: "string"}, ast.NameTypenamePair{VarName: "data", VarType: "list"}} - typeNo := values.ValueType(len(iz.cp.Vm.ConcreteTypeInfo)) - iz.TokenizedDeclarations[snippetDeclaration][i].ToStart() - decTok := iz.TokenizedDeclarations[snippetDeclaration][i].NextToken() - typeInfo, typeExists := iz.getDeclaration(decSTRUCT, &decTok, DUMMY) - if typeExists { // We see if it's already been declared. - typeNo = typeInfo.(structInfo).structNumber - typeInfo := iz.cp.Vm.ConcreteTypeInfo[typeNo].(vm.StructType) - typeInfo.Path = iz.p.NamespacePath - iz.cp.Vm.ConcreteTypeInfo[typeNo] = typeInfo - } else { - iz.cp.Vm.ConcreteTypeInfo = append(iz.cp.Vm.ConcreteTypeInfo, vm.StructType{Name: name, Path: iz.p.NamespacePath, Snippet: true, - Private: iz.IsPrivate(int(snippetDeclaration), i), AbstractStructFields: abTypes, IsMI: settings.MandatoryImportSet().Contains(decTok.Source)}) - iz.addStructLabelsToVm(name, typeNo, sig, &decTok) - iz.cp.Common.CodeGeneratingTypes.Add(typeNo) - } - iz.AddType(name, "snippet", typeNo) - // The parser needs to know about it too. - iz.p.Functions.Add(name) - fn := &ast.PrsrFunction{Sig: iz.p.MakeAbstractSigFromStringSig(sig), NameSig: sig, Body: &ast.BuiltInExpression{Name: name, Token: decTok}, Tok: &decTok} - iz.p.FunctionTable.Add(iz.p, name, fn) - iz.fnIndex[fnSource{snippetDeclaration, i}] = fn - } -} - // Function auxiliary to the above, for adding struct labels to the VM. // // TODO --- this appears to only be used by snippets and not by ordinary structs. Find out what they use and use that. @@ -1526,13 +1471,6 @@ func (iz *initializer) compileConstructors() { iz.fnIndex[fnSource{structDeclaration, i}].Number = iz.addToBuiltins(sig, name, altType(typeNo), iz.IsPrivate(int(structDeclaration), i), node.GetToken()) iz.fnIndex[fnSource{structDeclaration, i}].Compiler = iz.cp } - // Snippets. TODO --- should this even exist? It seems like all it adds is that you could make ill-formed snippets if you chose. - sig := ast.StringSig{ast.NameTypenamePair{VarName: "text", VarType: "string"}, ast.NameTypenamePair{VarName: "data", VarType: "list"}} - for i, name := range iz.Snippets { - typeNo := iz.cp.ConcreteTypeNow(name) - iz.fnIndex[fnSource{snippetDeclaration, i}].Number = iz.addToBuiltins(sig, name, altType(typeNo), iz.IsPrivate(int(snippetDeclaration), i), iz.ParsedDeclarations[snippetDeclaration][i].GetToken()) - iz.fnIndex[fnSource{snippetDeclaration, i}].Compiler = iz.cp - } // Clones for i, dec := range iz.TokenizedDeclarations[cloneDeclaration] { dec.ToStart() @@ -2187,9 +2125,6 @@ func (iz *initializer) AddType(name, supertype string, typeNo values.ValueType) iz.p.TypeMap[name+"?"] = values.MakeAbstractType(values.NULL, typeNo) iz.unserializableTypes.Add(name).Add(name+"?") types := []string{supertype} - if supertype == "snippet" { - types = append(types, "struct") - } iz.cp.Common.AddTypeNumberToSharedAlternateTypes(typeNo, types...) types = append(types, "any") for _, sT := range types { @@ -2326,7 +2261,7 @@ func (iz *initializer) addTokenizedDeclaration(decType declarationType, line *to iz.TokenizedDeclarations[decType] = append(iz.TokenizedDeclarations[decType], line) } -var typeMap = map[string]declarationType{"struct": structDeclaration, "enum": enumDeclaration, "snippet": snippetDeclaration, +var typeMap = map[string]declarationType{"struct": structDeclaration, "enum": enumDeclaration, "abstract": abstractDeclaration, "clone": cloneDeclaration, "interface": interfaceDeclaration} // Types and functions to help with housekeeping. The initializer stores the declarations of types and functions diff --git a/source/initializer/initializer_test.go b/source/initializer/initializer_test.go index 7575efae..2f69c36e 100644 --- a/source/initializer/initializer_test.go +++ b/source/initializer/initializer_test.go @@ -105,8 +105,7 @@ func TestClones(t *testing.T) { } func TestSnippet(t *testing.T) { tests := []test_helper.TestItem{ - {`makeSn 42`, `Foo with (text::"zort |x| troz", data::["zort ", 42, " troz"])`}, - {`post HTML --- zort |2 + 2| troz`, `OK`}, + } test_helper.RunTest(t, "snippets_test.pf", tests, test_helper.TestValues) } diff --git a/source/initializer/rsc-pf/builtins.pf b/source/initializer/rsc-pf/builtins.pf index c7261813..0a8d1a10 100644 --- a/source/initializer/rsc-pf/builtins.pf +++ b/source/initializer/rsc-pf/builtins.pf @@ -72,7 +72,8 @@ last(x tuple) -> any? : builtin "last_in_tuple" len(x listlike) -> int : builtin "len_list" len(x maplike) -> int : builtin "len_map" len(x setlike) -> int : builtin "len_set" -len(x string) -> int : builtin "len_string" +len(x snippetlike) -> int : builtin "len_snippet" +len(x stringlike) -> int : builtin "len_string" len(x tuple) -> int : builtin "len_tuple" list(x listlike) -> list : builtin "cast_to_list" literal(x any?) -> string : builtin "literal" @@ -85,7 +86,10 @@ rune(x int) -> rune : builtin "rune" set(x setlike) -> set : builtin "cast_to_set" set(x ... any?) -> set : makeSet(x) makeSet(x tuple) -> set : builtin "make_set" -// string(x stringlike) -> string : builtin "cast_to_string" +// string(x stringlike) -> string : builtin "cast_to_string" +snippet(x ... any?) -> snippet : snippetmaker(x) +snippetmaker(x tuple) : builtin "make_snippet" +snippet(x snippetlike) -> snippet : builtin "cast_to_snippet" string(x any?) -> string : builtin "string" tuple(x tuple) -> tuple : builtin "tuple_of_tuple" tuple(x ... any?) -> tuple : builtin "tuple_of_varargs" diff --git a/source/initializer/rsc-pf/world.pf b/source/initializer/rsc-pf/world.pf index 0ff78be6..8114cbe0 100644 --- a/source/initializer/rsc-pf/world.pf +++ b/source/initializer/rsc-pf/world.pf @@ -18,9 +18,6 @@ Random = struct(params int) RandomSeed = struct() Terminal = struct() -SQL = snippet -HTML = snippet - cmd posttooutputmaker(x any) : builtin "post_to_output" @@ -134,12 +131,12 @@ put (s string) into (fileAccess File) : delete (fileAccess File) : goDeleteFile(fileAccess[filepath]) -post(x SQL) : builtin "post_sql" -put(x SQL) : builtin "post_sql" -delete(x SQL) : builtin "post_sql" -get(x ref) from (y SQL) : builtin "get_from_SQL" +// post(x SQL) : builtin "post_sql" +// put(x SQL) : builtin "post_sql" +// delete(x SQL) : builtin "post_sql" +// get(x ref) from (y SQL) : builtin "get_from_SQL" -post(x HTML) : builtin "post_html" +// post(x HTML) : builtin "post_html" // This is strictly speaking not part of the "world" at all but it has to go somewhere and // it fights with the builtin definitions. diff --git a/source/initializer/rsc-pf/worldlite.pf b/source/initializer/rsc-pf/worldlite.pf index 53885fd0..fe01cae2 100644 --- a/source/initializer/rsc-pf/worldlite.pf +++ b/source/initializer/rsc-pf/worldlite.pf @@ -7,9 +7,6 @@ newtype Input = struct(prompt string) Output = struct() Terminal = struct() -HTML = snippet - -Foo = enum FOO, BAR cmd diff --git a/source/parser/getters.go b/source/parser/getters.go index f467d5b2..854660de 100644 --- a/source/parser/getters.go +++ b/source/parser/getters.go @@ -487,7 +487,7 @@ func (p *Parser) isPositionallyFunctional() bool { } if p.Functions.Contains(p.curToken.Literal) && p.TypeExists(p.curToken.Literal) { if p.peekToken.Type == token.EMDASH { - return false + return true } if literalsAndLParen.Contains(p.peekToken.Type) { return true diff --git a/source/parser/parser.go b/source/parser/parser.go index 7c82c2b5..1f578274 100644 --- a/source/parser/parser.go +++ b/source/parser/parser.go @@ -198,6 +198,8 @@ func (p *Parser) parseExpression(precedence int) ast.Node { leftExp = p.parseContinue() case token.ELSE: leftExp = p.parseElse() + case token.EMDASH : + leftExp = p.parseSnippetLiteral() case token.FALSE: leftExp = p.parseBooleanLiteral() case token.FLOAT: @@ -298,8 +300,18 @@ func (p *Parser) parseExpression(precedence int) ast.Node { } } + if p.peekToken.Type == token.EMDASH { + right := &ast.SnippetLiteral{p.peekToken, p.peekToken.Literal} + tok := token.Token{token.COMMA, ",", p.peekToken.Line, p.peekToken.ChStart, + p.peekToken.ChEnd, p.peekToken.Source} + children := []ast.Node{leftExp, &ast.Bling{tok, ",", []string{}}, right} + result := &ast.InfixExpression{tok, ",", children, []string{}} + p.NextToken() + return result + } + for precedence < p.peekPrecedence() { - for resolvingParser.Suffixes.Contains(p.peekToken.Literal) || resolvingParser.Endfixes.Contains(p.peekToken.Literal) || p.peekToken.Type == token.EMDASH || p.peekToken.Type == token.DOTDOTDOT { + for resolvingParser.Suffixes.Contains(p.peekToken.Literal) || resolvingParser.Endfixes.Contains(p.peekToken.Literal) || p.peekToken.Type == token.DOTDOTDOT { if p.curToken.Type == token.NOT || p.curToken.Type == token.IDENT && p.curToken.Literal == "-" || p.curToken.Type == token.ELSE { p.Throw("parse/before/b", &p.curToken, &p.peekToken) return nil @@ -844,6 +856,10 @@ func (p *Parser) recursivelyDesugarAst(exp ast.Node) ast.Node { return exp } +func (p *Parser) parseSnippetLiteral() ast.Node { + return &ast.SnippetLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + func (p *Parser) parseStringLiteral() ast.Node { return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} } diff --git a/source/parser/precedence.go b/source/parser/precedence.go index 027c3705..04206d50 100644 --- a/source/parser/precedence.go +++ b/source/parser/precedence.go @@ -93,7 +93,6 @@ var precedences = map[token.TokenType]int{ // SUM // PRODUCT token.DOTDOTDOT: FSUFFIX, - token.EMDASH: FSUFFIX, // MINUS (as prefix) token.LBRACK: INDEX, // BELOW_NAMESPACE diff --git a/source/parser/types.go b/source/parser/types.go index 66824d78..fc75f400 100644 --- a/source/parser/types.go +++ b/source/parser/types.go @@ -23,6 +23,7 @@ var baseTypes = map[string]values.ValueType{ "label": values.LABEL, "func": values.FUNC, "null": values.NULL, + "snippet": values.SNIPPET, } func NewCommonTypeMap() TypeSys { @@ -38,7 +39,7 @@ func NewCommonTypeMap() TypeSys { singleAndNull := anyType.Insert(values.NULL) result["any"] = anyType result["any?"] = singleAndNull - for _, abType := range []string{"enum", "struct", "snippet"} { + for _, abType := range []string{"enum", "struct"} { result[abType] = values.MakeAbstractType() result[abType+"?"] = values.MakeAbstractType(values.NULL) } @@ -54,11 +55,11 @@ func NewCommonTypeMap() TypeSys { } var BaseTypesOtherThanNull = []string{"int", "float", "bool", "string", "rune", "error", "type", "list", "label", - "pair", "set", "map", "func", "struct", "label"} + "pair", "set", "map", "func", "struct", "snippet"} -var ClonableTypes = map[string]values.ValueType{"float": values.FLOAT, "int": values.INT, "list": values.LIST, "map": values.MAP, "pair": values.PAIR, "rune": values.RUNE, "set": values.SET, "string": values.STRING} +var ClonableTypes = map[string]values.ValueType{"float": values.FLOAT, "int": values.INT, "list": values.LIST, "map": values.MAP, "pair": values.PAIR, "rune": values.RUNE, "set": values.SET, "snippet": values.SNIPPET, "string": values.STRING} -var AbstractTypesOtherThanSingle = []string{"struct", "snippet", "enum"} +var AbstractTypesOtherThanSingle = []string{"struct", "enum"} func (p *Parser) IsMoreSpecific(sigA, sigB ast.AbstractSig) (result bool, ok bool) { if len(sigB) == 0 { diff --git a/source/pf/service.go b/source/pf/service.go index fcccf5f1..731b16dd 100644 --- a/source/pf/service.go +++ b/source/pf/service.go @@ -128,6 +128,7 @@ const ( MAP Type = values.MAP SET Type = values.SET LABEL Type = values.LABEL + SNIPPET Type = values.SNIPPET ) // Makes an InHandler which does nothing but get a string from terminal diff --git a/source/settings/settings.go b/source/settings/settings.go index 4032bd6d..535a9523 100644 --- a/source/settings/settings.go +++ b/source/settings/settings.go @@ -45,7 +45,7 @@ const ( SHOW_GOLANG = false SHOW_API_SERIALIZATION = false SHOW_EXTERNAL_STUBS = false - SHOW_TESTS = true // Says whether the tests should say what is being tested, useful if one of them crashes and we don't know which. + SHOW_TESTS = false // Says whether the tests should say what is being tested, useful if one of them crashes and we don't know which. ) var PipefishHomeDirectory string diff --git a/source/text/text.go b/source/text/text.go index a502aedd..3a5e924f 100644 --- a/source/text/text.go +++ b/source/text/text.go @@ -319,7 +319,9 @@ func GetTextWithBarsAsList(text string) ([]string, bool) { if exp { return nil, false } - strList = append(strList, word) + if word != "" { + strList = append(strList, word) + } return strList, true } diff --git a/source/token/token.go b/source/token/token.go index 0ef0690e..09fab3e4 100644 --- a/source/token/token.go +++ b/source/token/token.go @@ -129,7 +129,7 @@ var keywords = map[string]TokenType{ "?>": FILTER, "==": EQ, "!=": NOT_EQ, - "---": EMDASH, + "--": EMDASH, // False and true. "true": TRUE, diff --git a/source/values/values.go b/source/values/values.go index 6e2c4691..17deaf37 100644 --- a/source/values/values.go +++ b/source/values/values.go @@ -12,7 +12,6 @@ const ( // Cross-reference with typeNames in BlankVm() UNDEFINED_TYPE ValueType = iota // For debugging purposes, it is useful to have the zero value be something it should never actually be. INT_ARRAY // V is an array of Golang uint32. TODO --- its only current use is a three-value enum. - SNIPPET_DATA // V is SnippetData. This is attached as an invisible field to a snippet struct to carry around things that can be deduced at compile time. THUNK // V is a ThunkValue which contains the address to call to evaluate the thunk and the memory location where the result ends up. CREATED_THUNK_OR_CONST // Returned by the compiler in the TypeScheme when we compile a thunk. COMPILE_TIME_ERROR // For when we have to return a type, but what we have is a compile time error. @@ -37,11 +36,12 @@ const ( // Cross-reference with typeNames in BlankVm() FLOAT // V : float TYPE // V : abstractType FUNC // V : vm.Lambda - PAIR // V : []values.Value + PAIR // V : []values.Value // TODO --- this should be [2]values.Value just for neatness. LIST // V : vector.Vector MAP // V : *values.Map SET // V : values.Set LABEL // V : int + SNIPPET // V : Snippet struct{Data []values.Value, Bindle *SnippetBindle} FIRST_DEFINED_TYPE // I.e the first of the enums. ) @@ -52,6 +52,17 @@ type Value struct { V any } +type Snippet struct { + Data []Value + Bindle *SnippetBindle +} + +// A grouping of all the things a snippet from a given snippet factory have in common. +type SnippetBindle struct { + CodeLoc uint32 // Where to find the code to compute the object string and the values. + ValueLocs []uint32 // The locations where we put the computed values to inject into SQL or HTML snippets. +} + func (v Value) compare(w Value) bool { // To implement the set and hash structures. It doesn't really matter which order if v.T < w.T { // these things are in, so long as there is one. return true diff --git a/source/vm/descriptors.go b/source/vm/descriptors.go index f5f4aafa..b6372d3f 100644 --- a/source/vm/descriptors.go +++ b/source/vm/descriptors.go @@ -56,7 +56,7 @@ func (vm *Vm) toString(v values.Value, flavor descriptionFlavor) string { typeInfo := vm.ConcreteTypeInfo[v.T] if typeInfo.IsStruct() { var buf strings.Builder - buf.WriteString(vm.ConcreteTypeInfo[v.T].GetName(flavor)) + buf.WriteString(typeInfo.GetName(flavor)) buf.WriteString(" with (") var sep string vals := v.V.([]values.Value) @@ -70,17 +70,29 @@ func (vm *Vm) toString(v values.Value, flavor descriptionFlavor) string { if typeInfo.IsEnum() { var buf strings.Builder if flavor == LITERAL { - buf.WriteString(vm.ConcreteTypeInfo[v.T].(EnumType).Path) + buf.WriteString(typeInfo.(EnumType).Path) } - buf.WriteString(vm.ConcreteTypeInfo[v.T].(EnumType).ElementNames[v.V.(int)]) + buf.WriteString(typeInfo.(EnumType).ElementNames[v.V.(int)]) return buf.String() } if typeInfo.IsClone() { + if typeInfo.(CloneType).Parent == values.SNIPPET { + var buf strings.Builder + buf.WriteString(typeInfo.GetName(flavor)) + buf.WriteByte('(') + var sep string + for _, k := range v.V.(values.Snippet).Data { + fmt.Fprintf(&buf, "%s%s", sep, vm.StringifyValue(k, flavor)) + sep = ", " + } + buf.WriteByte(')') + return buf.String() + } var buf strings.Builder - buf.WriteString(vm.ConcreteTypeInfo[v.T].GetName(flavor)) + buf.WriteString(typeInfo.GetName(flavor)) buf.WriteString("(") - buf.WriteString(vm.StringifyValue(values.Value{vm.ConcreteTypeInfo[v.T].(CloneType).Parent, v.V}, flavor)) + buf.WriteString(vm.StringifyValue(values.Value{typeInfo.(CloneType).Parent, v.V}, flavor)) buf.WriteByte(')') return buf.String() } @@ -163,6 +175,16 @@ func (vm *Vm) toString(v values.Value, flavor descriptionFlavor) string { }) buf.WriteByte(')') return buf.String() + case values.SNIPPET: + var buf strings.Builder + buf.WriteString("snippet(") + var sep string + for _, k := range v.V.(values.Snippet).Data { + fmt.Fprintf(&buf, "%s%s", sep, vm.StringifyValue(k, flavor)) + sep = ", " + } + buf.WriteByte(')') + return buf.String() case values.STRING: if flavor == DEFAULT { return v.V.(string) diff --git a/source/vm/operations.go b/source/vm/operations.go index a1014f49..191c6a8d 100644 --- a/source/vm/operations.go +++ b/source/vm/operations.go @@ -158,6 +158,7 @@ var OPERANDS = map[Opcode]opDescriptor{ Itkv: {"itgv", operands{dst, dst, mem}}, Itgv: {"itgv", operands{dst, mem}}, Itor: {"itor", operands{dst, mem}}, + IxSn: {"inxS", operands{dst, mem, mem}}, IxXx: {"ixXx", operands{dst, mem, mem, tok}}, Jmp: {"jmp", operands{loc}}, Jsr: {"jsr", operands{loc}}, @@ -171,6 +172,7 @@ var OPERANDS = map[Opcode]opDescriptor{ LenT: {"lenT", operands{dst, mem}}, Litx: {"litx", operands{dst, mem}}, List: {"list", operands{dst, mem}}, + LnSn: {"lnSn", operands{dst, mem}}, Log: {"log", operands{mem}}, Logn: {"logn", operands{}}, Logy: {"logy", operands{}}, @@ -190,7 +192,6 @@ var OPERANDS = map[Opcode]opDescriptor{ Orb: {"orb", operands{dst, mem, mem}}, Outp: {"outp", operands{mem}}, Outt: {"outt", operands{mem}}, - Psnp: {"psnp", operands{dst, mem}}, Qabt: {"qabt", operands{mem, tup, loc}}, Qfls: {"qfls", operands{mem, loc}}, Qitr: {"qitr", operands{mem, loc}}, @@ -203,8 +204,6 @@ var OPERANDS = map[Opcode]opDescriptor{ Qsat: {"qsat", operands{mem, loc}}, Qsng: {"qsng", operands{mem, loc}}, Qsnq: {"qsnq", operands{mem, loc}}, - Qspt: {"qspt", operands{mem, loc}}, - Qspq: {"qspq", operands{mem, loc}}, Qstr: {"qstr", operands{mem, loc}}, Qstq: {"qstq", operands{mem, loc}}, Qtpt: {"qtpt", operands{mem, num, tup, loc}}, @@ -285,6 +284,7 @@ const ( CalT // Specialized for tuple capture. Cast Casx + CoSn Diif Divf Divi @@ -325,6 +325,7 @@ const ( Itkv Itgv Itor + IxSn IxTn IxXx IxZl @@ -341,6 +342,7 @@ const ( LenT List Litx + LnSn Log Logn Logy @@ -349,8 +351,8 @@ const ( Mkit Mkmp Mkpr - Mkst MkSn + Mkst Mlfi Modi Mulf @@ -361,7 +363,6 @@ const ( Orb Outp Outt - Psnp Qabt Qfls Qitr diff --git a/source/vm/vm.go b/source/vm/vm.go index d5f95d6a..83690a09 100644 --- a/source/vm/vm.go +++ b/source/vm/vm.go @@ -3,7 +3,6 @@ package vm import ( "database/sql" "fmt" - "html/template" "os" "reflect" "strconv" @@ -84,27 +83,12 @@ type LambdaFactory struct { } // All the information we need to make a snippet at a particular point in the code. +// Currently contaains only the bindle but later will contain some secret sauce. type SnippetFactory struct { - SnippetType values.ValueType // The type of the snippet, adoy. - SourceString string // The plain text of the snippet before processing. - Bindle *SnippetBindle // Points to the structure defined below. + Bindle *values.SnippetBindle // Points to the structure defined below. } -// A grouping of all the things a snippet from a given snippet factory have in common. -type SnippetBindle struct { - Csk CompiledSnippetKind // An enum type saying whether it's uncompiled, an external service, SQL, or HTML. - CodeLoc uint32 // Where to find the code to compute the object string and the values. - ObjectStringLoc uint32 // Where to find the object string. - ValueLocs []uint32 // The locations where we put the computed values to inject into SQL or HTML snippets. -} - -type CompiledSnippetKind int -const ( - VANILLA_SNIPPET CompiledSnippetKind = iota - SQL_SNIPPET - HTML_SNIPPET -) // Container for the data we push when a function might be about to do recursion. type recursionData struct { @@ -121,10 +105,10 @@ type HTMLInjector struct { var CONSTANTS = []values.Value{values.UNDEF, values.FALSE, values.TRUE, values.U_OBJ, values.ONE, values.BLNG, values.OK, values.EMPTY} // Type names in upper case are things the user should never see. -var nativeTypeNames = []string{"UNDEFINED VALUE", "INT ARRAY", "SNIPPET DATA", "THUNK", - "CREATED LOCAL CONSTANT", "COMPILE TIME ERROR", "BLING", "UNSATISFIED CONDITIONAL", "REFERENCE VARIABLE", +var nativeTypeNames = []string{"UNDEFINED VALUE", "INT ARRAY", "THUNK", "CREATED LOCAL CONSTANT", + "COMPILE TIME ERROR", "BLING", "UNSATISFIED CONDITIONAL", "REFERENCE VARIABLE", "ITERATOR", "ok", "tuple", "error", "null", "int", "bool", "string", "rune", "float", "type", "func", - "pair", "list", "map", "set", "label"} + "pair", "list", "map", "set", "label", "snippet"} func BlankVm(db *sql.DB) *Vm { vm := &Vm{Mem: make([]values.Value, len(CONSTANTS)), Database: db, @@ -323,6 +307,8 @@ loop: vm.Mem[args[0]] = values.Value{values.TUPLE, []values.Value{vm.Mem[args[1]], vm.Mem[args[2]]}} } } + case CoSn: + vm.Mem[args[0]] = values.Value{values.SNIPPET, values.Snippet{vm.Mem[args[1]].V.([]values.Value), nil}} case Cpnt: vm.Mem[args[0]] = values.Value{values.INT, int(vm.Mem[args[1]].V.(rune))} case Cv1T: @@ -659,6 +645,8 @@ loop: vm.Mem[args[0]] = vm.Mem[args[1]].V.(values.Iterator).GetValue() case Itor: vm.Mem[args[0]] = values.Value{values.RUNE, rune(vm.Mem[args[1]].V.(int))} + case IxSn: + vm.Mem[args[0]] = vm.Mem[args[1]].V.(values.Snippet).Data[vm.Mem[args[2]].V.(int)] case IxTn: vm.Mem[args[0]] = vm.Mem[args[1]].V.([]values.Value)[args[2]] case IxXx: @@ -770,6 +758,16 @@ loop: vm.Mem[args[0]] = vm.makeError("vm/index/k", args[3], ix) } break Switch + case values.SNIPPET: + snippetData := container.V.(values.Snippet).Data + ix := index.V.(int) + ok := 0 <= ix && ix < len(snippetData) + if ok { + vm.Mem[args[0]] = snippetData[ix] + } else { + vm.Mem[args[0]] = vm.makeError("vm/index/s", args[3], ix, len(snippetData), args[1], args[2]) + } + break Switch case values.STRING: str := container.V.(string) ix := index.V.(int) @@ -866,6 +864,8 @@ loop: vm.Mem[args[0]] = values.Value{values.LIST, list} case Litx: vm.Mem[args[0]] = values.Value{values.STRING, vm.Literal(vm.Mem[args[1]])} + case LnSn: + vm.Mem[args[0]] = values.Value{values.INT, len(vm.Mem[args[1]].V.(values.Snippet).Data)} case Log: fmt.Print(vm.Mem[args[0]].V.(string) + "\n\n") case Logn: @@ -919,12 +919,11 @@ loop: vm.Mem[args[0]] = values.Value{values.SET, result} case MkSn: sFac := vm.SnippetFactories[args[1]] - vals := vector.Empty - for _, v := range sFac.Bindle.ValueLocs { - vals = vals.Conj(vm.Mem[v]) + vals := make([]values.Value, len(sFac.Bindle.ValueLocs)) + for i, v := range sFac.Bindle.ValueLocs { + vals[i] = vm.Mem[v] } - vm.Mem[args[0]] = values.Value{values.ValueType(sFac.SnippetType), - []values.Value{{values.STRING, sFac.SourceString}, {values.LIST, vals}, {values.SNIPPET_DATA, sFac.Bindle}}} + vm.Mem[args[0]] = values.Value{values.SNIPPET, values.Snippet{vals, sFac.Bindle}} case Mlfi: vm.Mem[args[0]] = values.Value{values.FLOAT, vm.Mem[args[1]].V.(float64) * float64(vm.Mem[args[2]].V.(int))} case Modi: @@ -954,49 +953,6 @@ loop: } else { fmt.Println(vm.DefaultDescription(vm.Mem[args[0]])) } - case Psnp: // Only for if you 'post HTML' or 'post SQL'. - // Everything we need to evaluate the snippets has been precompiled into a secret third field of the snippet struct, having - // type SNIPPET_DATA. We extract the relevant data from this and execute the precompiled code. - bindle := vm.Mem[args[1]].V.([]values.Value)[2].V.(*SnippetBindle) - objectString := vm.Mem[bindle.ObjectStringLoc].V.(string) - // What we do at that point depends on what kind of snippet it is, which is also recorded in the snippet data: - switch bindle.Csk { - case HTML_SNIPPET: // We parse this and emit it to whatever Output is. - t, err := template.New("html snippet").Parse(objectString) // TODO: parse this at compile time and stick it in the bindle. - if err != nil { - panic("Template parsing error.") - // TODO --- this. - // continue - } - var buf strings.Builder - injector := HTMLInjector{make([]any, 0, len(bindle.ValueLocs))} - for i := 1; i < len(bindle.ValueLocs); i = i + 2 { - mLoc := bindle.ValueLocs[i] - v := vm.Mem[mLoc] - switch v.T { - case values.STRING: - injector.Data = append(injector.Data, v.V.(string)) - case values.INT: - injector.Data = append(injector.Data, v.V.(int)) - case values.BOOL: - injector.Data = append(injector.Data, v.V.(bool)) - case values.FLOAT: - injector.Data = append(injector.Data, v.V.(float64)) - default: - panic("Unhandled case:" + vm.ConcreteTypeInfo[v.T].GetName(LITERAL)) - } - } - t.Execute(&buf, injector) - vm.OutHandle.Out(values.Value{values.STRING, buf.String()}) - vm.Mem[args[0]] = values.Value{values.SUCCESSFUL_VALUE, nil} - case SQL_SNIPPET: - injector := make([]values.Value, 0, len(bindle.ValueLocs)) - for i := 1; i < len(bindle.ValueLocs); i = i + 2 { - mLoc := bindle.ValueLocs[i] - injector = append(injector, vm.Mem[mLoc]) - } - vm.Mem[args[0]] = vm.evalPostSQL(objectString, injector) - } case Qabt: varcharLimit := args[1] for _, t := range args[2 : len(args)-1] { @@ -1086,30 +1042,6 @@ loop: loc = args[1] } continue - case Qspt: - switch ty := vm.ConcreteTypeInfo[vm.Mem[args[0]].T].(type) { - case StructType: - if ty.Snippet { - loc = loc + 1 - continue - } - } - loc = args[1] - continue - case Qspq: - if vm.Mem[args[0]].T == values.NULL { - loc = loc + 1 - continue - } - switch ty := vm.ConcreteTypeInfo[vm.Mem[args[0]].T].(type) { - case StructType: - if ty.Snippet { - loc = loc + 1 - continue - } - } - loc = args[1] - continue case Qstr: switch vm.ConcreteTypeInfo[vm.Mem[args[0]].T].(type) { case StructType: diff --git a/source/vm/vm_test.go b/source/vm/vm_test.go index ed201bd7..4252844b 100644 --- a/source/vm/vm_test.go +++ b/source/vm/vm_test.go @@ -379,8 +379,7 @@ func TestClones(t *testing.T) { } func TestSnippet(t *testing.T) { tests := []test_helper.TestItem{ - {`makeSn 42`, `Foo with (text::"zort |x| troz", data::["zort ", 42, " troz"])`}, - {`post HTML --- zort |2 + 2| troz`, `OK`}, + } test_helper.RunTest(t, "snippets_test.pf", tests, test_helper.TestValues) }