Is go-template Turing complete? I made a brainf * ck processing system

TL;DR

-** Implement brainfuck \ * ck processing system only with standard go-template function / syntax ! - Use kubectl as brainfuck \ * ck interpreter ** (Powerword) --Brainfuck \ * ck code in pod manifest --When getting pod information with kubectl, apply to go-template of brainfuck \ * ck interpreter --Repository: go-template-bf-interpreter --Practicality ...: smirk:

Introduction

C ++ templates, Python comprehensions, ... there are language features that have become Turing complete by themselves due to the black magic provided by the extra freedom.

And here we declare that ** go-template has joined the ranks of black magic **.

Speaking of go-template, there is a strong image of a mini language for templates such as HTML of gin and response shaping of kubectl (~~ or it is made with that intention by name ~~), but The following can be ** all possible ** [^ 1].

--Collection loop with range block --Conditional branching by if block --Declaration and update of variables (reassignment)

Reference (official): template --GoDoc

So, I implemented the processing system of brainfuck \ * ck, which is the gateway to Turing completeness, with go-template (standard function / syntax only [^ 2] without relying on Go language).

How it works

Use kubectl.

Put the source code of brainfuck \ * ck in the manifest file of pod, and when you get it with kubectl, use go-template to mold (= evaluate with brainfuck \ * ck interpreter of go-template). I have.

Repository: go-template-bf-interpreter

