/**
* 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}"));
} |