wsfmt/wsfmt.go
lordwelch d6c0a9e0ec wsfmt.go: refactor
Refactor and cleanup of unused variables
Switch to string builder
Better error handling
Make printIdentifier account for the dot e.g. structName.fieldName
Fix formatting for negative numbers in a case statment
Rewrite array printing
Simplify newline printing
2018-05-15 13:07:27 -07:00

572 lines
14 KiB
Go

package main
import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
"timmy.narnian.us/git/timmy/wsfmt/text/lex"
)
type stateFn func(*Formatter) stateFn
type Formatter struct {
l *lex.Lexer
previousToken lex.Item
token lex.Item
nextToken lex.Item
state stateFn
maxNewlines int
newlineCount int
parenDepth int
Output strings.Builder
scopeLevel []int
// Each index is one scope deep delimited by braces or case statement.
// the number is how many 'soft' scopes deep it is resets to 1 on newline e.g. an if statement without braces
}
var (
blank = lex.Item{Pos: -1}
b []byte
fmtErr error
)
func main() {
file, _ := os.Open(os.Args[1])
f := Format(file)
f.run()
fmt.Println(f.Output.String())
if fmtErr != nil {
os.Stdout.Sync()
fmt.Fprintln(os.Stderr, "\n", fmtErr)
os.Exit(1)
}
}
func Format(text io.Reader) (f *Formatter) {
var err error
FILE := transform.NewReader(text, unicode.BOMOverride(unicode.UTF8.NewDecoder().Transformer))
b, err = ioutil.ReadAll(FILE)
if err != nil {
panic(err)
}
f = &Formatter{}
f.l = lex.Lex("name", string(b))
f.maxNewlines = 3
f.nextToken = blank
return f
}
func (f *Formatter) next() lex.Item {
f.previousToken = f.token
var temp lex.Item
if f.nextToken == blank {
temp = f.l.NextItem()
} else {
temp = f.nextToken
f.nextToken = blank
}
for ; temp.Typ == lex.ItemSpace || temp.Typ == lex.ItemNewline; temp = f.l.NextItem() {
}
f.token = temp
return f.token
}
func (f *Formatter) peek() lex.Item {
if f.nextToken == blank {
temp := f.l.NextItem()
count := 0
for ; temp.Typ == lex.ItemSpace || temp.Typ == lex.ItemNewline; temp = f.l.NextItem() {
if temp.Typ == lex.ItemNewline {
count += strings.Count(temp.Val, "\n")
}
}
if count < 3 {
f.newlineCount = count
} else {
f.newlineCount = 3
}
f.nextToken = temp
}
return f.nextToken
}
func (f *Formatter) run() {
for f.state = format; f.state != nil; {
f.state = f.state(f)
}
}
func errorf(format string, args ...interface{}) stateFn {
fmtErr = fmt.Errorf(format, args...)
return nil
}
func format(f *Formatter) stateFn {
switch t := f.next().Typ; {
case t == lex.ItemEOF:
return nil
case t == lex.ItemError:
return errorf("error: %s", f.token.Val)
case t == lex.ItemComment:
f.printComment()
case t == lex.ItemFunction:
return formatFunction
case t == lex.ItemIf, t == lex.ItemWhile, t == lex.ItemFor, t == lex.ItemSwitch:
return formatConditional
case t == lex.ItemElse:
if f.previousToken.Typ == lex.ItemRightBrace {
f.Output.WriteString(" else")
} else {
f.Output.WriteString("else")
}
if f.peek().Typ != lex.ItemLeftBrace && f.peek().Typ != lex.ItemIf {
f.scopeLevel[len(f.scopeLevel)-1]++
printNewline(f)
printTab(f)
}
case t == lex.ItemReturn:
f.Output.WriteString(f.token.Val + " ")
case t == lex.ItemModifiers, t == lex.ItemIdentifier, t == lex.ItemNumber, t == lex.ItemBool, t == lex.ItemString:
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
case isChar(t):
return printChar(f)
case t == lex.ItemStruct:
return formatStruct
case t == lex.ItemVar:
return formatVar
case t == lex.ItemOperator:
printOperator(f)
case t == lex.ItemArray:
return formatArray
case t == lex.ItemCase:
return formatCase
case t == lex.ItemEnum:
return formatEnum
default:
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
return format
}
func (f *Formatter) printComment() {
f.Output.WriteString(f.token.Val)
printNewline(f)
}
func formatFunction(f *Formatter) stateFn {
if f.token.Typ == lex.ItemFunction {
f.Output.WriteString(f.token.Val + " ")
}
switch t := f.next().Typ; {
case t == lex.ItemEOF:
return errorf("unexpected EOF wanted identifier\n")
case t == lex.ItemComment:
f.printComment()
case t == lex.ItemIdentifier:
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
if f.next().Typ == lex.ItemLeftParen {
printChar(f)
return format
}
return errorf("expected left Parenthesis got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
func formatStruct(f *Formatter) stateFn {
if f.token.Typ == lex.ItemStruct {
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
}
switch t := f.next().Typ; {
case t == lex.ItemEOF:
return errorf("unexpected EOF wanted identifier\n")
case t == lex.ItemComment:
f.printComment()
case t == lex.ItemIdentifier:
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
return format
default:
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
return formatStruct
}
func formatVar(f *Formatter) stateFn {
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
for notdone := true; notdone; {
if f.next().Typ == lex.ItemIdentifier {
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
if f.next().Typ == lex.ItemChar {
switch f.token.Val {
case ",":
printChar(f)
case ":":
printChar(f)
notdone = false
default:
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
} else {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
} else {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
}
switch f.next().Typ {
case lex.ItemIdentifier:
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
case lex.ItemArray:
return formatArray // errorf("Bad array syntax")
default:
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
return format
}
func printIdentifier(f *Formatter) bool {
switch i := f.peek(); {
case i.Val == "{", i.Val == "}", i.Val == "(", i.Val == ")", i.Val == "[", i.Val == "]", i.Val == "|", i.Val == ",", i.Val == ":", i.Val == ";":
f.Output.WriteString(f.token.Val)
case i.Typ == lex.ItemDot:
f.Output.WriteString(f.token.Val)
f.next()
return printDot(f)
default:
f.Output.WriteString(f.token.Val + " ")
}
return true
}
func printDot(f *Formatter) bool {
f.Output.WriteString(".")
if f.peek().Typ == lex.ItemIdentifier {
f.next()
return printIdentifier(f)
}
return false
}
func printOperator(f *Formatter) {
str := "%s"
switch f.token.Val {
case "|", "!":
case "+", "-":
switch f.previousToken.Typ {
case lex.ItemLeftParen, lex.ItemOperator, lex.ItemReturn, lex.ItemCase:
default:
str = "%s "
}
default:
str = "%s "
}
switch f.previousToken.Val {
case ")", "]":
str = " " + str
}
f.Output.WriteString(fmt.Sprintf(str, f.token.Val))
}
func formatConditional(f *Formatter) stateFn {
switch f.token.Typ {
case lex.ItemIf, lex.ItemWhile, lex.ItemFor, lex.ItemSwitch:
if f.previousToken.Typ == lex.ItemElse {
f.Output.WriteString(" ")
}
if f.next().Typ != lex.ItemLeftParen {
return errorf("expected parenthesis got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
f.Output.WriteString(f.previousToken.Val + " (")
f.parenDepth = 1
}
switch t := f.next().Typ; {
case t == lex.ItemEOF:
return errorf("unexpected EOF wanted identifier\n")
case t == lex.ItemComment:
f.printComment()
case t == lex.ItemOperator:
printOperator(f)
case t == lex.ItemIdentifier, t == lex.ItemNumber, t == lex.ItemString, t == lex.ItemBool:
if !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
case isChar(t):
if f.token.Val == ";" {
f.Output.WriteString("; ")
} else {
printChar(f)
}
switch f.token.Val {
case ")":
f.parenDepth--
if f.parenDepth == 0 {
if f.peek().Typ != lex.ItemLeftBrace {
f.scopeLevel[len(f.scopeLevel)-1]++
printNewline(f)
printTab(f)
}
return format
}
case "(":
f.parenDepth++
}
}
return formatConditional
}
func formatNewLine(f *Formatter) stateFn {
switch t := f.peek().Typ; {
case t == lex.ItemEOF:
f.Output.WriteString("\n")
return nil
case t == lex.ItemError:
return errorf("error: " + f.token.Val)
case t == lex.ItemCase:
printNewline(f)
f.scopeLevel = f.scopeLevel[:len(f.scopeLevel)-1]
printTab(f)
case f.peek().Val == ";" || f.peek().Val == "}":
default:
printNewline(f)
printTab(f)
}
return format
}
func formatEnum(f *Formatter) stateFn {
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
if f.next().Typ != lex.ItemIdentifier {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
if !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
if f.next().Typ != lex.ItemLeftBrace {
return errorf("expected left brace got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
f.scopeLevel = append(f.scopeLevel, 1)
f.Output.WriteString(" {")
printNewline(f)
printTab(f)
return formatEnumIdent
}
func formatEnumIdent(f *Formatter) stateFn {
if f.next().Typ != lex.ItemIdentifier {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
if !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
switch f.peek().Val {
case "=":
f.next()
printOperator(f)
if f.peek().Typ != lex.ItemNumber {
return errorf("expected Number got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
f.next()
if !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
if f.peek().Typ == lex.ItemRightBrace {
return format
}
case "}":
return format
}
return formatEnumChar
}
func formatEnumChar(f *Formatter) stateFn {
if f.next().Val != "," {
return errorf("expected Comma got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
f.Output.WriteString(",")
if f.peek().Typ == lex.ItemRightBrace {
return format
}
printNewline(f)
printTab(f)
return formatEnumIdent
}
func formatRightBrace(f *Formatter) stateFn {
f.scopeLevel = f.scopeLevel[:len(f.scopeLevel)-1]
if f.previousToken.Typ != lex.ItemLeftBrace {
f.Output.WriteString("\n")
printTab(f)
}
f.Output.WriteString("}")
switch f.peek().Typ {
case lex.ItemChar, lex.ItemElse, lex.ItemRightBrace:
return format
}
return formatNewLine
}
func formatCase(f *Formatter) stateFn {
if !printIdentifier(f) {
return errorf("invalid identifier: trailing dot '.'")
}
switch f.next().Typ {
case lex.ItemLeftParen:
f.Output.WriteString(" (")
if f.next().Typ != lex.ItemIdentifier || !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
if f.next().Typ != lex.ItemRightParen {
return errorf("expected parenthesis got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
f.Output.WriteString(")")
if f.next().Typ != lex.ItemIdentifier || !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
case lex.ItemIdentifier, lex.ItemNumber, lex.ItemString:
if !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
case lex.ItemOperator:
if (f.token.Val == "+" || f.token.Val == "-") && f.peek().Typ == lex.ItemNumber {
printOperator(f)
f.next()
if !printIdentifier(f) {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
} else {
return errorf("Invalid Operator got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
}
if f.next().Val != ":" {
return errorf("expected \":\" got %s: %s %s\n", lex.Rkey[f.token.Typ], f.token.Val, f.previousToken.Val)
}
f.Output.WriteString(":")
f.scopeLevel = append(f.scopeLevel, 1)
return formatNewLine
}
func formatArray(f *Formatter) stateFn {
if f.next().Val != "<" {
return errorf("expected \"<\" got %s: %s %s\n", lex.Rkey[f.token.Typ], f.token.Val, f.previousToken.Val)
}
f.Output.WriteString("array<")
switch f.next().Typ {
case lex.ItemIdentifier:
for {
f.Output.WriteString(f.token.Val)
if f.peek().Typ != lex.ItemDot {
break
}
f.next()
f.Output.WriteString(".")
if f.peek().Typ != lex.ItemIdentifier {
return errorf("expected Identifier got %s: %s\n", lex.Rkey[f.token.Typ], f.token.Val)
}
f.next()
}
case lex.ItemArray:
return formatArray
default:
return errorf("Bad array syntax")
}
if f.next().Val != ">" {
return errorf("expected \">\" got %s: %s %s\n", lex.Rkey[f.token.Typ], f.token.Val, f.previousToken.Val)
}
for f.peek(); f.nextToken.Val == ">"; {
f.next()
f.Output.WriteString(">")
}
if f.peek().Typ == lex.ItemOperator && f.peek().Val != ">" {
f.Output.WriteString("> ")
} else {
f.Output.WriteString(">")
}
return format
}
func printTab(f *Formatter) {
for _, t := range f.scopeLevel {
for i := 0; i < t; i++ {
f.Output.WriteString("\t")
}
}
}
func isChar(t lex.ItemType) bool {
switch t {
case lex.ItemChar, lex.ItemLeftParen, lex.ItemRightParen, lex.ItemLeftBrace, lex.ItemRightBrace, lex.ItemDot:
return true
default:
return false
}
}
func printChar(f *Formatter) stateFn {
switch f.token.Val {
case ":", ",":
f.Output.WriteString(f.token.Val + " ")
case ";":
f.Output.WriteString(";")
if len(f.scopeLevel) > 0 {
f.scopeLevel[len(f.scopeLevel)-1] = 1
}
return formatNewLine
case "{":
f.Output.WriteString(" {")
f.scopeLevel = append(f.scopeLevel, 1)
return formatNewLine
case "}":
return formatRightBrace
case ".":
printDot(f)
default:
f.Output.WriteString(f.token.Val)
}
return format
}
func printNewline(f *Formatter) {
f.peek()
if f.nextToken.Typ != lex.ItemEOF {
for i := 0; i < f.newlineCount; i++ {
f.Output.WriteString("\n")
}
}
}