Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 116 additions & 86 deletions data/transactions/logic/assembler.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}

func base32DecdodeAnyPadding(x string) (val []byte, err error) {
func base32DecodeAnyPadding(x string) (val []byte, err error) {
val, err = base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(x)
if err != nil {
// try again with standard padding
Expand All @@ -567,7 +567,7 @@ func parseBinaryArgs(args []string) (val []byte, consumed int, err error) {
err = errors.New("byte base32 arg lacks close paren")
return
}
val, err = base32DecdodeAnyPadding(arg[open+1 : close])
val, err = base32DecodeAnyPadding(arg[open+1 : close])
if err != nil {
return
}
Expand Down Expand Up @@ -595,7 +595,7 @@ func parseBinaryArgs(args []string) (val []byte, consumed int, err error) {
err = fmt.Errorf("need literal after 'byte %s'", arg)
return
}
val, err = base32DecdodeAnyPadding(args[1])
val, err = base32DecodeAnyPadding(args[1])
if err != nil {
return
}
Expand Down Expand Up @@ -1399,25 +1399,29 @@ func typecheck(expected, got StackType) bool {
return expected == got
}

var spaces = [256]uint8{'\t': 1, ' ': 1}
// newline not included since handled in scanner
var tokenSeparators = [256]bool{'\t': true, ' ': true, ';': true}

func fieldsFromLine(line string) []string {
var fields []string
func tokensFromLine(line string) []string {
var tokens []string

i := 0
for i < len(line) && spaces[line[i]] != 0 {
for i < len(line) && tokenSeparators[line[i]] {
if line[i] == ';' {
tokens = append(tokens, ";")
}
i++
}

start := i
inString := false
inBase64 := false
inString := false // tracked to allow spaces and comments inside
inBase64 := false // tracked to allow '//' inside
for i < len(line) {
if spaces[line[i]] == 0 { // if not space
if !tokenSeparators[line[i]] { // if not space
switch line[i] {
case '"': // is a string literal?
if !inString {
if i == 0 || i > 0 && spaces[line[i-1]] != 0 {
if i == 0 || i > 0 && tokenSeparators[line[i-1]] {
inString = true
}
} else {
Expand All @@ -1428,9 +1432,9 @@ func fieldsFromLine(line string) []string {
case '/': // is a comment?
if i < len(line)-1 && line[i+1] == '/' && !inBase64 && !inString {
if start != i { // if a comment without whitespace
fields = append(fields, line[start:i])
tokens = append(tokens, line[start:i])
}
return fields
return tokens
}
case '(': // is base64( seq?
prefix := line[start:i]
Expand All @@ -1446,19 +1450,29 @@ func fieldsFromLine(line string) []string {
i++
continue
}

// we've hit a space, end last token unless inString

if !inString {
field := line[start:i]
fields = append(fields, field)
if field == "base64" || field == "b64" {
inBase64 = true
} else if inBase64 {
token := line[start:i]
tokens = append(tokens, token)
if line[i] == ';' {
Comment thread
tzaffi marked this conversation as resolved.
tokens = append(tokens, ";")
}
if inBase64 {
inBase64 = false
} else if token == "base64" || token == "b64" {
inBase64 = true
}
}
i++

// gobble up consecutive whitespace (but notice semis)
if !inString {
for i < len(line) && spaces[line[i]] != 0 {
for i < len(line) && tokenSeparators[line[i]] {
if line[i] == ';' {
tokens = append(tokens, ";")
}
i++
}
start = i
Expand All @@ -1467,10 +1481,10 @@ func fieldsFromLine(line string) []string {

// add rest of the string if any
if start < len(line) {
fields = append(fields, line[start:i])
tokens = append(tokens, line[start:i])
}

return fields
return tokens
}

func (ops *OpStream) trace(format string, args ...interface{}) {
Expand Down Expand Up @@ -1531,6 +1545,16 @@ func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction
}
}

// splitTokens breaks tokens into two slices at the first semicolon.
func splitTokens(tokens []string) (current, rest []string) {
Comment thread
tzaffi marked this conversation as resolved.
for i, token := range tokens {
if token == ";" {
return tokens[:i], tokens[i+1:]
}
}
return tokens, nil
}
Copy link
Copy Markdown
Contributor

@tzaffi tzaffi Aug 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the fact that splitTokens() behaves differently for the following 2 examples problematic? (the 2nd output isn't consistently nil):

	tokens := []string{"hello", "there"}
	splitTokens(tokens)

	tokens = []string{"hello", "there", ";"}
	splitTokens(tokens)

See the playground

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the difference will turn out to be important in the next step of the plan. Once tokens can expand into other tokens, it will matter whether something expands to included the semi or not. So I think the distinction is useful. (But in the meantime, the distinction won't matter because the next tokens are tested with len.


// assemble reads text from an input and accumulates the program
func (ops *OpStream) assemble(text string) error {
fin := strings.NewReader(text)
Expand All @@ -1541,73 +1565,80 @@ func (ops *OpStream) assemble(text string) error {
for scanner.Scan() {
ops.sourceLine++
line := scanner.Text()
line = strings.TrimSpace(line)
if len(line) == 0 {
ops.trace("%3d: 0 line\n", ops.sourceLine)
continue
}
if strings.HasPrefix(line, "//") {
ops.trace("%3d: // line\n", ops.sourceLine)
continue
}
if strings.HasPrefix(line, "#pragma") {
ops.trace("%3d: #pragma line\n", ops.sourceLine)
ops.pragma(line)
continue
}
fields := fieldsFromLine(line)
if len(fields) == 0 {
ops.trace("%3d: no fields\n", ops.sourceLine)
continue
}
// we're about to begin processing opcodes, so settle the Version
if ops.Version == assemblerNoVersion {
ops.Version = AssemblerDefaultVersion
}
if ops.versionedPseudoOps == nil {
ops.versionedPseudoOps = prepareVersionedPseudoTable(ops.Version)
}
opstring := fields[0]
if opstring[len(opstring)-1] == ':' {
ops.createLabel(opstring[:len(opstring)-1])
fields = fields[1:]
if len(fields) == 0 {
ops.trace("%3d: label only\n", ops.sourceLine)
tokens := tokensFromLine(line)
if len(tokens) > 0 {
if first := tokens[0]; first[0] == '#' {
directive := first[1:]
switch directive {
case "pragma":
ops.pragma(tokens)
ops.trace("%3d: #pragma line\n", ops.sourceLine)
default:
ops.errorf("Unknown directive: %s", directive)
}
continue
}
opstring = fields[0]
}
spec, expandedName, ok := getSpec(ops, opstring, fields[1:])
if ok {
ops.trace("%3d: %s\t", ops.sourceLine, opstring)
ops.recordSourceLine()
if spec.Modes == modeApp {
ops.HasStatefulOps = true
for current, next := splitTokens(tokens); len(current) > 0 || len(next) > 0; current, next = splitTokens(next) {
if len(current) == 0 {
continue
}
args, returns := spec.Arg.Types, spec.Return.Types
if spec.refine != nil {
nargs, nreturns := spec.refine(&ops.known, fields[1:])
if nargs != nil {
args = nargs
}
if nreturns != nil {
returns = nreturns
}
// we're about to begin processing opcodes, so settle the Version
if ops.Version == assemblerNoVersion {
ops.Version = AssemblerDefaultVersion
}
ops.trackStack(args, returns, append([]string{expandedName}, fields[1:]...))
spec.asm(ops, &spec, fields[1:])
if spec.deadens() { // An unconditional branch deadens the following code
ops.known.deaden()
if ops.versionedPseudoOps == nil {
ops.versionedPseudoOps = prepareVersionedPseudoTable(ops.Version)
}
if spec.Name == "callsub" {
// since retsub comes back to the callsub, it is an entry point like a label
ops.known.label()
opstring := current[0]
if opstring[len(opstring)-1] == ':' {
ops.createLabel(opstring[:len(opstring)-1])
current = current[1:]
if len(current) == 0 {
ops.trace("%3d: label only\n", ops.sourceLine)
continue
}
opstring = current[0]
}
spec, expandedName, ok := getSpec(ops, opstring, current[1:])
if ok {
ops.trace("%3d: %s\t", ops.sourceLine, opstring)
ops.recordSourceLine()
if spec.Modes == modeApp {
ops.HasStatefulOps = true
}
args, returns := spec.Arg.Types, spec.Return.Types
if spec.refine != nil {
nargs, nreturns := spec.refine(&ops.known, current[1:])
if nargs != nil {
args = nargs
}
if nreturns != nil {
returns = nreturns
}
}
ops.trackStack(args, returns, append([]string{expandedName}, current[1:]...))
spec.asm(ops, &spec, current[1:])
if spec.deadens() { // An unconditional branch deadens the following code
ops.known.deaden()
}
if spec.Name == "callsub" {
// since retsub comes back to the callsub, it is an entry point like a label
ops.known.label()
}
}
ops.trace("\n")
continue
}
}

if err := scanner.Err(); err != nil {
if errors.Is(err, bufio.ErrTooLong) {
err = errors.New("line too long")
}
ops.error(err)
}

// backward compatibility: do not allow jumps behind last instruction in v1
if ops.Version <= 1 {
for label, dest := range ops.labels {
Expand Down Expand Up @@ -1635,21 +1666,20 @@ func (ops *OpStream) assemble(text string) error {
return nil
}

func (ops *OpStream) pragma(line string) error {
fields := strings.Split(line, " ")
if fields[0] != "#pragma" {
return ops.errorf("invalid syntax: %s", fields[0])
func (ops *OpStream) pragma(tokens []string) error {
if tokens[0] != "#pragma" {
return ops.errorf("invalid syntax: %s", tokens[0])
}
if len(fields) < 2 {
if len(tokens) < 2 {
return ops.error("empty pragma")
}
key := fields[1]
key := tokens[1]
switch key {
case "version":
if len(fields) < 3 {
if len(tokens) < 3 {
return ops.error("no version value")
}
value := fields[2]
value := tokens[2]
var ver uint64
if ops.pending.Len() > 0 {
return ops.error("#pragma version is only allowed before instructions")
Expand All @@ -1674,10 +1704,10 @@ func (ops *OpStream) pragma(line string) error {
}
return nil
case "typetrack":
if len(fields) < 3 {
if len(tokens) < 3 {
return ops.error("no typetrack value")
}
value := fields[2]
value := tokens[2]
on, err := strconv.ParseBool(value)
if err != nil {
return ops.errorf("bad #pragma typetrack: %#v", value)
Expand Down
Loading