| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package formatter |
| |
| import ( |
| "bytes" |
| "fmt" |
| "mojom/mojom_tool/lexer" |
| "mojom/mojom_tool/mojom" |
| "sort" |
| "strings" |
| ) |
| |
| // printer is a single-use struct that keeps track of the state necessary to |
| // pretty-print a mojom file. |
| // It is used by FormatMojom (see formatter.go) |
| // |
| // Internall, the entry point is writeMojomFile and the result is obtained by |
| // calling result. |
| // |
| // The pretty-printing of individual elements is implemented using the write.* |
| // methods. Those methods accept the element to be printed (or the element |
| // of which a portion is to be printed) and write the pretty-printed output |
| // to |buffer|. |
| type printer struct { |
| // used indicates whether the printer has been used or not. If set to true, |
| // writeMojomFile will panic. |
| used bool |
| |
| // buffer is the buffer to which the write.* methods write the pretty-printed |
| // mojom file element. |
| buffer bytes.Buffer |
| |
| // indentSize is the current size of the indentation in number of space runes. |
| indentSize int |
| |
| // eolComment is the comment to be printed at the end of the current line. |
| eolComment *lexer.Token |
| |
| // linePos is the number of runes that have been written to the current line. |
| linePos int |
| |
| // maxLineLength is the maximum number of runes that should be printed on a line. |
| // A negative maxLineLength indicates no maximum line length should be enforced. |
| // This is currently only used to decide when to break up a method on different lines. |
| maxLineLength int |
| |
| // mojomFile is the file being printed. |
| mojomFile *mojom.MojomFile |
| |
| // noFormat indicates we are in a block where formatting has been disabled. |
| noFormat bool |
| |
| // token that begins a block where formatting has been disabled. |
| noFormatStart lexer.Token |
| |
| // indentSize at the time noFormat is set. |
| noFormatStartIndent int |
| } |
| |
| // newPrinter is a constructor for printer. |
| func newPrinter() (p *printer) { |
| p = new(printer) |
| p.maxLineLength = 80 |
| return |
| } |
| |
| // result returns the pretty-printed version of what was given to the printer. |
| // Usually, this is a pretty-printed MojomFile. |
| func (p *printer) result() string { |
| return p.buffer.String() |
| } |
| |
| // writeMojomFile is the entry point of the pretty printer. |
| // It takes a mojom file with attached comments and creates a pretty-printed |
| // representation. |
| func (p *printer) writeMojomFile(mojomFile *mojom.MojomFile) { |
| if p.used { |
| panic("A printer can only be used once!") |
| } |
| p.used = true |
| p.mojomFile = mojomFile |
| |
| p.writeAttributes(mojomFile.Attributes) |
| p.writeModuleNamespace(mojomFile.ModuleNamespace) |
| |
| if len(mojomFile.Imports) > 0 { |
| p.writeImportedFiles(mojomFile.Imports) |
| } |
| |
| for _, declaredObject := range mojomFile.DeclaredObjects { |
| p.writeDeclaredObject(declaredObject) |
| p.nl() |
| } |
| |
| if mojomFile.FinalComments != nil { |
| finalComments := trimEmptyLinesEnd(mojomFile.FinalComments) |
| if len(finalComments) > 0 { |
| p.nl() |
| p.writeCommentBlocks(finalComments, true) |
| } |
| } |
| |
| p.eofNoFormat() |
| } |
| |
| // writeModuleNamespace writes a mojom file's module statement and associated |
| // comments. |
| func (p *printer) writeModuleNamespace(module *mojom.ModuleNamespace) { |
| if module == nil || module.Identifier == "" { |
| return |
| } |
| p.writeBeforeComments(module) |
| p.writef("module %s;", module.Identifier) |
| p.writeRightComments(module) |
| p.nl() |
| } |
| |
| // writeImportedFiles identifies blocks of imports, sorts and writes them. |
| func (p *printer) writeImportedFiles(imports []*mojom.ImportedFile) { |
| blockStart := 0 |
| for i, imported := range imports { |
| if isNewBlock(imported) { |
| p.writeImportedFilesBlock(imports[blockStart:i]) |
| blockStart = i |
| p.nl() |
| } |
| } |
| p.writeImportedFilesBlock(imports[blockStart:len(imports)]) |
| } |
| |
| // writeImportedFilesBlock sorts and writes a slice of import statements. |
| func (p *printer) writeImportedFilesBlock(imports []*mojom.ImportedFile) { |
| sortImportedFiles(imports) |
| for _, imported := range imports { |
| p.writeBeforeComments(imported) |
| p.writef("import \"%v\";", imported.SpecifiedName) |
| p.writeRightComments(imported) |
| p.nl() |
| } |
| } |
| |
| // writeDeclaredObject writes a declared object's attributes, then its |
| // preceeding comments and finally the object itself. |
| func (p *printer) writeDeclaredObject(declaredObject mojom.DeclaredObject) { |
| if isNewBlock(declaredObject) { |
| p.nl() |
| } |
| p.writeDeclaredObjectAttributes(declaredObject) |
| p.writeBeforeComments(declaredObject) |
| switch o := declaredObject.(type) { |
| case *mojom.MojomStruct: |
| p.writeMojomStruct(o) |
| case *mojom.StructField: |
| p.writeStructField(o) |
| case *mojom.MojomUnion: |
| p.writeMojomUnion(o) |
| case *mojom.UnionField: |
| p.writeUnionField(o) |
| case *mojom.MojomEnum: |
| p.writeMojomEnum(o) |
| case *mojom.EnumValue: |
| p.writeEnumValue(o) |
| case *mojom.UserDefinedConstant: |
| p.writeUserDefinedConstant(o) |
| case *mojom.MojomInterface: |
| p.writeMojomInterface(o) |
| case *mojom.MojomMethod: |
| p.writeMojomMethod(o) |
| default: |
| panic(fmt.Sprintf("writeDeclaredObject cannot write %v.", declaredObject)) |
| } |
| } |
| |
| func (p *printer) writeMojomStruct(mojomStruct *mojom.MojomStruct) { |
| p.write("struct ") |
| p.writeDeclaredObjectsContainer(mojomStruct) |
| } |
| |
| func (p *printer) writeMojomUnion(mojomUnion *mojom.MojomUnion) { |
| p.write("union ") |
| p.writeDeclaredObjectsContainer(mojomUnion) |
| } |
| |
| func (p *printer) writeMojomEnum(mojomEnum *mojom.MojomEnum) { |
| p.write("enum ") |
| p.writeDeclaredObjectsContainer(mojomEnum) |
| } |
| |
| func (p *printer) writeMojomInterface(mojomInterface *mojom.MojomInterface) { |
| p.write("interface ") |
| p.writeDeclaredObjectsContainer(mojomInterface) |
| } |
| |
| func (p *printer) writeMojomMethod(mojomMethod *mojom.MojomMethod) { |
| splitResponse := false |
| if p.maxLineLength > 0 { |
| scratch := newPrinter() |
| scratch.maxLineLength = -1 |
| scratch.writeMojomMethod(mojomMethod) |
| if len(scratch.result())+p.lineLength() > p.maxLineLength { |
| splitResponse = true |
| } |
| } |
| |
| p.write(mojomMethod.NameToken().Text) |
| if mojomMethod.DeclaredOrdinal() >= 0 { |
| p.writef("@%v", mojomMethod.DeclaredOrdinal()) |
| } |
| |
| p.writeMethodParams(mojomMethod.Parameters, mojomMethod) |
| if mojomMethod.ResponseParameters != nil { |
| if splitResponse { |
| p.nl() |
| p.write(" ") |
| } |
| p.write(" => ") |
| p.writeMethodParams(mojomMethod.ResponseParameters, mojomMethod) |
| } |
| p.write(";") |
| p.writeRightComments(mojomMethod) |
| } |
| |
| // writeMethodParams writes the pretty-printed method parameters represented by |
| // a MojomStruct. |
| func (p *printer) writeMethodParams(params *mojom.MojomStruct, method *mojom.MojomMethod) { |
| // The presence or absence of a semi-colon needs to be taken into account when |
| // trying to enforce the line size limit. |
| semiColon := false |
| if params.StructType() == mojom.StructTypeSyntheticResponse || method.ResponseParameters == nil { |
| semiColon = true |
| } |
| ownLine := false |
| if p.maxLineLength > 0 { |
| scratch := newPrinter() |
| scratch.maxLineLength = -1 |
| scratch.writeMethodParams(params, method) |
| scratchLineLen := len(scratch.result()) + p.lineLength() |
| // If a semi-colon will be added after the parameters, account for it. |
| if semiColon { |
| scratchLineLen += 1 |
| } |
| if scratchLineLen > p.maxLineLength { |
| ownLine = true |
| } |
| } |
| p.write("(") |
| declaredObjects := params.GetDeclaredObjects() |
| |
| extraIndent := p.lineLength() - p.indentSize |
| if ownLine { |
| // If we are printing every parameter on its own line, check to see if |
| // aligning the parameters to the right of the method name can be done. |
| for i, param := range declaredObjects { |
| scratch := newPrinter() |
| scratch.maxLineLength = -1 |
| scratch.writeMethodParam(param.(*mojom.StructField)) |
| // If any parameter would go beyond the maximum line length, start the |
| // list of parameters on the line after the method name and indented 4 |
| // more than the method itself. |
| scratchLineLen := len(scratch.result()) + p.lineLength() |
| // If this is the last parameter, account for the closing parenthesis. |
| if i == len(declaredObjects)-1 { |
| scratchLineLen += 1 |
| // If a semi-colo will be added after the parameters, account for it. |
| if semiColon { |
| scratchLineLen += 1 |
| } |
| } |
| if scratchLineLen > p.maxLineLength { |
| p.nl() |
| extraIndent = 4 |
| break |
| } |
| } |
| |
| p.indentSize += extraIndent |
| } |
| |
| for i, param := range declaredObjects { |
| p.writeMethodParam(param.(*mojom.StructField)) |
| if i < len(declaredObjects)-1 { |
| p.write(",") |
| if ownLine { |
| p.nl() |
| } else { |
| p.write(" ") |
| } |
| } |
| } |
| |
| if ownLine { |
| p.indentSize -= extraIndent |
| } |
| p.write(")") |
| } |
| |
| // writeMethodParam writes a single pretty-printed method parameter represented |
| // by a StructField. |
| func (p *printer) writeMethodParam(param *mojom.StructField) { |
| p.writeAttributesSingleLine(param.Attributes()) |
| p.writeBeforeComments(param) |
| p.writeTypeRef(param.FieldType) |
| p.writef(" %v", param.NameToken().Text) |
| if param.DeclaredOrdinal() >= 0 { |
| p.writef("@%v", param.DeclaredOrdinal()) |
| } |
| if param.DefaultValue != nil { |
| p.write("=") |
| p.writeValueRef(param.DefaultValue) |
| } |
| p.writeRightComments(param) |
| } |
| |
| func (p *printer) writeUserDefinedConstant(constant *mojom.UserDefinedConstant) { |
| p.write("const ") |
| p.writeTypeRef(constant.DeclaredType()) |
| p.writef(" %s", constant.NameToken().Text) |
| p.write(" = ") |
| p.writeValueRef(constant.ValueRef()) |
| p.write(";") |
| p.writeRightComments(constant) |
| } |
| |
| func (p *printer) writeStructField(structField *mojom.StructField) { |
| p.writeTypeRef(structField.FieldType) |
| p.writef(" %v", structField.NameToken().Text) |
| if structField.DeclaredOrdinal() >= 0 { |
| p.writef("@%v", structField.DeclaredOrdinal()) |
| } |
| if structField.DefaultValue != nil { |
| p.write(" = ") |
| p.writeValueRef(structField.DefaultValue) |
| } |
| p.write(";") |
| p.writeRightComments(structField) |
| } |
| |
| func (p *printer) writeUnionField(unionField *mojom.UnionField) { |
| p.writeTypeRef(unionField.FieldType) |
| p.writef(" %v", unionField.NameToken().Text) |
| if unionField.DeclaredOrdinal() >= 0 { |
| p.writef("@%v", unionField.DeclaredOrdinal()) |
| } |
| p.write(";") |
| p.writeRightComments(unionField) |
| } |
| |
| func (p *printer) writeEnumValue(enumValue *mojom.EnumValue) { |
| p.write(enumValue.NameToken().Text) |
| if enumValue.ValueRef() != nil { |
| p.write(" = ") |
| p.writeValueRef(enumValue.ValueRef()) |
| } |
| p.write(",") |
| p.writeRightComments(enumValue) |
| } |
| |
| // writeDeclaredObjectsContainer |
| // DeclaredObjectsContainers are MojomEnum, MojomUnion, MojomInterface, MojomStruct. |
| // This method writes the name of the object, an opening brace, the contained |
| // declarations (fields, enum values, nested declarations and methods) and the |
| // closing brace. |
| func (p *printer) writeDeclaredObjectsContainer(container mojom.DeclaredObjectsContainer) { |
| |
| p.writef("%v {", container.(mojom.DeclaredObject).NameToken().Text) |
| if len(container.GetDeclaredObjects()) == 0 && !containsFinalComments(container.(mojom.MojomElement)) { |
| p.writef("};") |
| return |
| } |
| p.writeRightComments(container.(mojom.MojomElement)) |
| p.incIndent() |
| p.nl() |
| declaredObjects := container.GetDeclaredObjects() |
| for i, declaredObject := range declaredObjects { |
| if i == 0 { |
| trimEmptyLinesDeclaredObject(declaredObject) |
| } |
| p.writeDeclaredObject(declaredObject) |
| if i < len(declaredObjects)-1 { |
| p.nl() |
| } |
| } |
| |
| // Write the comments at the end of the struct, enum, union or interface body. |
| p.writeFinalComments(container) |
| |
| p.decIndent() |
| p.nl() |
| p.write("};") |
| } |
| |
| func (p *printer) writeDeclaredObjectAttributes(declaredObject mojom.DeclaredObject) { |
| switch declaredObject.(type) { |
| case *mojom.MojomStruct: |
| p.writeAttributes(declaredObject.Attributes()) |
| case *mojom.MojomUnion: |
| p.writeAttributes(declaredObject.Attributes()) |
| case *mojom.MojomEnum: |
| p.writeAttributes(declaredObject.Attributes()) |
| case *mojom.UserDefinedConstant: |
| p.writeAttributes(declaredObject.Attributes()) |
| case *mojom.MojomInterface: |
| p.writeAttributes(declaredObject.Attributes()) |
| case *mojom.MojomMethod: |
| p.writeAttributes(declaredObject.Attributes()) |
| case *mojom.StructField: |
| p.writeAttributesSingleLine(declaredObject.Attributes()) |
| case *mojom.UnionField: |
| p.writeAttributesSingleLine(declaredObject.Attributes()) |
| case *mojom.EnumValue: |
| p.writeAttributesSingleLine(declaredObject.Attributes()) |
| default: |
| panic(fmt.Sprintf("writeDeclaredObjectAttributes cannot write %v.", declaredObject)) |
| } |
| } |
| |
| // See writeAttributesBase. |
| func (p *printer) writeAttributes(attrs *mojom.Attributes) { |
| p.writeAttributesBase(attrs, false) |
| } |
| |
| // writeAttributesSingleLine writes the provided attributes on a single line. |
| // This is used by the writeMethodParam to write the attributes of a parameter. |
| func (p *printer) writeAttributesSingleLine(attrs *mojom.Attributes) { |
| p.writeAttributesBase(attrs, true) |
| } |
| |
| // writeAttributesBase writes the provided attributes. If singleLine is |
| // false, every attribute is written on a new line and a new line is appended |
| // after the attributes are written. Otherwise, the attributes are written on |
| // a single line. |
| func (p *printer) writeAttributesBase(attrs *mojom.Attributes, singleLine bool) { |
| if attrs == nil { |
| return |
| } |
| |
| p.writeBeforeComments(attrs) |
| |
| if len(attrs.List) == 0 { |
| return |
| } |
| |
| p.writef("[") |
| for idx, attr := range attrs.List { |
| p.writeAttribute(&attr) |
| if idx < len(attrs.List)-1 { |
| p.writef(",") |
| if !singleLine { |
| p.nl() |
| } |
| p.write(" ") |
| } |
| } |
| p.writef("]") |
| if singleLine { |
| p.write(" ") |
| } else { |
| p.nl() |
| } |
| } |
| |
| func (p *printer) writeAttribute(attr *mojom.MojomAttribute) { |
| p.writeBeforeComments(attr) |
| p.writef("%s=", attr.Key) |
| p.writeValueRef(attr.Value) |
| p.writeRightComments(attr) |
| } |
| |
| func (p *printer) writeTypeRef(t mojom.TypeRef) { |
| switch t.TypeRefKind() { |
| case mojom.TypeKindUserDefined: |
| u := t.(*mojom.UserTypeRef) |
| p.write(u.Identifier()) |
| if u.IsInterfaceRequest() { |
| p.write("&") |
| } |
| if u.Nullable() { |
| p.write("?") |
| } |
| case mojom.TypeKindArray: |
| a := t.(*mojom.ArrayTypeRef) |
| p.write("array<") |
| p.writeTypeRef(a.ElementType()) |
| if a.FixedLength() >= 0 { |
| p.writef(", %v", a.FixedLength()) |
| } |
| p.write(">") |
| if a.Nullable() { |
| p.write("?") |
| } |
| case mojom.TypeKindMap: |
| m := t.(*mojom.MapTypeRef) |
| p.write("map<") |
| p.writeTypeRef(m.KeyType()) |
| p.write(", ") |
| p.writeTypeRef(m.ValueType()) |
| p.write(">") |
| if m.Nullable() { |
| p.write("?") |
| } |
| case mojom.TypeKindHandle: |
| fallthrough |
| case mojom.TypeKindSimple: |
| fallthrough |
| case mojom.TypeKindString: |
| p.write(t.String()) |
| default: |
| panic("This unhandled TypeRefKind: " + t.String()) |
| } |
| } |
| |
| func (p *printer) writeValueRef(value mojom.ValueRef) { |
| switch v := value.(type) { |
| case mojom.LiteralValue: |
| // The sign of a numeric literal is a separate token from the number itself. |
| // So the token that is stored does not include a sign. Here we check if |
| // the literal is a signed constant and if so, we print a negative sign if |
| // appropriate. |
| if _, ok := v.ValueType().(mojom.LiteralType); ok { |
| negative := false |
| switch num := v.Value().(type) { |
| case int8: |
| negative = num < 0 |
| case int16: |
| negative = num < 0 |
| case int32: |
| negative = num < 0 |
| case int64: |
| negative = num < 0 |
| case float32: |
| negative = num < 0 |
| case float64: |
| negative = num < 0 |
| } |
| if negative { |
| p.write("-") |
| } |
| } |
| if v.Token() == nil { |
| panic(fmt.Sprintf("nil token when trying to print %s.", v.String())) |
| } |
| p.write(v.Token().Text) |
| case *mojom.UserValueRef: |
| p.write(v.Identifier()) |
| default: |
| panic("Cannot handle this value ref.") |
| } |
| } |
| |
| // writeBeforeComments writes the comments preceeding a MojomElement. |
| func (p *printer) writeBeforeComments(el mojom.MojomElement) { |
| attachedComments := el.AttachedComments() |
| if attachedComments == nil { |
| return |
| } |
| |
| p.writeAboveComments(el) |
| p.writeLeftComments(el) |
| } |
| |
| // writeAboveComments writes the comments above of a MojomElement. |
| func (p *printer) writeAboveComments(el mojom.MojomElement) { |
| attachedComments := el.AttachedComments() |
| if attachedComments == nil { |
| return |
| } |
| |
| p.writeCommentBlocks(attachedComments.Above, true) |
| } |
| |
| func (p *printer) writeFinalComments(container mojom.DeclaredObjectsContainer) { |
| el := container.(mojom.MojomElement) |
| attachedComments := el.AttachedComments() |
| if attachedComments == nil { |
| return |
| } |
| finalComments := trimEmptyLinesEnd(attachedComments.Final) |
| if len(finalComments) == 0 { |
| return |
| } |
| |
| // Only print blank lines if there is something other than comments in the |
| // container. |
| if len(container.GetDeclaredObjects()) > 0 { |
| p.nl() |
| if finalComments[0].Kind == lexer.EmptyLine { |
| p.nl() |
| } |
| } |
| p.writeCommentBlocks(finalComments, false) |
| } |
| |
| // writeLeftComments writes the comments left of a MojomElement. |
| func (p *printer) writeLeftComments(el mojom.MojomElement) { |
| attachedComments := el.AttachedComments() |
| if attachedComments == nil { |
| return |
| } |
| |
| for _, comment := range attachedComments.Left { |
| if comment.Kind == lexer.SingleLineComment { |
| panic("SingleLineComment cannot be on the left of an element.") |
| } |
| |
| p.writef("%s ", comment.Text) |
| } |
| } |
| |
| // writeRightComments writes the comments to the right of a MojomElement. |
| func (p *printer) writeRightComments(el mojom.MojomElement) { |
| attachedComments := el.AttachedComments() |
| if attachedComments == nil { |
| return |
| } |
| |
| for i, comment := range attachedComments.Right { |
| if comment.Kind == lexer.SingleLineComment { |
| if i < len(attachedComments.Right)-1 { |
| panic("You can't have anything after a SingleLineComment!") |
| } |
| p.setEolComment(comment) |
| break |
| } |
| p.writef(" %s", comment.Text) |
| } |
| } |
| |
| // writeCommentBlocks writes a slice of comments. If finalEol is true, a new |
| // line is written after all the comments are written. |
| func (p *printer) writeCommentBlocks(comments []lexer.Token, finalEol bool) { |
| for i, comment := range comments { |
| switch comment.Kind { |
| case lexer.SingleLineComment: |
| if comment.Text == "// no-format" { |
| p.startNoFormat(comment) |
| } else if comment.Text == "// end-no-format" { |
| p.endNoFormat(comment) |
| } |
| p.writeSingleLineComment(comment) |
| if finalEol || i < len(comments)-1 { |
| p.nl() |
| } |
| case lexer.MultiLineComment: |
| p.writeMultiLineComment(comment) |
| if finalEol || i < len(comments)-1 { |
| p.nl() |
| } |
| case lexer.EmptyLine: |
| // We only use EmptyLine here to separate comment blocks. And we collapse |
| // sequences of empty lines to a single empty line. |
| if i != 0 && comments[i-1].Kind != lexer.EmptyLine { |
| p.nl() |
| } |
| default: |
| panic(fmt.Sprintf("%s is not a comment.", comment)) |
| } |
| } |
| } |
| |
| func (p *printer) writeSingleLineComment(comment lexer.Token) { |
| if comment.Kind != lexer.SingleLineComment { |
| panic(fmt.Sprintf("This is not a SingleLineComment: %s", comment)) |
| } |
| commentText := comment.Text |
| |
| // We expect that the first 2 characters are // followed by a space or tab. |
| // If the third character is not a space or tab, we insert a space. |
| // There is an exception for three forward slashes which are allowed. |
| if len(commentText) > 2 && !strings.ContainsAny(" \t/", commentText[2:3]) { |
| commentText = "// " + commentText[2:] |
| } |
| p.write(commentText) |
| } |
| |
| func (p *printer) writeMultiLineComment(comment lexer.Token) { |
| if comment.Kind != lexer.MultiLineComment { |
| panic(fmt.Sprintf("This is not a MultiLineComment: %s", comment)) |
| } |
| |
| lines := strings.Split(comment.Text, "\n") |
| for i, line := range lines { |
| trimmed := strings.Trim(line, " \t") |
| // If a line starts with * we assume the user is trying to use the pattern |
| // whereby they align the comment with spaces after the *. Otherwise, |
| // we try to align the comment by prepending 3 spaces. |
| if i != 0 && len(trimmed) > 0 { |
| if trimmed[0] == '*' { |
| p.write(" ") |
| } else { |
| p.write(" ") |
| } |
| } |
| |
| if p.lineLength()+len(trimmed) <= p.maxLineLength || p.maxLineLength < 0 { |
| p.write(trimmed) |
| } else { |
| // If the comment is too long to fit it on a line, break up the line along |
| // spaces and wrap the line. |
| words := strings.Split(trimmed, " ") |
| for i, word := range words { |
| if i > 0 && len(word)+p.lineLength()+1 > p.maxLineLength { |
| p.nl() |
| if trimmed[0] == '*' { |
| p.write(" *") |
| } else { |
| p.write(" ") |
| } |
| } |
| if i > 0 { |
| p.write(" ") |
| } |
| p.write(word) |
| } |
| } |
| if i < len(lines)-1 { |
| p.nl() |
| } |
| } |
| } |
| |
| // Utility functions |
| |
| // writef writes according to the format specifier. See fmt.Printf for the |
| // format specifier. |
| func (p *printer) writef(format string, a ...interface{}) { |
| p.write(fmt.Sprintf(format, a...)) |
| } |
| |
| // write writes the provided string to the buffer. |
| // If nothing has been written yet on the current line write also writes the |
| // current indentation level. |
| func (p *printer) write(s string) { |
| if strings.ContainsRune(s, '\n') { |
| panic(fmt.Sprintf("Only the nl method can write a new line: %q", s)) |
| } |
| |
| // We only print the indentation if the line is not empty. |
| if p.linePos == 0 { |
| if !p.noFormat { |
| p.buffer.WriteString(strings.Repeat(" ", p.indentSize)) |
| } |
| p.linePos += p.indentSize |
| } |
| p.linePos += len(s) |
| if !p.noFormat { |
| p.buffer.WriteString(s) |
| } |
| } |
| |
| // nl writes a new line. Before writing the new line, nl writes the last |
| // comment on the line. |
| func (p *printer) nl() { |
| // Before going to the next line, print the last comment on the line. |
| if p.eolComment != nil { |
| if !p.noFormat { |
| p.write(" ") |
| p.writeSingleLineComment(*p.eolComment) |
| } |
| p.eolComment = nil |
| } |
| |
| if !p.noFormat { |
| p.buffer.WriteString("\n") |
| } |
| p.linePos = 0 |
| } |
| |
| func (p *printer) incIndent() { |
| p.indentSize += 2 |
| } |
| |
| func (p *printer) decIndent() { |
| p.indentSize -= 2 |
| if p.indentSize < 0 { |
| panic("The printer is attempting to use negative indentation!") |
| } |
| } |
| |
| // setEolComment sets the comment that is to be printed at the end of the current line. |
| // The last comment on the line is not necessarily associated with the last |
| // element on the line. So when the last comment is found, we record that |
| // comment so we may print it right before going to the next line. |
| func (p *printer) setEolComment(comment lexer.Token) { |
| if p.eolComment != nil { |
| panic("There is space for only one comment at the end of the line!") |
| } |
| |
| if comment.Kind != lexer.SingleLineComment { |
| panic("Only SingleLineComments need to be handled specially at the end of line.") |
| } |
| |
| p.eolComment = &comment |
| } |
| |
| // lineLength returns the length of the line so far. If nothing has been written |
| // to the line yet, it returns the indent size. |
| // The purpose of lineLenght is to answer the question: If I write something now |
| // how far on the current line will it be written? |
| func (p *printer) lineLength() int { |
| if p.linePos == 0 { |
| return p.indentSize |
| } |
| return p.linePos |
| } |
| |
| // startNoFormat disables writes to the buffer (future writes are discarded). |
| func (p *printer) startNoFormat(startToken lexer.Token) { |
| if p.noFormat { |
| return |
| } |
| |
| p.noFormat = true |
| p.noFormatStart = startToken |
| p.noFormatStartIndent = p.indentSize |
| } |
| |
| // endNoFormat re-enables writes to the buffer and writes the source starting |
| // at the beginning of p.startToken and ending right before endToken. |
| func (p *printer) endNoFormat(endToken lexer.Token) { |
| if !p.noFormat { |
| return |
| } |
| |
| fileContents := p.mojomFile.FileContents() |
| notFormatted := fileContents[p.noFormatStart.SourcePosBytes:endToken.SourcePosBytes] |
| |
| // Remove any unformatted indentation right before the end of the unformatted block. |
| notFormatted = strings.TrimRight(notFormatted, " \t") |
| p.buffer.WriteString(strings.Repeat(" ", p.noFormatStartIndent)) |
| p.buffer.WriteString(notFormatted) |
| p.noFormat = false |
| } |
| |
| // eofNoFormat handles the case where formatting had been disabled but not |
| // re-enabled. It writes everything after the formatting was ended. |
| func (p *printer) eofNoFormat() { |
| if !p.noFormat { |
| return |
| } |
| |
| fileContents := p.mojomFile.FileContents() |
| p.endNoFormat(lexer.Token{SourcePosBytes: len(fileContents)}) |
| } |
| |
| // Standalone utilities that do not operate on the buffer. |
| |
| // isNewBlock determines if an empty line should be printed above the element |
| // under consideration. The purpose is to respect user-intention to separate |
| // elements in a mojom file. |
| // |
| // If the element has attributes, and the first "comment" attached above the |
| // attributes is an empty line, the element is a new block. |
| // If the element has no attributes and the first "comment" attached above the |
| // element itself is an empty line, the element is a new block. |
| // Else, the element is not a new block. |
| func isNewBlock(mojomElement mojom.MojomElement) bool { |
| if declaredObject, ok := mojomElement.(mojom.DeclaredObject); ok { |
| if declaredObject.Attributes() != nil { |
| comments := declaredObject.Attributes().AttachedComments() |
| if comments == nil || len(comments.Above) == 0 { |
| return false |
| } |
| return comments.Above[0].Kind == lexer.EmptyLine |
| } |
| } |
| comments := mojomElement.AttachedComments() |
| |
| if comments == nil || len(comments.Above) == 0 { |
| return false |
| } |
| return comments.Above[0].Kind == lexer.EmptyLine |
| } |
| |
| // If the element has AttachedComments and there is at least one comment |
| // in AttachedComments.Final which is not an EmptyLine, containsFinalComments |
| // returns true. Otherwise, it returns false. |
| func containsFinalComments(el mojom.MojomElement) bool { |
| attachedComments := el.AttachedComments() |
| if attachedComments == nil { |
| return false |
| } |
| |
| for _, comment := range attachedComments.Final { |
| if comment.Kind != lexer.EmptyLine { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // trimEmptyLinesEnd trims out empty line comments at the end of a slice of comments. |
| func trimEmptyLinesEnd(comments []lexer.Token) []lexer.Token { |
| lastNonEmpty := -1 |
| for i, comment := range comments { |
| if comment.Kind != lexer.EmptyLine { |
| lastNonEmpty = i |
| } |
| } |
| return comments[:lastNonEmpty+1] |
| } |
| |
| // trimEmptyLinesBegin trims out empty line comments at the beginning of a slice of comments. |
| func trimEmptyLinesBegin(comments []lexer.Token) []lexer.Token { |
| for i, comment := range comments { |
| if comment.Kind != lexer.EmptyLine { |
| return comments[i:] |
| } |
| } |
| return comments[len(comments):] |
| } |
| |
| // trimEmptyLinesDeclaredObject trims out empty lines from the beginning of the |
| // comments on a DeclaredObject or its Attributes. |
| func trimEmptyLinesDeclaredObject(declaredObject mojom.DeclaredObject) { |
| if declaredObject.Attributes() != nil { |
| comments := declaredObject.Attributes().AttachedComments() |
| if comments == nil { |
| return |
| } |
| comments.Above = trimEmptyLinesBegin(comments.Above) |
| return |
| } |
| comments := declaredObject.AttachedComments() |
| |
| if comments == nil || len(comments.Above) == 0 { |
| return |
| } |
| comments.Above = trimEmptyLinesBegin(comments.Above) |
| } |
| |
| // Following is a utility to sort slices of |ImportedFile|s. |
| |
| // sortImportedFiles sorts the slice of imported files it receives. |
| func sortImportedFiles(imports []*mojom.ImportedFile) { |
| sort.Sort(&importedFilesSorter{imports}) |
| } |
| |
| type importedFilesSorter struct { |
| imports []*mojom.ImportedFile |
| } |
| |
| // See sort.Interface. |
| func (ifs *importedFilesSorter) Len() int { |
| return len(ifs.imports) |
| } |
| |
| // See sort.Interface. |
| func (ifs *importedFilesSorter) Less(i, j int) bool { |
| return ifs.imports[i].SpecifiedName < ifs.imports[j].SpecifiedName |
| } |
| |
| // See sort.Interface. |
| func (ifs *importedFilesSorter) Swap(i, j int) { |
| ifs.imports[i], ifs.imports[j] = ifs.imports[j], ifs.imports[i] |
| } |