Beispiel #1
0
        // Format formats src HCL and returns the result.
        public static slice <byte> Format(slice <byte> src)
        {
            var node = Parser.Parse(src);

            var buf = new GoBuffer();

            DefaultConfig.Fprint(buf, node);

            // Add trailing newline to result
            buf.WriteString("\n");
            return(buf.Bytes());
        }
        /// New creates and initializes a new instance of Scanner using src as
        /// its source content.
        public static Scanner New(slice <byte> src)
        {
            // even though we accept a src, we read from a io.Reader compatible type
            // (*bytes.Buffer). So in the future we might easily change it to streaming
            // read.
            var b = GoBuffer.NewBuffer(src);
            var s = new Scanner
            {
                _buf = b,
                _src = src,
            };

            // srcPosition always starts with 1
            s._srcPos.Line = 1;
            return(s);
        }
        public void TestPosition()
        {
            // create artifical source code
            var buf = new GoBuffer();

            foreach (var listName in orderedTokenLists)
            {
                foreach (var ident in tokenLists[listName])
                {
                    buf.WriteString(string.Format("\t\t\t\t{0}\n", ident.text));
                    //fmt.Fprintf(buf, "\t\t\t\t%s\n", ident.text)
                }
            }

            //System.IO.File.WriteAllText(@"C:\local\prj\bek\zyborg\Zyborg.HCL\test\Zyborg.HCL-tests\ScannerTests-out.txt", buf.Bytes().AsString());

            var s = Scanner.New(buf.Bytes());

            var pos = new Pos("", 4, 1, 5);

            s.Scan();
            foreach (var listName in orderedTokenLists)
            {
                foreach (var k in tokenLists[listName])
                {
                    var curPos = s.TokPos;
                    // fmt.Printf("[%q] s = %+v:%+v\n", k.text, curPos.Offset, curPos.Column)

                    Assert.AreEqual(pos.Offset, curPos.Offset,
                                    "offset = {0}, want {1} for {2}", curPos.Offset, pos.Offset, k.text);
                    Assert.AreEqual(pos.Line, curPos.Line,
                                    "line = {0}, want {1} for {2}", curPos.Line, pos.Line, k.text);
                    Assert.AreEqual(pos.Column, curPos.Column,
                                    "column = {0}, want {1} for {2}", curPos.Column, pos.Column, k.text);

                    pos.Offset += 4 + k.text.Length + 1;     // 4 tabs + token bytes + newline
                    pos.Line   += CountNewlines(k.text) + 1; // each token is on a new line
                    s.Scan();
                }
            }
            // make sure there were no token-internal errors reported by scanner
            Assert.AreEqual(0, s.ErrorCount);
        }
        /// objectItem returns the printable HCL form of an object item. An object type
        /// starts with one/multiple keys and has a value. The value might be of any
        /// type.
        private slice <byte> ObjectItem(ObjectItem o)
        {
            //using (Defer.Call(() => un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text)))))
            {
                var buf = new GoBuffer();

                if (o.LeadComment != null)
                {
                    foreach (var comment in o.LeadComment.List)
                    {
                        buf.WriteString(comment.Text);
                        buf.WriteByte(newline);
                    }
                }

                foreach (var(i, k) in o.Keys.Range())
                {
                    buf.WriteString(k.Token.Text);
                    buf.WriteByte(blank);

                    // reach end of key
                    if (o.Assign.IsValid && (i == o.Keys.Length - 1) && (o.Keys.Length == 1))
                    {
                        buf.WriteString("=");
                        buf.WriteByte(blank);
                    }
                }

                buf.Write(this.Output(o.Val));

                if ((o.Val.Pos().Line == o.Keys[0].Pos().Line) && (o.LineComment != null))
                {
                    buf.WriteByte(blank);
                    foreach (var comment in o.LineComment.List)
                    {
                        buf.WriteString(comment.Text);
                    }
                }

                return(buf.Bytes());
            }
        }
        /// diff compares a and b.
        void Diff(string aname, string bname, slice <byte> a, slice <byte> b)
        {
            var buf = new GoBuffer(); // holding long error message

            // compare lengths
            if (a.Length != b.Length)
            {
                buf.WriteString($"\nlength changed: len({aname}) = {a.Length},"
                                + " len({bname}) = {b.Length}");
            }

            // compare contents
            var line = 1;
            var offs = 1;

            for (var i = 0; i < a.Length && i < b.Length; i++)
            {
                var ch = a[i];
                if (ch != b[i])
                {
                    buf.WriteString(string.Format("\n{0}:{1}:{2}: {3}", aname, line, i - offs + 1,
                                                  LineAt(a, offs)));
                    buf.WriteString(string.Format("\n{0}:{1}:{2}: {3}", bname, line, i - offs + 1,
                                                  LineAt(b, offs)));
                    buf.WriteString("\n\n");
                    break;
                }
                if (ch == '\n')
                {
                    line++;
                    offs = i + 1;
                }
            }

            if (buf.Len() > 0)
            {
                throw new IOException(buf.ToString());
            }
        }
        /// list returns the printable HCL form of an list type.
        private slice <byte> List(ListType l)
        {
            var buf = new GoBuffer();

            buf.WriteString("[");

            var longestLine = 0;

            foreach (var item in l.List)
            {
                // for now we assume that the list only contains literal types
                var lit = item as LiteralType;
                if (lit != null)
                {
                    var lineLen = lit.Token.Text.Length;
                    if (lineLen > longestLine)
                    {
                        longestLine = lineLen;
                    }
                }
            }

            var insertSpaceBeforeItem = false;
            var lastHadLeadComment    = false;

            foreach (var(i, item) in l.List.Range())
            {
                // Keep track of whether this item is a heredoc since that has
                // unique behavior.
                var heredoc = false;
                var lit     = item as LiteralType;
                if (lit != null && lit.Token.Type == TokenType.HEREDOC)
                {
                    heredoc = true;
                }

                if (item.Pos().Line != l.Lbrack.Line)
                {
                    // multiline list, add newline before we add each item
                    buf.WriteByte(newline);
                    insertSpaceBeforeItem = false;

                    // If we have a lead comment, then we want to write that first
                    var leadComment = false;
                    lit = item as LiteralType;
                    if (lit != null && lit.LeadComment != null)
                    {
                        leadComment = true;

                        // If this isn't the first item and the previous element
                        // didn't have a lead comment, then we need to add an extra
                        // newline to properly space things out. If it did have a
                        // lead comment previously then this would be done
                        // automatically.
                        if (i > 0 && !lastHadLeadComment)
                        {
                            buf.WriteByte(newline);
                        }

                        foreach (var comment in lit.LeadComment.List)
                        {
                            buf.Write(this.Indent(comment.Text.AsByteSlice()));
                            buf.WriteByte(newline);
                        }
                    }

                    // also indent each line
                    var val    = this.Output(item);
                    var curLen = val.Length;
                    buf.Write(this.Indent(val));

                    // if this item is a heredoc, then we output the comma on
                    // the next line. This is the only case this happens.
                    var comma = slice.From((byte)',');
                    if (heredoc)
                    {
                        buf.WriteByte(newline);
                        comma = this.Indent(comma);
                    }

                    buf.Write(comma);

                    lit = item as LiteralType;
                    if (lit != null && lit.LineComment != null)
                    {
                        // if the next item doesn't have any comments, do not align
                        buf.WriteByte(blank); // align one space
                        for (var ii = 0; ii < longestLine - curLen; ii++)
                        {
                            buf.WriteByte(blank);
                        }

                        foreach (var comment in lit.LineComment.List)
                        {
                            buf.WriteString(comment.Text);
                        }
                    }

                    var lastItem = i == l.List.Length - 1;
                    if (lastItem)
                    {
                        buf.WriteByte(newline);
                    }

                    if (leadComment && !lastItem)
                    {
                        buf.WriteByte(newline);
                    }

                    lastHadLeadComment = leadComment;
                }
                else
                {
                    if (insertSpaceBeforeItem)
                    {
                        buf.WriteByte(blank);
                        insertSpaceBeforeItem = false;
                    }

                    // Output the item itself
                    // also indent each line
                    var val    = this.Output(item);
                    var curLen = val.Length;
                    buf.Write(val);

                    // If this is a heredoc item we always have to output a newline
                    // so that it parses properly.
                    if (heredoc)
                    {
                        buf.WriteByte(newline);
                    }

                    // If this isn't the last element, write a comma.
                    if (i != l.List.Length - 1)
                    {
                        buf.WriteString(",");
                        insertSpaceBeforeItem = true;
                    }

                    lit = item as LiteralType;
                    if (lit != null && lit.LineComment != null)
                    {
                        // if the next item doesn't have any comments, do not align
                        buf.WriteByte(blank); // align one space
                        for (var ii = 0; ii < longestLine - curLen; ii++)
                        {
                            buf.WriteByte(blank);
                        }

                        foreach (var comment in lit.LineComment.List)
                        {
                            buf.WriteString(comment.Text);
                        }
                    }
                }
            }

            buf.WriteString("]");
            return(buf.Bytes());
        }
        private slice <byte> AlignedItems(slice <ObjectItem> items)
        {
            var buf = new GoBuffer();

            // find the longest key and value length, needed for alignment
            var longestKeyLen = 0; // longest key length
            var longestValLen = 0; // longest value length

            foreach (var item in items)
            {
                var key = item.Keys[0].Token.Text.Length;
                var val = this.Output(item.Val).Length;

                if (key > longestKeyLen)
                {
                    longestKeyLen = key;
                }

                if (val > longestValLen)
                {
                    longestValLen = val;
                }
            }

            foreach (var(i, item) in items.Range())
            {
                if (item.LeadComment != null)
                {
                    foreach (var comment in item.LeadComment.List)
                    {
                        buf.WriteString(comment.Text);
                        buf.WriteByte(newline);
                    }
                }

                foreach (var k in item.Keys)
                {
                    var keyLen = k.Token.Text.Length;
                    buf.WriteString(k.Token.Text);
                    var ii = 0;
                    for (; ii < longestKeyLen - keyLen + 1; ii++)
                    {
                        buf.WriteByte(blank);
                    }

                    // reach end of key
                    if ((ii == item.Keys.Length - 1) && (item.Keys.Length == 1))
                    {
                        buf.WriteString("=");
                        buf.WriteByte(blank);
                    }
                }

                var val    = this.Output(item.Val);
                var valLen = val.Length;
                buf.Write(val);

                if ((item.Val.Pos().Line == item.Keys[0].Pos().Line) && (item.LineComment != null))
                {
                    var ii = 0;
                    for (; ii < longestValLen - valLen + 1; ii++)
                    {
                        buf.WriteByte(blank);
                    }

                    foreach (var comment in item.LineComment.List)
                    {
                        buf.WriteString(comment.Text);
                    }
                }

                // do not print for the last item
                if (i != items.Length - 1)
                {
                    buf.WriteByte(newline);
                }
            }

            return(buf.Bytes());
        }
        /// objectType returns the printable HCL form of an object type. An object type
        /// begins with a brace and ends with a brace.
        private slice <byte> ObjectType(ObjectType o)
        {
            //defer un(trace(p, "ObjectType"))

            var buf = new GoBuffer();

            buf.WriteString("{");

            int  index = 0;
            Pos  nextItem;
            bool commented = false, newlinePrinted = false;

            for (;;)
            {
                // Determine the location of the next actual non-comment
                // item. If we're at the end, the next item is the closing brace
                if (index != o.List.Items.Length)
                {
                    nextItem = o.List.Items[index].Pos();
                }
                else
                {
                    nextItem = o.Rbrace;
                }

                // Go through the standalone comments in the file and print out
                // the comments that we should be for this object item.
                foreach (var c in this.standaloneComments)
                {
                    var printed        = false;
                    var lastCommentPos = new Pos();
                    foreach (var comment in c.List)
                    {
                        // We only care about comments after the previous item
                        // we've printed so that comments are printed in the
                        // correct locations (between two objects for example).
                        // And before the next item.
                        if (comment.Pos().After(this.prev) && comment.Pos().Before(nextItem))
                        {
                            // If there are standalone comments and the initial newline has not
                            // been printed yet, do it now.
                            if (!newlinePrinted)
                            {
                                newlinePrinted = true;
                                buf.WriteByte(newline);
                            }

                            // add newline if it's between other printed nodes
                            if (index > 0)
                            {
                                commented = true;
                                buf.WriteByte(newline);
                            }

                            // Store this position
                            lastCommentPos = comment.Pos();

                            // output the comment itself
                            buf.Write(this.Indent(this.HeredocIndent(comment.Text.AsByteSlice())));

                            // Set printed to true to note that we printed something
                            printed = true;

                            /*
                             *  if index != len(o.List.Items) {
                             *      buf.WriteByte(newline) // do not print on the end
                             *  }
                             */
                        }
                    }

                    // Stuff to do if we had comments
                    if (printed)
                    {
                        // Always write a newline
                        buf.WriteByte(newline);

                        // If there is another item in the object and our comment
                        // didn't hug it directly, then make sure there is a blank
                        // line separating them.
                        if (!object.Equals(nextItem, o.Rbrace) && (nextItem.Line != lastCommentPos.Line + 1))
                        {
                            buf.WriteByte(newline);
                        }
                    }
                }

                if (index == o.List.Items.Length)
                {
                    this.prev = o.Rbrace;
                    break;
                }

                // At this point we are sure that it's not a totally empty block: print
                // the initial newline if it hasn't been printed yet by the previous
                // block about standalone comments.
                if (!newlinePrinted)
                {
                    buf.WriteByte(newline);
                    newlinePrinted = true;
                }

                // check if we have adjacent one liner items. If yes we'll going to align
                // the comments.
                var aligned = slice <ObjectItem> .Empty;
                foreach (var item in o.List.Items.Slice(index))
                {
                    // we don't group one line lists
                    if (o.List.Items.Length == 1)
                    {
                        break;
                    }

                    // one means a oneliner with out any lead comment
                    // two means a oneliner with lead comment
                    // anything else might be something else
                    var cur = Lines(this.ObjectItem(item).AsString());
                    if (cur > 2)
                    {
                        break;
                    }

                    var curPos = item.Pos();

                    var nextPos = new Pos();
                    if (index != o.List.Items.Length - 1)
                    {
                        nextPos = o.List.Items[index + 1].Pos();
                    }

                    var prevPos = new Pos();
                    if (index != 0)
                    {
                        prevPos = o.List.Items[index - 1].Pos();
                    }

                    // fmt.Println("DEBUG ----------------")
                    // fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos)
                    // fmt.Printf("cur = %+v curPos: %s\n", cur, curPos)
                    // fmt.Printf("next = %+v nextPos: %s\n", next, nextPos)

                    if ((curPos.Line + 1) == nextPos.Line)
                    {
                        aligned = aligned.Append(item);
                        index++;
                        continue;
                    }

                    if ((curPos.Line - 1) == prevPos.Line)
                    {
                        aligned = aligned.Append(item);
                        index++;

                        // finish if we have a new line or comment next. This happens
                        // if the next item is not adjacent
                        if ((curPos.Line + 1) != nextPos.Line)
                        {
                            break;
                        }
                        continue;
                    }

                    break;
                }

                // put newlines if the items are between other non aligned items.
                // newlines are also added if there is a standalone comment already, so
                // check it too
                if (!commented && (index != aligned.Length))
                {
                    buf.WriteByte(newline);
                }

                if (aligned.Length >= 1)
                {
                    this.prev = aligned[aligned.Length - 1].Pos();

                    var items = this.AlignedItems(aligned);
                    buf.Write(this.Indent(items));
                }
                else
                {
                    this.prev = o.List.Items[index].Pos();

                    buf.Write(this.Indent(this.ObjectItem(o.List.Items[index])));
                    index++;
                }

                buf.WriteByte(newline);
            }

            buf.WriteString("}");
            return(buf.Bytes());
        }
        /// output prints creates b printable HCL output and returns it.
        private slice <byte> Output(object n)
        {
            var buf = new GoBuffer();

            using (var defer = Defer.Call())
            {
                switch (n)
                {
                case File t:
                    // File doesn't trace so we add the tracing here
                    //defer.Add(() => un(trace(this, "File")));
                    return(Output(t.Node));

                case ObjectList t:
                    //defer.Add(() => un(trace(p, "ObjectList")));

                    int index = 0;
                    for (;;)
                    {
                        // Determine the location of the next actual non-comment
                        // item. If we're at the end, the next item is at "infinity"
                        Pos nextItem;
                        if (index != t.Items.Length)
                        {
                            nextItem = t.Items[index].Pos();
                        }
                        else
                        {
                            nextItem = new Pos {
                                Offset = infinity, Line = infinity
                            };
                        }

                        // Go through the standalone comments in the file and print out
                        // the comments that we should be for this object item.
                        foreach (var c in this.standaloneComments)
                        {
                            // Go through all the comments in the group. The group
                            // should be printed together, not separated by double newlines.
                            var printed        = false;
                            var newlinePrinted = false;
                            foreach (var comment in c.List)
                            {
                                // We only care about comments after the previous item
                                // we've printed so that comments are printed in the
                                // correct locations (between two objects for example).
                                // And before the next item.
                                if (comment.Pos().After(this.prev) && comment.Pos().Before(nextItem))
                                {
                                    // if we hit the end add newlines so we can print the comment
                                    // we don't do this if prev is invalid which means the
                                    // beginning of the file since the first comment should
                                    // be at the first line.
                                    if (!newlinePrinted && this.prev.IsValid && index == t.Items.Length)
                                    {
                                        buf.Write(slice.From(newline, newline));
                                        newlinePrinted = true;
                                    }

                                    // Write the actual comment.
                                    buf.WriteString(comment.Text);
                                    buf.WriteByte(newline);

                                    // Set printed to true to note that we printed something
                                    printed = true;
                                }
                            }

                            // If we're not at the last item, write a new line so
                            // that there is a newline separating this comment from
                            // the next object.
                            if (printed && index != t.Items.Length)
                            {
                                buf.WriteByte(newline);
                            }
                        }

                        if (index == t.Items.Length)
                        {
                            break;
                        }

                        buf.Write(this.Output(t.Items[index]));
                        if (index != t.Items.Length - 1)
                        {
                            // Always write a newline to separate us from the next item
                            buf.WriteByte(newline);

                            // Need to determine if we're going to separate the next item
                            // with a blank line. The logic here is simple, though there
                            // are a few conditions:
                            //
                            //   1. The next object is more than one line away anyways,
                            //      so we need an empty line.
                            //
                            //   2. The next object is not a "single line" object, so
                            //      we need an empty line.
                            //
                            //   3. This current object is not a single line object,
                            //      so we need an empty line.
                            var current = t.Items[index];
                            var next    = t.Items[index + 1];
                            if ((next.Pos().Line != t.Items[index].Pos().Line + 1) ||
                                !this.IsSingleLineObject(next) ||
                                !this.IsSingleLineObject(current))
                            {
                                buf.WriteByte(newline);
                            }
                        }
                        index++;
                    }
                    break;

                case ObjectKey t:
                    buf.WriteString(t.Token.Text);
                    break;

                case ObjectItem t:
                    this.prev = t.Pos();
                    buf.Write(this.ObjectItem(t));
                    break;

                case LiteralType t:
                    buf.Write(this.LiteralType(t));
                    break;

                case ListType t:
                    buf.Write(this.List(t));
                    break;

                case ObjectType t:
                    buf.Write(this.ObjectType(t));
                    break;

                default:
                    Console.WriteLine($"unknown type {n}");
                    break;
                }

                return(buf.Bytes());
            }
        }