(I used kubectl because I knew go-template for the first time with kubectl. ~~ I wrote at the beginning that "I'm familiar with the HTML generation of gin ", but I'm sorry for Airp ~~)

Store brainfuck \ * ck source code in manifest file

Since any key and value (character string) can be stored in metadata.annotations, the source code is stored here. Also, let's put in the standard input to brainfuck \ * ck.

hello.yaml


metadata:
  name: bf-source-pod
# add dummies to make annotations long enough to loop (see bf-interpreter.tpl for details)
  annotations:
# used for bf stdin
    input: ""
# bf source code
    src: >
      +++++++++[>++++++++>+++++++++++>+++>+<<<<-]>.>++.+++++++..+++.
      >+++++.<<+++++++++++++++.>.+++.------.--------.>+.>+.
    dummy1: dummy1
    dummy2: dummy2
    #...

By the way, the reason why I put a lot of dummy keys is to increase the number of loops of the interpreter (described later).

I used the code of helloworld from Brainfuck Super Introduction --Qiita.

I don't use the pod container anyway, so anything is fine. For the time being, I made an alpine image that starts up quickly.

Execution flow

python


#Build a k8s cluster(An example is kind)
$ kind create cluster

#Create a pod with the above helloworld code
$ kubectl create -f hello.yaml
pod/bf-source-pod created

#pod information(=Source code)And its contents become an interpreter go-Evaluate with template
$ kubectl get pods -o go-template-file=bf-interpreter.tpl
Hello World!

go-template programming design pattern (?)

The implementation of the brainf \ * ck interpreter looks like this. ~~ Indent hell. ~~ bf-interpreter.tpl

Below, I will introduce the tricks I used.

Fill in the blanks

You can fill the space outside the parentheses by adding - to both ends of{{}}. If you use this, even if you put indents and line breaks outside {{}}, they will all be ignored. ** Required for readability ** in go-template programming. ** If not attached, one-liner binding will start **.

withspace.tpl


{{if true}}
    {{println "got it!"}}
{{else}}
    {{println "no..."}}
{{end}}

Wasted space is output as it is


 kubectl get pods -o go-template-file=withspace.tpl

    got it!

--With hyphen

trimspace.tpl


{{- if true -}}
    {{- println "got it!" -}}
{{- else -}}
    {{- println "no..." -}}
{{- end -}}

Wasted space disappears


$ kubectl get pods -o go-template-file=trimspace.tpl
got it!

loop

brainf \ * ck requires a while loop. Source code Used for parsing each character and jumping when evaluating [, ].

But unfortunately string cannot iterate with range. Furthermore, since the only literals that can be created with go-template are constants, it is not possible to create new arrays or maps.

python


$ kubectl get pods -o go-template --template '{{range $c := "abc"}}{{println $c}}{{end}}'
...
error: error executing template "{{range $c := \"abc\"}}{{println $c}}{{end}}": template: output:1:14: executing "output" at <"abc">: range can't iterate over abc

So we use the pod info metadata.annotations (map [string] string) for the loop in range. A dummy is mixed with the annotation so that it can be looped 16 times.

hello.yaml


metadata:
  name: bf-source-pod
  annotations:
# used for bf stdin
    input: ""
# bf source code
    src: >
      +++++++++[>++++++++>+++++++++++>+++>+<<<<-]>.>++.+++++++..+++.
      >+++++.<<+++++++++++++++.>.+++.------.--------.>+.>+.
    dummy1: dummy1
    dummy2: dummy2
    #...
    dummy14: dummy14

By using this loop in multiple stages, memory initialization and source code parsing are performed.

bf-interpreter.tpl


{{- /*Substitute map and use for range block*/ -}}
{{- $Looper := (index .items 0).metadata.annotations -}}

{{- /*Memory initialization(len $Looper)^Fill 2 bytes with 0) */ -}}
{{- $memory := "" -}}
{{- range $Looper -}}
    {{- range $Looper -}}
        {{- $memory = print $memory "\x00" -}}
    {{- end -}}
{{- end -}}

{{- /*Load source code(len $Looper)^Perth 3 letters from the beginning) */ -}}
{{- range $Looper -}}
    {{- range $Looper -}}
        {{- range $Looper -}}
            {{- /* NOTE: exists is implemented only in k8s parser */ -}}
            {{- if exists $Source (len $parsingBytePos) -}}
                {{- $tokenByte := index $Source (len $parsingBytePos) -}}
                {{- $token := printf "%c" $tokenByte -}}
                
                {{- /*Evaluate tokens (omitted)*/ -}}

                {{- /* increment pos */ -}}
                {{- $parsingBytePos = print $parsingBytePos " " -}}
            {{- end -}}
        {{- end -}}
    {{- end -}}
{{- end -}}

By the way, the reason why it loops 16 times is to improve the specifications of the interpreter.

--Memory size: 256byte ($ Looper 2-stage loop) --Upper limit of parseable source code length: 4096 characters ( $ Looper 3-stage loop)

Addition and subtraction

Unfortunately (second time), go-template doesn't have integer addition / subtraction functions or operators. However, brainfuck \ * ck needs addition and subtraction when updating memory values and pointers.

So ** use the length of the string instead of an integer **. The length of the character string can be changed by combining and slicing, and the length can be obtained as an integer with the len function.

--Addition

inc.tpl


{{- /* go-template print is equivalent to Go's Sprint(No side effects) */ -}}
{{- $numStr := " " -}}
{{- println (len $numStr) -}}
{{- $numStr = print $numStr " " -}}
{{- println (len $numStr) -}}

python


$ kubectl get pods -o go-template-file=inc.tpl
1
2

--Subtraction

dec.tpl


{{- $numStr := " " -}}
{{- println (len $numStr) -}}
{{- $numStr = slice $numStr 1 -}}
{{- println (len $numStr) -}}

python


$ kubectl get pods -o go-template-file=dec.tpl
1
0

Memory update

As mentioned above, you cannot create an array with go-template. Also, it is not possible to update only the elements of an existing object. Only variables can be rvalues in assignment expressions.

python


$ kubectl get pods -o go-template --template '{{(index .items 0) := "hoge"}}'
error: error parsing template {{(index .items 0) := "hoge"}}, template: output:1: unexpected ":=" in operand

Therefore, the character string is used as memory. In Go, when indexing a string, the string is treated as [] byte, so the string itself can be regarded as a byte string.

go language index


s := "abc"
fmt.Println([]byte(s)) // [97 98 99]
fmt.Println(s[0]) // 97
fmt.Println([]byte(s)[0]) // 97

And, when updating only a certain byte in the character string with + or - etc.," a new memory character string in which only the byte is replaced "is created.

bf-interpreter.tpl


{{- else if eq $token "+" -}}
	{{- /* ...Takes out the value of the reference address and increments it(abridgement) */ -}}

	{{- /*Memory update*/ -}}
	{{- /*Replace with new memory that replaces only the reference address*/ -}}

	{{- /*Memory before the reference address*/ -}}
	{{- $former := slice $memory 0 (len $memoryPtr) -}}

	{{- /*Memory after the reference address*/ -}}
	{{- /* NOTE: (len (print $memoryPtr " ")Is the reference address+1 */ -}}
	{{- $latter := slice $memory (len (print $memoryPtr " ")) -}}

	{{- /*Substitution (If you print the byte value as it is, the integer will be converted to a character string, so printf will convert it to the corresponding ASCII code character)*/ -}}
	{{- $memory = print $former (printf "%c" $incrementedValue) $latter -}}
{{- end -}}

in conclusion

This is the introduction of the go-template brainfuck \ * ck processing system.

** Let's go-template programming! ** **

[^ 1]: Looking at this, it feels like "You can do that with erb!", But while erb can write any Ruby expression, go-template has an independent grammar (only scalars can be created, and go's standard functions are also available. There is a binding that you can not call it as it is ...

[^ 2]: Some k8s original go-template functions (index) are used. The implementation is here

Recommended Posts

Is go-template Turing complete? I made a brainf * ck processing system
〇✕ I made a game
Is this a system trade?
What is a system call
I made a python text
I made a discord bot
I made a lo command that is more useful than ls
i! i! ← This is a mathematical formula
I made a C ++ learning site
I made a Line-bot using Python!
I made a CUI-based translation script (2)
I made a wikipedia gacha bot
I made a fortune with Python.
I made a CUI-based translation script
I made a daemon with Python
I made a kind of simple image processing tool in Go language.
[C language] My locomotive is too slow ~ I made a sl command ~