/* * Copyright (c) 2003-2008 The University of Wroclaw. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. The name of the University may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY ``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 UNIVERSITY 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. */ using StringTemplate; using Nemerle; using Nemerle.Compiler; using Nemerle.Collections; using Nemerle.Utility; using Nemerle.Compiler.Parsetree; using System; using System.Diagnostics.Debug; using System.Reflection; using System.Text; using TT = Nemerle.Compiler.Typedtree; using TExpr = Nemerle.Compiler.Typedtree.TExpr; using PT = Nemerle.Compiler.Parsetree; using BF = System.Reflection.BindingFlags; using SCG = System.Collections.Generic; namespace Nemerle.IO { using Nemerle.IO.Helper; macro printf (format, params parms : array [expr]) { PrintfHelper(format, Macros.ImplicitCTX().Env, fun (strFormat) { def (evals, refs) = make_evaluation_exprs (parse_format (strFormat), parms); def seq = evals + refs.Map(x => <[ Console.Write ($x) ]>); <[ { .. $seq } ]> }) } macro sprintf (format, params parms : array [expr]) { when (parms.Length == 0) Message.Warning ("sprintf with no parameters"); PrintfHelper(format, Macros.ImplicitCTX().Env, fun (strFormat) { def format = parse_format (strFormat); def (evals, refs) = make_evaluation_exprs (format, parms); def exprs = match (refs) { | [] => Message.Warning ("empty format"); [<[ "" ]>] | [expr] => if (format.Head.NeedsNullCheck) evals + [<[ if ($expr == null) string.Empty else $expr.ToString () ]>] else evals + [<[ $expr.ToString () ]>] | _ :: _ => evals + [<[ string.Concat (..$refs) ]>] } <[ { ..$exprs } ]> }) } /** Writes output to given System.IO.TextWriter */ macro fprintf (writer, format, params parms : array [expr]) { PrintfHelper(format, Macros.ImplicitCTX().Env, fun (strFormat) { def (evals, refs) = make_evaluation_exprs (parse_format (strFormat), parms); def seq = evals + refs.Map(x => <[ writer_v.Write ($x) ]>); <[ def writer_v = $writer : IO.TextWriter; {.. $seq } ]> }) } macro scanf (format : string, params parms : array [expr]) { def seq = make_scan_expressions (format, parms, <[ stdin ]>); <[ def stdin = Console.In; ConsumeWhiteSpace (stdin); {.. $seq } ]> } macro fscanf (stream, format : string, params parms : array [expr]) { def seq = make_scan_expressions (format, parms, <[ stream_cached ]>); <[ def stream_cached = $stream; ConsumeWhiteSpace (stream_cached); {.. $seq } ]> } macro sscanf (str, format : string, params parms : array [expr]) { def seq = make_scan_expressions (format, parms, <[ stream_cached ]>); <[ def stream_cached = IO.StringReader ($str); ConsumeWhiteSpace (stream_cached); {.. $seq } ]> } macro sprint(str) { StringTemplate.Helper.SprintImpl(str, true, expr => expr, Macros.ImplicitCTX().Env) } // use writer.Write($"...") instade // macro fprint (writer, str : string) // { // <[ $writer.Write(sprint($(str : string))) ]> // } /// If string literal is supplied, then prints it to System.Console, replacing all /// occurences of $id with id.ToString () invocation /// If any other expression is supplied, it is equivalent to System.Console.Write macro print (value) { match (value) { | <[ $(_ : string) ]> => StringTemplate.Helper.SprintImpl( value, false, expr => <[ Console.Write($expr) ]>, Macros.ImplicitCTX().Env) | _ => <[ Console.Write ($value) ]> } } // module internal to this assembly used for compile time analysis of string formatters, etc. internal module Helper { public PrintfHelper(format : PExpr, env : GlobalEnv, makeExpr : string -> PExpr) : PExpr { match (ConstantFolder.FoldConstants(env, format)) { | PExpr.Literal(Literal.String(strFormat)) => makeExpr(strFormat) | _ => Message.Error ("The 'format' parameter mast be string literal."); PExpr.Error () } } public variant FormatToken { | Text { body : string; } | Number { unsigned : bool; longformat : bool; } | NumberFloat { longformat : bool; } | Str | Chr public NeedsNullCheck : bool { get { match (this) { | Text | Str => true | Number | NumberFloat | Chr => false } } } } /// parse printf-style formatting string /// http://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html#Formatted%20Output public parse_format (form : string) : list [FormatToken] { def buf = StringBuilder (); mutable result = []; mutable i = 0; def n = form.Length; /* def parse_percent () : FormatToken { match (form [i]) { | } }; */ def append_text () { match (result) { | FormatToken.Text (t) :: rest => result = FormatToken.Text (t + buf.ToString ()) :: rest | _ => result = FormatToken.Text (buf.ToString ()) :: result } ignore (buf.Remove (0, buf.Length)); }; while (i < n) { match (form[i]) { | '%' => // % occured - first, dump text from buffer when ( buf.Length > 0 ) append_text (); mutable longform = false; def next_char () { // now analyze next character ++i; if ( i < n ) { match (form[i]) { | 'd' | 'i' => result = FormatToken.Number (false, longform) :: result | 'u' => result = FormatToken.Number (true, longform) :: result | 'f' => result = FormatToken.NumberFloat (longform) :: result | 's' => result = FormatToken.Str () :: result | 'c' => result = FormatToken.Chr () :: result | '%' => ignore (buf.Append ('%')) | 'l' => if (longform) Message.Error ("`l' in printf-format specified twice") else { longform = true; next_char (); } | _ => Message.Error ("Unsupported formatting sequence after % character") } } else Message.Error ("Unexpected end of format after % character") } next_char (); | c => ignore (buf.Append (c)) }; ++i; }; when (buf.Length > 0) append_text (); // the result is reversed, but function using it will reverse it again result } public make_evaluation_exprs (toks : list [FormatToken], parms : array [PT.PExpr]) : list [PT.PExpr] * list [PT.PExpr] { def make_expressions (toks, i, acc_eval, acc_ref) { def continue (x, xs) { def sym = Macros.NewSymbol (); make_expressions (xs, i - 1, <[ def $(sym : name) = $x ]> :: acc_eval, <[ $(sym : name) ]> :: acc_ref) } match (toks) { | [] when i == 0 => (acc_eval, acc_ref) | FormatToken.Text (t) :: xs => make_expressions (xs, i, acc_eval, <[ $(t : string) ]> :: acc_ref) | _ when i == 0 => Message.Error ("not enough arguments for printf macro"); (acc_eval, acc_ref) | FormatToken.Number (false, false) :: xs => continue (<[ $(parms[i - 1]) : int ]>, xs) | FormatToken.Number (true, false) :: xs => continue (<[ $(parms[i - 1]) : uint ]>, xs) | FormatToken.Number (false, true) :: xs => continue (<[ $(parms[i - 1]) : long ]>, xs) | FormatToken.Number (true, true) :: xs => continue (<[ $(parms[i - 1]) : ulong ]>, xs) | FormatToken.NumberFloat (false) :: xs => continue (<[ Convert.ToString (($(parms[i - 1]) : float), Globalization.NumberFormatInfo.InvariantInfo) ]>, xs) | FormatToken.NumberFloat (true) :: xs => continue (<[ Convert.ToString (($(parms[i - 1]) : double), Globalization.NumberFormatInfo.InvariantInfo) ]>, xs) | FormatToken.Str :: xs => continue (<[ $(parms[i - 1]) : string ]>, xs) | FormatToken.Chr :: xs => continue (<[ $(parms[i - 1]) : char ]>, xs) | [] => Message.Error ("too many arguments for printf macro"); (acc_eval, acc_ref) } }; make_expressions (toks, parms.Length, [], []); } public make_scan_expressions (format : string, parms : array [PT.PExpr], stream : PT.PExpr) : list [PT.PExpr] { def iter_through (toks, i, acc) { match ((toks, i)) { | ([], 0) => acc | (FormatToken.Text (t) :: xs, _) => iter_through (xs, i, <[ CheckInput ($(t : string), $stream) ]> :: acc) | (_, 0) => Message.Error ("not enough arguments for scanf-style macro"); acc | (FormatToken.Number (false, false) :: xs, _) => iter_through (xs, i - 1, <[ $(parms[i - 1]) = Convert.ToInt32 (ReadIntDigits ($stream)) ]> :: acc) | (FormatToken.Number (true, false) :: xs, _) => iter_through (xs, i - 1, <[ $(parms[i - 1]) = Convert.ToUInt32 (ReadIntDigits ($stream)) ]> :: acc) | (FormatToken.Number (false, true) :: xs, _) => iter_through (xs, i - 1, <[ $(parms[i - 1]) = Convert.ToInt64 (ReadIntDigits ($stream)) ]> :: acc) | (FormatToken.Number (true, true) :: xs, _) => iter_through (xs, i - 1, <[ $(parms[i - 1]) = Convert.ToUInt64 (ReadIntDigits ($stream)) ]> :: acc) | (FormatToken.NumberFloat :: _, _) => Message.FatalError ("scaning floats not supported") | (FormatToken.Str :: xs, _) => iter_through (xs, i - 1, <[ $(parms[i - 1]) = ReadString ($stream) ]> :: acc) | (FormatToken.Chr :: xs, _) => iter_through (xs, i - 1, <[ $(parms[i - 1]) = Convert.ToChar ($stream.Read ()) ]> :: acc) | ([], _) => Message.Error ("too many arguments for scanf macro"); acc } }; iter_through (parse_format (format), parms.Length, []); } } }