Daniel Keep /
shfmt
Difference (previous author) (Change, Edit, normal page display)
Changed: 1c1,339
Describe the new page here. |
A Shell/PHP-style formatter using inline variable expansion. See down the bottom of the file for an example. [[code]/** * Shell/PHP style formatter. * Written by Daniel Keep, 2007. * Released under the BSDv2 license. */ /* Copyright © 2007, Daniel Keep All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ module shfmt; /* * BNF for expansions: * * Expansion ::= "$" ExpansionSpec? * * ExpansionSpec? ::= FormattingOptions? ExpansionExpr? * ExpansionSpec? ::= ExpansionExpr? * * FormattingOptions? ::= "(" string ")" * * ExpansionExpr? ::= "{" {D Expression}? "}" * ExpansionExpr? ::= {D Identifier}? */ import std.stdio : writefln; import std.string : format; /** * Determines if the given character is a valid D identifier character. If it * must be a valid starting character, you should specify first as true. * * Compatible with CTFE. */ private bool isIdentChar(dchar c, bool first=false) { if( 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || 0xa0 <= c && c < 0xd800 || 0xdfff < c || !first && ('0' <= c && c <= '9') ) return true; else return false; } /** * Expands the given format string. * * Compatible with CTFE. */ char[] _F(char[] fs) { return "_F_join(" ~ _F_raw(fs) ~ ")"; } /** * List of internal states _F_raw can be in. */ private enum FState { Default, /// just spitting stuff out PostDollar?, /// immediately after a '$' Options, /// inside format options (...) PostOptions?, /// immediately after format options Expression, /// inside a complex expression {...} Varname /// a regular identifier } /** * Expands the given format string into a comma-delimited list of string * components. * * Compatible with CTFE. */ char[] _F_raw(char[] fs) { char[] result; /// result accumulator FState state; /// current state /// This is used to track how many nested braces deep we are within a /// complex expression we are. It gets incremented when we see a '{', and /// decremented when we see a '}'. Once depth == 0, we're finished. uint depth; /// Used to build up the formatting options list. If this is empty, it is /// equivalent to "s". char[] opt; /* Assume the first component is a literal. Empty strings are basically * harmless. */ result ~= "\""; /* BUG: this should iterate over the format string using dchars, but * automatic unpacking isn't supported in CTFE as of dmd v1.014. */ foreach( char c ; fs ) { // Default state if( state == FState.Default ) { if( c == '$' ) // Start of a variable expansion state = FState.PostDollar?; else if( c == '"' ) { // Make sure to escape double quotes. result ~= '\\'; result ~= c; } else // Just output this character literally. result ~= c; } // Start of a variable expansion else if( state == FState.PostDollar? ) { if( c == '$' ) { // Escaped '$' state = FState.Default; result ~= c; } else if( c == '(' ) // Start of option list state = FState.Options; else if( c == '{' ) { // Start of a complex expression state = FState.Expression; depth = 1; result ~= "\",format(\"%s\",("; } else if( isIdentChar(c, true) ) { // Start of a simple identifier state = FState.Varname; result ~= "\",format(\"%s\",("; result ~= c; } else { /* Something else; just output the $ and this character * literally. */ state = FState.Default; result ~= "$"; result ~= c; } } // Inside format options (...) else if( state == FState.Options ) { if( c == ')' ) // That's all, folks! state = FState.PostOptions?; else // Append character to options opt ~= c; } // Immediately after format options else if( state == FState.PostOptions? ) { if( c == '{' ) { // Starting a complex expression state = FState.Expression; depth = 1; if( opt.length == 0 ) opt = "s"; result ~= "\",format(\"%"~opt~"\",("; opt = null; } else if( isIdentChar(c, true) ) { // Just a regular identifier state = FState.Varname; if( opt.length == 0 ) opt = "s"; result ~= "\",format(\"%"~opt~"\",("; opt = null; result ~= c; } else // Malformed format string assert(false, "Invalid format string: "~fs); } // Within a complex expression else if( state == FState.Expression ) { if( c == '{' ) { // Nest another level ++depth; result ~= c; } else if( c == '}' ) { // Leaving current level. --depth; if( depth == 0 ) { // We just closed the initial opening brace; we're done state = FState.Default; result ~= ")),\""; } else result ~= c; } else result ~= c; } // A standard identifier else if( state == FState.Varname ) { if( isIdentChar(c) ) { result ~= c; } else { state = FState.Default; result ~= ")),\""; result ~= c; } } else // Unknown state; now how'd that happen? assert(false); } if( state == FState.Default ) // Close current literal string result ~= "\""; else if( state == FState.PostDollar? ) // Format string ended with a '$': that's cool. result ~= "$\""; else if( state == FState.Varname ) // Finish the ending variable expansion result ~= "))"; else // Malformed format string assert(false); return result; } /** * This function takes several separate strings and joins them together * efficiently. */ char[] _F_join(char[][] parts...) { char[] result; uint acc; // Work out how much room we'll need foreach( part ; parts ) acc += part.length; if( acc == 0 ) return null; // Stuff the parts together into the result result.length = acc; acc = 0; foreach( part ; parts ) { if( part.length == 0 ) continue; result[acc..acc+part.length] = part[]; acc += part.length; } return result; } /** * Provides a simple wrapper around writefln and _F. */ char[] writeFln(char[] fs) { return "writefln(\"%s\"," ~ _F(fs) ~ ");"; } version( shfmt_test ): void main() { // Prints: // foo = 13 // bar = 29 // foo + bar = 42 // .. in hex = 2a int foo = 13; int bar = 29; mixin(writeFln("foo = $foo")); mixin(writeFln("bar = $bar")); mixin(writeFln("foo + bar = ${foo+bar}")); mixin(writeFln(".. in hex = $(x){foo+bar}")); }] |
A Shell/PHP-style formatter using inline variable expansion. See down the bottom of the file for an example.
|