gitee.com/quant1x/num@v0.3.2/asm/asm2plan9/asm2plan9s.go (about)

     1  /*
     2   * Minio Cloud Storage, (C) 2016-2017 Minio, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package main
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"io"
    23  	"log"
    24  	"os"
    25  	"strings"
    26  )
    27  
    28  type Instruction struct {
    29  	instruction string
    30  	lineno      int
    31  	commentPos  int
    32  	inDefine    bool
    33  	assembled   string
    34  	opcodes     []byte
    35  }
    36  
    37  type Assembler struct {
    38  	Prescan      bool
    39  	Instructions []Instruction
    40  	Compact      bool
    41  }
    42  
    43  // assemble assembles an array of lines into their
    44  // resulting plan9 equivalents
    45  func (a *Assembler) assemble(lines []string) ([]string, error) {
    46  
    47  	result := make([]string, 0)
    48  
    49  	for lineno, line := range lines {
    50  		startsWithTab := strings.HasPrefix(line, "\t")
    51  		line := strings.Replace(line, "\t", "    ", -1)
    52  		fields := strings.Split(line, "//")
    53  		if len(fields) == 2 && (startsAfterLongWordByteSequence(fields[0]) || len(fields[0]) == 65) {
    54  
    55  			// test whether string before instruction is terminated with a backslash (so used in a #define)
    56  			trimmed := strings.TrimSpace(fields[0])
    57  			inDefine := len(trimmed) > 0 && string(trimmed[len(trimmed)-1]) == `\`
    58  
    59  			// While prescanning collect the instructions
    60  			if a.Prescan {
    61  				ins := Instruction{instruction: fields[1], lineno: lineno, commentPos: len(fields[0]), inDefine: inDefine}
    62  				a.Instructions = append(a.Instructions, ins)
    63  				continue
    64  			}
    65  
    66  			var ins *Instruction
    67  			for i := range a.Instructions {
    68  				if lineno == a.Instructions[i].lineno {
    69  					ins = &a.Instructions[i]
    70  				}
    71  			}
    72  			if ins == nil {
    73  				if a.Compact {
    74  					continue
    75  				}
    76  				panic("failed to find entry with correct line number")
    77  			}
    78  			if startsWithTab {
    79  				ins.assembled = strings.Replace(ins.assembled, "    ", "\t", 1)
    80  			}
    81  			result = append(result, ins.assembled)
    82  		} else if !a.Prescan {
    83  			if startsWithTab {
    84  				line = strings.Replace(line, "    ", "\t", 1)
    85  			}
    86  			result = append(result, line)
    87  		}
    88  	}
    89  
    90  	return result, nil
    91  }
    92  
    93  // startsAfterLongWordByteSequence determines if an assembly instruction
    94  // starts on a position after a combination of LONG, WORD, BYTE sequences
    95  func startsAfterLongWordByteSequence(prefix string) bool {
    96  
    97  	if len(strings.TrimSpace(prefix)) != 0 && !strings.HasPrefix(prefix, "    LONG $0x") &&
    98  		!strings.HasPrefix(prefix, "    WORD $0x") && !strings.HasPrefix(prefix, "    BYTE $0x") {
    99  		return false
   100  	}
   101  
   102  	length := 4 + len(prefix) + 1
   103  
   104  	for objcodes := 3; objcodes <= 8; objcodes++ {
   105  
   106  		ls, ws, bs := 0, 0, 0
   107  
   108  		oc := objcodes
   109  
   110  		for ; oc >= 4; oc -= 4 {
   111  			ls++
   112  		}
   113  		if oc >= 2 {
   114  			ws++
   115  			oc -= 2
   116  		}
   117  		if oc == 1 {
   118  			bs++
   119  		}
   120  		size := 4 + ls*(len("LONG $0x")+8) + ws*(len("WORD $0x")+4) + bs*(len("BYTE $0x")+2) + (ls+ws+bs-1)*len("; ")
   121  
   122  		if length == size+6 { // comment starts after a space
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  // combineLines shortens the output by combining consecutive lines into a larger list of opcodes
   130  func (a *Assembler) combineLines() {
   131  	startIndex, startLine, opcodes := -1, -1, make([]byte, 0, 1024)
   132  	combined := make([]Instruction, 0, 100)
   133  	for i, ins := range a.Instructions {
   134  		if startIndex == -1 {
   135  			startIndex, startLine = i, ins.lineno
   136  		}
   137  		if ins.lineno != startLine+(i-startIndex) { // we have found a non-consecutive line
   138  			combiAssem, _ := toPlan9s(opcodes, "", 0, false)
   139  			combiIns := Instruction{assembled: combiAssem, lineno: startLine, inDefine: false}
   140  
   141  			combined = append(combined, combiIns)
   142  			opcodes = opcodes[:0]
   143  			startIndex, startLine = i, ins.lineno
   144  		}
   145  		opcodes = append(opcodes, ins.opcodes...)
   146  	}
   147  	if len(opcodes) > 0 {
   148  		combiAssem, _ := toPlan9s(opcodes, "", 0, false)
   149  		ins := Instruction{assembled: combiAssem, lineno: startLine, inDefine: false}
   150  
   151  		combined = append(combined, ins)
   152  	}
   153  
   154  	a.Instructions = combined
   155  }
   156  
   157  // readLines reads a whole file into memory
   158  // and returns a slice of its lines.
   159  func readLines(path string, in io.Reader) ([]string, error) {
   160  	if in == nil {
   161  		file, err := os.Open(path)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		defer file.Close()
   166  		in = file
   167  	}
   168  
   169  	var lines []string
   170  	scanner := bufio.NewScanner(in)
   171  	for scanner.Scan() {
   172  		lines = append(lines, scanner.Text())
   173  	}
   174  	return lines, scanner.Err()
   175  }
   176  
   177  // writeLines writes the lines to the given file.
   178  func writeLines(lines []string, path string, out io.Writer) error {
   179  	if path != "" {
   180  		file, err := os.Create(path)
   181  		if err != nil {
   182  			return err
   183  		}
   184  		defer file.Close()
   185  		out = file
   186  	}
   187  
   188  	w := bufio.NewWriter(out)
   189  	for _, line := range lines {
   190  		fmt.Fprintln(w, line)
   191  	}
   192  	return w.Flush()
   193  }
   194  
   195  func assemble(lines []string, compact bool) (result []string, err error) {
   196  
   197  	// TODO: Make compaction configurable
   198  	a := Assembler{Prescan: true, Compact: compact}
   199  
   200  	_, err = a.assemble(lines)
   201  	if err != nil {
   202  		return result, err
   203  	}
   204  
   205  	err = as(a.Instructions)
   206  	if err != nil {
   207  		return result, err
   208  	}
   209  
   210  	if a.Compact {
   211  		a.combineLines()
   212  	}
   213  
   214  	a.Prescan = false
   215  	result, err = a.assemble(lines)
   216  	if err != nil {
   217  		return result, err
   218  	}
   219  
   220  	return result, nil
   221  }
   222  
   223  func main() {
   224  
   225  	file := ""
   226  	if len(os.Args) >= 2 {
   227  		file = os.Args[1]
   228  	}
   229  
   230  	var lines []string
   231  	var err error
   232  	if len(file) > 0 {
   233  		fmt.Println("Processing file", file)
   234  		lines, err = readLines(file, nil)
   235  	} else {
   236  		lines, err = readLines("", os.Stdin)
   237  	}
   238  	if err != nil {
   239  		log.Fatalf("readLines: %s", err)
   240  	}
   241  
   242  	result, err := assemble(lines, false)
   243  	if err != nil {
   244  		fmt.Print(err)
   245  		os.Exit(-1)
   246  	}
   247  
   248  	err = writeLines(result, file, os.Stdout)
   249  	if err != nil {
   250  		log.Fatalf("writeLines: %s", err)
   251  	}
   252  }