SquirrelJME/SquirrelJME

View on GitHub
modules/c-source-writer/src/main/java/cc/squirreljme/c/out/PrettyCTokenOutput.java

Summary

Maintainability
A
35 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// Multi-Phasic Applications: SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.c.out;

import cc.squirreljme.runtime.cldc.debug.Debugging;
import java.io.IOException;

/**
 * Wraps the output and makes it very pretty.
 *
 * @since 2023/06/19
 */
public class PrettyCTokenOutput
    extends __FormattedCTokenOutput__
{
    /** The tab size. */
    private static final int _TAB_SIZE =
        4;
    
    /** The column limit. */
    private static final int _GUTTER =
        69;
    
    /** Current indentation level. */
    private volatile int _indent;
    
    /** Did the indentation level change? */
    private volatile boolean _indentChanged;
    
    /** Is a post newline pending? */
    private volatile boolean _pendingPostNewline;
    
    /**
     * Initializes the output wrapper.
     * 
     * @param __wrap The output to wrap.
     * @throws NullPointerException On null arguments.
     * @since 2023/06/19
     */
    public PrettyCTokenOutput(CTokenOutput __wrap)
        throws NullPointerException
    {
        super(__wrap);
    }
    
    /**
     * {@inheritDoc}
     * @since 2023/06/22
     */
    @Override
    public void close()
        throws IOException
    {
        this.out.close();
    }
    
    /**
     * {@inheritDoc}
     * @since 2023/06/22
     */
    @Override
    public void indent(int __adjust)
    {
        // Adjust indentation level
        int lastIndent = this._indent;
        this._indent = Math.max(0, this._indent + __adjust);
        
        // Did the indentation actually change?
        this._indentChanged = (lastIndent != this._indent);
    }
    
    /**
     * {@inheritDoc}
     * @since 2023/07/21
     */
    @Override
    public void newLine(boolean __force)
        throws IOException
    {
        super.newLine(__force);
        
        // We newlined, so forget that indentation changed
        this._indentChanged = false;
    }
    
    /**
     * {@inheritDoc}
     * @since 2023/06/22
     */
    @Override
    public void token(CharSequence __cq, boolean __forceNewline)
        throws IOException, NullPointerException
    {
        if (__cq == null)
            throw new NullPointerException("NARG");
        
        // Specifically handle single character tokens
        int len = __cq.length();
        char first = (len == 0 ? 0 : __cq.charAt(0));
        char second = (len <= 1 ? 0 : __cq.charAt(1));
        char last = (len == 0 ? 0 : __cq.charAt(len - 1));
        
        // Always indent around these characters
        boolean alwaysIndent = ((len == 1 && (first == '{' || first == '}')) ||
            (len == 2 && (first == '}' && second == ';')));
        
        // Do we hug the line start with this?
        boolean hugLineStart = (first == '#' ||
            (first == '/' && second == '/'));
        
        // Do not always indent if the last characters are these, for
        // making formatting better...
        boolean forgetIndent = (last == ',' || last == ';' || last == '.' ||
            last == ':' || last == ')');
        
        // If we were pending a post newline, emit it now accordingly
        if (this._pendingPostNewline)
        {
            // Emit newline, as long as we are not forgetting the indent
            if (!forgetIndent)
                this.newLine(true);
            
            // Clear
            this._pendingPostNewline = false;
        }
        
        // If column is past the gutter, start on a fresh line
        // Note that forgetting indent can override this!
        // But do emit a newline if our indentation actually changed
        if ((!forgetIndent && this.column >= PrettyCTokenOutput._GUTTER) ||
            (this._indentChanged))
            this.newLine(true);
        
        // Newline before indentation, as long as we are not on the first
        // column
        if (alwaysIndent && !forgetIndent && this.column > 0)
            this.newLine(true);
        
        // As long as we are not going to hug the line start
        if (this.column == 0 && !hugLineStart)
            for (int i = 0, n = this._indent; i < n; i++)
            {
                this.out.tab();
                this.column += PrettyCTokenOutput._TAB_SIZE;
            }
            
        // Emit token
        super.token(__cq, __forceNewline);
        
        // Always add indentation after this token, put post it instead of
        // actually adding it.
        if (alwaysIndent)
            this._pendingPostNewline = true;
    }
}