[svn] r7811: nemerle/trunk: macros/io.n ncc/testsuite/negative/tyenf.n

VladD2 svnadmin at nemerle.org
Wed Oct 17 16:12:04 CEST 2007


Log:
Add NemerleStringTemplate functionality.

Author: VladD2
Date: Wed Oct 17 16:12:01 2007
New Revision: 7811

Modified:
   nemerle/trunk/macros/io.n
   nemerle/trunk/ncc/testsuite/negative/tyenf.n

Modified: nemerle/trunk/macros/io.n
==============================================================================
--- nemerle/trunk/macros/io.n	(original)
+++ nemerle/trunk/macros/io.n	Wed Oct 17 16:12:01 2007
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright (c) 2003, 2004 The University of Wroclaw.
  * All rights reserved.
  *
@@ -26,15 +26,25 @@
  * 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.Trace;
+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;
+using SB = Ext;//Nemerle.Utility.NStringBuilderExtensions;
 
 namespace Nemerle.IO
 {
@@ -112,26 +122,59 @@
     ]>
   }
 
-  /** 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)
+  macro sprint(str : string)
   {
-    match (value) {
-      | <[ $(str : string) ]> =>
-        def seq = List.RevMap (make_splice_distribution (str, Macros.ImplicitCTX().Env), fun (x) {
-          <[ Console.Write ($x) ]> 
-        });
-        <[ {.. $seq } ]>
+    if (string.IsNullOrEmpty (str))
+    {
+      Message.Warning ("empty spliced string");
+      <[ string.Empty ]>
+    }
+    else
+    {
+      def seq = StringTemplate.Helper.make_splice_distribution (str, Macros.ImplicitCTX().Env).Rev ();
+
+      match (seq)
+      {
+        | [StrPart.Lit(val)] =>
+          Message.Warning ($"spliced string without splices: '$str'");
+          <[ $(val : string) ]> //PT.PExpr.Literal(Literal.String(val))
 
       | _ =>
-        <[ Console.Write ($value) ]>       
+          //def indentPresent = seq.Exists(_ is StrPart.IndentedExpr);
+          //def seq = if (indentPresent) StrPart.Expr(<[ def ident = ""; ]>) :: seq;
+          //mutable curIndent = "";
+          def seq = seq;
+          
+          def res = seq.Map(e => 
+            match (e : StrPart)
+            {
+              | Lit(str)    => <[ $(str : string) ]>
+              // TODO: Try add support of identation.
+              | NewLine     =>   <[ Environment.NewLine ]>
+              | Expr(expr)  => expr
+              | IndentedExpr(indent, expr) => 
+                <[ def indent = $(indent : string);
+                   indent + Convert.ToString($expr) ]>
+            });
+            
+          <[ string.Concat (..$res) ]>
+      }
     }
   }
 
-  macro sprint (str : string)
+  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)
+    {
+      | <[ $(str : string) ]> => 
     if (string.IsNullOrEmpty (str))
     {
       Message.Warning ("empty spliced string");
@@ -139,26 +182,38 @@
     }
     else
     {
-      def seq = make_splice_distribution (str, Macros.ImplicitCTX().Env).Rev ();
+          def seq = StringTemplate.Helper.make_splice_distribution (str, Macros.ImplicitCTX().Env).Rev ();
 
       match (seq)
       {
-        | [PT.PExpr.Literal as lit] =>
-          Message.Warning ($"spliced string without splices: '$str'");
-          lit
+            | [StrPart.Lit(val)] =>
+              <[ Console.Write ($(val : string)) ]>
 
-        | _ => <[ string.Concat (..$seq) ]>
+            | _ => 
+              //def indentPresent = seq.Exists(_ is StrPart.IndentedExpr);
+              //def seq = if (indentPresent) StrPart.Expr(<[ def ident = ""; ]>) :: seq;
+              //mutable curIndent = "";
+              def seq = seq;
+              
+              def res = seq.Map(e => 
+                match (e : StrPart)
+                {
+                  | Lit(str)    => <[ $(str : string) ]>
+                  // TODO: Try add support of identation.
+                  | NewLine     =>   <[ Environment.NewLine ]>
+                  | Expr(expr)  => expr
+                  | IndentedExpr(indent, expr) => 
+                    <[ def indent = $(indent : string);
+                       indent + Convert.ToString($expr) ]>
+                });
+                
+              <[ Console.Write (string.Concat (..$res)) ]>
       }
     }
+
+      | _                     => <[ Console.Write ($value) ]>
   }
 
-  /** Writes text to given System.IO.TextWriter */
-  macro fprint (writer, str : string)
-  {
-    def seq = List.RevMap (make_splice_distribution (str, Macros.ImplicitCTX().Env), fun (x) {
-      <[ writer_v.Write ($x) ]> 
-    });
-    <[ def writer_v = $writer : IO.TextWriter; {.. $seq }; ]>
   }
 
   // module internal to this assembly used for compile time analysis of string formatters, etc.
@@ -345,12 +400,378 @@
       iter_through (parse_format (format), parms.Length, []);
     }
 
-    /** for $(...) and ..$(...) expressions:
-        - first evaluate expressions
-        - store intermediate results in variables
-        - return list of evaluators and reference variables in reverse order
-     */
-    public make_splice_distribution (str : string, env : GlobalEnv) : list [PT.PExpr]
+/*
+
+  /// 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) {
+      | <[ $(str : string) ]> =>
+        def seq = List.RevMap (make_splice_distribution (str, Macros.ImplicitCTX().Env), fun (x) {
+          <[ Console.Write ($x) ]> 
+        });
+        <[ {.. $seq } ]>
+
+      | _ =>
+        <[ Console.Write ($value) ]>       
+    }
+  }
+
+  macro sprint (str : string)
+  {
+    if (string.IsNullOrEmpty (str))
+    {
+      Message.Warning ("empty spliced string");
+      <[ string.Empty ]>
+    }
+    else
+    {
+      def seq = make_splice_distribution (str, Macros.ImplicitCTX().Env).Rev ();
+
+      match (seq)
+      {
+        | [PT.PExpr.Literal as lit] =>
+          Message.Warning ($"spliced string without splices: '$str'");
+          lit
+
+        | _ => <[ string.Concat (..$seq) ]>
+      }
+    }
+  }
+
+  /// Writes text to given System.IO.TextWriter
+  macro fprint (writer, str : string)
+  {
+    def seq = List.RevMap (make_splice_distribution (str, Macros.ImplicitCTX().Env), fun (x) {
+      <[ writer_v.Write ($x) ]> 
+    });
+    <[ def writer_v = $writer : IO.TextWriter; {.. $seq }; ]>
+  }
+*/
+  }
+}
+
+namespace StringTemplate
+{
+  using StringTemplate.Helper;
+
+  [MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Class, Inherited = true)]
+  macro StringTemplateGroup(tb : TypeBuilder)
+  {
+    Helper2.BeforeTypedMembers(tb, Nemerle.Macros.ImplicitCTX());
+  }
+
+  [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = true)]
+  macro StringTemplateGroup(tb : TypeBuilder)
+  {
+    Helper2.WithTypedMembers(tb, Nemerle.Macros.ImplicitCTX());
+  }
+
+  internal module Helper2
+  {
+    StSuffix = "__StImpl";
+
+    internal BeforeTypedMembers(tb : TypeBuilder, ctx : Typer) : void
+    {
+      ctx.Env.Manager.MacroColors.PushUseSiteColor();
+      try
+      {
+        // Add <MethodName>__StImpl method into STG.
+        
+        def members1 = tb.GetParsedMembers(true);
+        def ideTest      = m => m.Tokens != null // BUG in compiler! Workaround!
+          && m.Tokens is Token.BracesGroup(Token.LooseGroup(Token.StringLiteral(_)));
+        def compilerTest = m => m.Body is PExpr.Sequence([PExpr.Literal(Literal.String(_))]);
+        def test = if (ctx.Manager.IsIntelliSenseMode) ideTest else compilerTest;
+        def members2 = members1.Filter(_ => { | m is ClassMember.Function when test(m) => true | _ => false});
+        
+        foreach (method is ClassMember.Function in members2)
+        {
+          def h            = method.header;
+          def workMethName = method.Name + StSuffix;
+          def newMethodAst = <[ decl:
+            protected virtual $(workMethName : usesite) [..$(h.typarms.tyvars)] (..$(h.Parameters)) : void
+              where ..$(h.typarms.constraints)
+            {
+            }]>;
+          _ = tb.DefineWithSource(newMethodAst);
+          method.UserData = newMethodAst;
+          method.Attributes |= NemerleAttributes.Static | NemerleAttributes.Public;
+        }
+      }
+      finally { ctx.Env.Manager.MacroColors.PopColor(); }
+    }
+    
+    internal WithTypedMembers(tb : TypeBuilder, ctx : Typer) : void
+    {
+      ctx.Env.Manager.MacroColors.PushUseSiteColor();
+      try
+      {
+        tb.Define(<[ decl: protected         _builder : StringBuilder = StringBuilder(1024); ]>);
+        tb.Define(<[ decl: protected mutable _indent : string = "\n"; ]>);
+        tb.Define(<[ decl: protected         _indentLenStack : Stack[int] = Stack(); ]>);
+        _ = tb.DefineWithSource(<[ decl: 
+          protected AddIndent(indent : string) : void
+          {
+            _indentLenStack.Add(indent.Length);
+            _indent += indent;
+          }
+         ]>);
+        _ = tb.DefineWithSource(<[ decl: 
+          protected RemoveLastIndent() : void
+          {
+            _indent = _indent.Substring(0, _indent.Length - _indentLenStack.Pop());
+          }
+         ]>);
+        _ = tb.DefineWithSource(<[ decl: 
+          protected PrintNewLineAndIndent() : void
+          {
+            _ = _builder.Append(_indent);
+          }
+         ]>);
+
+        def beforeBodyTypingHandler(mb : MethodBuilder, pBody) : PExpr
+        {
+          def pBody = pBody;
+          
+          match (pBody)
+          {
+            | PExpr.Sequence([PExpr.Literal(Literal.String(str)) as lit]) =>
+            
+              // 1. Сгенерировать и подставить тело для х__StImpl-метода.
+              // 2. В тело данного метода подставить код вызова этого метода.
+              
+              def findCorrespondMethod()
+              {
+                def stAst = mb.Ast.UserData;
+                def stMethods = tb.GetMethods(BF.DeclaredOnly | BF.Instance | BF.NonPublic).FindAll(
+                  _ => { | m is MethodBuilder when m.Ast == stAst => true | _ => false });
+                  
+                match (stMethods)
+                {
+                  | [m is MethodBuilder] => m
+                  | _ => Util.ice($"Found to many correspond 'string template' methods: ..$stMethods");
+                }
+              }
+              
+              def expr         = MakeStringTemplateExpr(mb, str, lit.Location, ctx);
+              def m            = findCorrespondMethod();
+              
+              m.Body = expr;
+              
+              def h            = mb.Ast.header;
+              def workMethName = mb.Ast.Name + StSuffix;
+              
+              <[  def instance = $(tb.FullName : usesite)();
+                  instance.$(workMethName : usesite)(..$(h.ParametersReferences));
+                  instance._builder.ToString() ]>
+
+            | _ => pBody
+          }
+        }
+
+        foreach (mb is MethodBuilder in tb.GetMethods(BF.DeclaredOnly | BF.Static | BF.Public))
+          when (mb.Ast.UserData is ClassMember.Function)
+            mb.AddBeforeBodyTypingHandler(beforeBodyTypingHandler);
+      }
+      finally { ctx.Env.Manager.MacroColors.PopColor(); }
+    }
+    
+    MakeStringTemplateExpr(mb : MethodBuilder, template : string, loc : Location, ctx : Typer) : PExpr
+    {
+      def (template, loc) = SquareString(template, loc);
+      def makeEllipsisSplaceExpr(env : GlobalEnv, expr : string, isComplexExpr : bool) : PT.PExpr
+      {
+        if (isComplexExpr)
+        {
+          def pExpr = MainParser.ParseExpr (env, expr, loc);
+          match (pExpr)
+          {
+            | <[ $seqExpr; $sep; $cnvFuncExpr; ]> =>
+            
+              def loc = loc;
+              _ = loc;
+              
+              // If cnvFuncExpr is a StrinTemplete reference,
+              // replace it by <methodName>__StImpl-method.
+
+              def mb = mb;
+              // Find __StImpl-method corresponding to mb.
+              def corespStImplMethod = (mb.Ast.UserData :> ClassMember.Function).Builder;
+              // Type fake expression to determinate what is cnvFuncExpr (it type).
+              def expr = <[ NCollectionsUtils.MapLazy($seqExpr, $cnvFuncExpr) ]>;
+              def typer = Typer(corespStImplMethod);
+                
+              _ = typer.TypeExpr(expr);
+
+              match (cnvFuncExpr.TypedObject)
+              {
+                | TExpr.StaticRef(_, m is MethodBuilder, _) 
+                  when m.DeclaringType.Equals(mb.DeclaringType) =>
+                  match (m.Ast.UserData) 
+                  {
+                    | coresp is ClassMember.Function =>
+                      <[ SB.AppendSeq(_builder, $seqExpr, $sep, _indent, this.$(coresp.Name : usesite)); ]>
+                    | _ => <[ SB.AppendSeq(_builder, $seqExpr, $sep, _indent, $cnvFuncExpr); ]>
+                  }
+                | _ => <[ SB.AppendSeq(_builder, $seqExpr, $sep, _indent, $cnvFuncExpr); ]>
+              }
+              
+            | <[ $seqExpr; $sep; ]> => <[ SB.AppendSeq(_builder, $seqExpr, $sep, _indent); ]>
+            | _                     => <[ SB.AppendSeq(_builder, $pExpr, ", ", _indent); ]>
+          }
+        }
+        else if (expr == "this") <[ SB.AppendSeq(_builder, this,              ", ", _indent); ]>
+        else                     <[ SB.AppendSeq(_builder, $(expr : usesite), ", ", _indent); ]>
+      }      
+      def makeSplaceExpr(env : GlobalEnv, expr : string, isComplexExpr : bool) : PT.PExpr
+      {
+        def makeExpr(pExpr)
+        {
+          <[ 
+              if (_indent.Length > 1)
+              {
+                def pos = _builder.Length;
+                _ = _builder.Append($pExpr);
+                _ = _builder.Replace("\n", _indent, pos, _builder.Length - pos);
+              }
+              else
+                _ = _builder.Append($pExpr);
+          ]>
+        }
+        if (isComplexExpr)
+        {
+          env.Manager.MacroColors.PushUseSiteColor ();
+          try
+          {
+            def _builder = StringBuilder();
+            def pExpr = MainParser.ParseExpr(env, expr);
+            makeExpr(pExpr);
+          }
+          finally { env.Manager.MacroColors.PopColor (); }
+        } else if (expr == "this") makeExpr(<[ this ]>);
+        else                       makeExpr(<[ $(expr : usesite) ]>);
+      }
+
+      def exprs = Helper.make_splice_distribution2(template, ctx.Env, makeSplaceExpr, makeEllipsisSplaceExpr);
+      //def isNeedOptimize(_ : list[StrPart])
+      //{
+        //| Lit(_) :: Lit(_) :: _    => true
+        //| _                :: tail => isNeedOptimize(tail)
+        //| []                       => false
+      //}
+      //def optimize(_ : list[StrPart])
+      //{
+        //| Lit(s1) :: Lit(s2) :: tail => optimize(StrPart.Lit(s2 + s1) :: tail)
+        //| x                  :: tail => x :: optimize(tail)
+        //| []                         => []
+      //}
+      //def exprs = if (isNeedOptimize(exprs)) optimize(exprs) else exprs;
+      def res = exprs.RevMap(e => 
+        match (e : StrPart)
+        {
+          | Lit(str)    => <[ _ = _builder.Append($(str : string)); ]>
+          | NewLine     => <[ _ = PrintNewLineAndIndent(); ]>
+          | Expr(expr)  => expr
+          | IndentedExpr(indent, expr) => <[ 
+            def indent = $(indent : string);
+            _ = _builder.Append(indent);
+            AddIndent(indent);
+            $expr;
+            RemoveLastIndent(); ]>
+        });
+      
+      <[ { ..$res } ]>
+    }
+    
+    public static Last[T](this lst : SCG.IList[T]) : T
+    {
+      lst[lst.Count - 1]
+    }
+    
+    /// If we have string like this
+    ///     &lt;#
+    ///   SomeText1
+    ///   SomeText2
+    ///   #&gt;
+    /// this function convert it to
+    /// "SomeText1\nSomeText2"   
+    SquareString(str : string, loc : Location) : string * Location
+    {
+      match (str.LastIndexOfAny(array['\r', '\n']))
+      {
+        | -1 => (str, Location(loc.FileIndex, loc.Line,    loc.Column,
+                                              loc.EndLine, loc.EndColumn))
+        | _ => 
+          def rows = str.Split(array["\r\n", "\n", "\r"], StringSplitOptions.None);
+          when (rows.Length <= 2)
+              Message.Error(loc, "The multiline String Template should cantain 3 and more row. "
+                                 "(First and last line in multiline String Template ignored.)");
+          def prefix = rows.Last();
+          def firstIndex = if (rows[0].ForAll(char.IsWhiteSpace)) 1 else 0;
+          def sb = StringBuilder(str.Length - prefix.Length 
+                                 - if (firstIndex == 1) rows[0].Length else 0);
+          def len = rows.Length - 1;
+          mutable isIndentMismatch = false;
+          for (mutable i = firstIndex; i < len; i++)
+          {
+            def row = rows[i];
+            if (row.StartsWith(prefix, StringComparison.InvariantCulture))
+              _ = sb.AppendLine(row.Substring(prefix.Length, row.Length - prefix.Length));
+            else
+            {
+              Message.Error(Location(loc, loc.Line + i, 1, loc.Line + i, row.Length + 1),
+                "Mismatch of the string template strBuilder characters.");
+              isIndentMismatch = true;
+              _ = sb.AppendLine(row);
+            }
+          }
+          
+          when (sb.Length > Environment.NewLine.Length)
+            sb.Length -= Environment.NewLine.Length;
+
+          when (isIndentMismatch)
+            Message.Hint(Location(loc, loc.EndLine, 1, loc.EndLine, loc.EndColumn),
+              "Please, make sure that all of the strBuilder characters of your "
+              "string template match the last line indentation.");
+            // TODO: Локейшон вычисляется неверно. Переделать.
+          (sb.ToString(), Location(loc.FileIndex, 
+            loc.Line    + firstIndex, prefix.Length + 1,
+            loc.EndLine - firstIndex, rows[len - 1].Length + 1)); // + 1 => Location coordinates 1 bound
+      }
+    }
+    
+    //public static Offset(this loc : Location, lineOffset : int, colOffset : int) : Location
+    //{
+      //Location(loc, loc.Line    + lineOffset, loc.Column    + colOffset,
+                    //loc.EndLine + lineOffset, loc.EndColumn + colOffset)
+    //}
+  }
+
+  variant StrPart
+  {
+    | Lit           { str    : string; }
+    | Expr          { expr   : PT.PExpr; }
+    | NewLine
+    | IndentedExpr  { indent : string; expr : PT.PExpr; }
+    
+    public override ToString() : string
+    {
+      match (this)
+      {
+        | Lit(str)                   => $"Lit: '$str'"
+        | Expr(expr)                 => $"Expr: $expr"
+        | NewLine                    => "<\n>"
+        | IndentedExpr(indent, expr) => $"IndentedExpr: '$expr' ('$indent')"
+      }
+    }
+  }
+
+  internal module Helper
+  {  
+    public make_splice_distribution (str : string, env : GlobalEnv) : list [StrPart]
     {
       def makeEllipsisSplaceExpr(env : GlobalEnv, expr : string, isComplexExpr : bool) : PT.PExpr
       {
@@ -392,135 +813,342 @@
         else                       <[ Convert.ToString ($(expr : usesite)) ]>;
       }
       
-      make_splice_distribution (str, env, makeSplaceExpr, makeEllipsisSplaceExpr)
+      make_splice_distribution2 (str, env, makeSplaceExpr, makeEllipsisSplaceExpr)
     }
 
-    public make_splice_distribution (
+    /** for $(...) and ..$(...) expressions:
+        - first evaluate expressions
+        - store intermediate results in variables
+        - return list of evaluators and reference variables in reverse order
+     */
+    public make_splice_distribution2 (
       str                    : string,
-      _env                   : GlobalEnv,
+      env                    : GlobalEnv,
       makeSplaceExpr         : GlobalEnv * string * bool -> PT.PExpr, // env * strExpr * isComplexExpr
       makeEllipsisSplaceExpr : GlobalEnv * string * bool -> PT.PExpr  // env * strExpr * isComplexExpr
     )
-      : list [PT.PExpr]
+      : list [StrPart]
+    {
+      mutable nestLevel = 0;
+      mutable index = -1;
+      mutable ch = if (str.Length > 0) str[0] else '\0';
+      def strBuilder = StringBuilder();
+      def peekN(n) { def next = index + n; if (next < str.Length) str[next] else '\0' }
+      def peek() { peekN(1) }
+      def next() { ch = peek(); index++; ch }
+      def getStrFromBuilder() { def res = strBuilder.ToString(); strBuilder.Length = 0; res }
+      def appendToBuilder(chr) { _ = strBuilder.Append(chr) }
+      /// ~~~Parse expression based on nested brackets.
+      /// Разбирает строку производя поиск закрывающей скобки.
+      /// Вложенные скобки игнорируются. В итоге получается строка содержащая
+      /// выражение заключенное в скбоки (которое так же может содержать вложенные скобки)
+      /// и булево значение говорящее, содержится ли в строке простой идентификатор или варажение.
+      /// Returns pare of (exprStr * isIdentifier)
+      def parseExpressionStr() : string * bool
+      {
+        Assert(strBuilder.Length == 0, "strBuilder.Length == 0");
+        Assert(ch == '(', "ch == '('");
+        /// exprStr * allIsAlphNum
+        def loop(balance, allIsAlphNum) : string * bool
+        {
+          match (peek())
+          {
+            // TODO: Обработать ситуацию когда скобка не закрыта! См. файл:
+            // C:\MyProjects\Nemerle\nemerle\ncc\testsuite\negative\tyenf.n
+            | '\0'
+            | ')' when balance == 1 => _ = next(); (getStrFromBuilder(), allIsAlphNum)
+            | ')'                   => appendToBuilder(next()); loop(balance - 1, false)
+            | '('                   => appendToBuilder(next()); loop(balance + 1, false)
+            | curCh                 =>
+              appendToBuilder(next());
+              loop(balance, allIsAlphNum && (char.IsLetterOrDigit(curCh) || curCh == '_'))
+          }
+        }
+        
+        def (expr, allIsAlphNum) = loop(1, true);
+        (expr, allIsAlphNum && expr.Length != 0 && expr != "_" && char.IsLetter(expr[0]))
+      }
+      def parseIdentifier()
     {
-      mutable seen_non_alnum = false;
+        Assert(strBuilder.Length == 0, "strBuilder.Length == 0");
       
-      def find_end (balance, idx) {
-        when (idx >= str.Length)
-          Message.FatalError ("runaway $(...) in format string");
+        def loop()
+        {
+          def curCh = peek();
+          match (curCh)
+          {
+            | '_'
+            | _ when char.IsLetterOrDigit(curCh) => appendToBuilder(next()); loop()
+            | _ => getStrFromBuilder()
+          }
+        }
 
-        def ch = str[idx];
-        seen_non_alnum = seen_non_alnum || !(System.Char.IsLetterOrDigit (ch) || ch == '_');
-        match (ch) {
-          | ')' when balance == 1 => idx
-          | ')' => find_end (balance - 1, idx + 1)
-          | '(' => find_end (balance + 1, idx + 1)
-          | _ => find_end (balance, idx + 1)
+        if (ch == '_' || char.IsLetter(ch)) 
+        {
+          appendToBuilder(ch);
+          loop()
         }
+        else ""
       }
 
-      def find_end_normal (idx) {
-        if (idx >= str.Length) idx
+      def loop (res : list[StrPart]) : list[StrPart]
+      {
+        nestLevel++; Diagnostics.Trace.Assert(nestLevel < 20000, "Prevent stack owerflow"); // Prevent stack owerflow
+        
+        // Завершает акомуляцию сиволов литерала и создает соотвествующую 
+        // лексему добавляя ее к началу списка лексем
+        def endLiteral()
+        {
+          if (strBuilder.Length == 0)
+            res
         else
-          match (str[idx]) {
-            | '_' 
-            | ch when System.Char.IsLetterOrDigit (ch) => find_end_normal (idx + 1)
-            | _ => idx
+            StrPart.Lit(getStrFromBuilder()) :: res
           }
+        def isNextDollar(n)
+        {
+          def ch1 = peekN(n);
+          if (char.IsWhiteSpace(ch1)) isNextDollar(n + 1)
+          else ch1 == '$'
       }
+        def isElipse() { peek() == '.' && isNextDollar(2) }
+        def processNewLine() { loop (StrPart.NewLine() :: endLiteral()) }
       
-      def indexOfDollar(idx)
+        match (next())
+        {
+          | '\0'                     => endLiteral()
+          | '$'                      => parceSpliceEx(endLiteral(), true)
+          | '.'  when isElipse()     => index = str.IndexOf('$', index);
+                                        parceSpliceEx(endLiteral(), false); // '..$'
+          | '\r' when peek() == '\n' => _ = next(); processNewLine()
+          | '\n' | '\r'              =>             processNewLine()
+          | x                        => appendToBuilder(x); loop(res)
+        }
+      }
+      and parceSpliceEx(res, isSimple)
+      {
+        when (next() == '\0')
       {
-        def str = str;
-        if (idx >= str.Length) -1
-        else if (char.IsWhiteSpace(str[idx])) indexOfDollar(idx + 1)
-        else if (str[idx] == '$') idx
-        else -1
+          //Diagnostics.Trace.Assert(false);
+          Message.Error ("lone `$' at the end of the format string");
+          Nemerle.Imperative.Return ([StrPart.Lit("$")]);
       }
 
-      mutable nestLevel = 0;
+        def rtyIndent(res : list[StrPart], expr)
+        {
+          match (res)
+          {
+            | Lit(str) :: NewLine :: tail when str.ForAll(char.IsWhiteSpace) => 
+              StrPart.IndentedExpr(str, expr)  :: StrPart.NewLine() :: tail
+            | _ => StrPart.Expr(expr) :: res
+          }
+        }
+        
+        def str = str; _ = str;
 
-      def loop (res, idx)
+        if (ch == '(')
       {
-        def str = str;
-        nestLevel++;
-        Diagnostics.Trace.Assert(nestLevel < 200);
+          //def index1 = index; _ = index1;
+          def (exprStr, isIdentifier) = parseExpressionStr();
 
-        if (idx < 0 || idx >= str.Length)
-          res
-        else if (str[idx] == '.') // try recognize pattern '..$x'
+          if (ch == '\0') // скобка не закрыта
         {
-          if (idx + 3 < str.Length && str[idx + 1] == '.' && indexOfDollar(idx + 2) >= 0)
-            parceSpliceEx(res, indexOfDollar(idx + 2), false);  // found pattern '..$'
-          else
-            loop (<[$("." : string)]> :: res, idx + 1)
+            def exprStr = "(" + exprStr;
+            Message.Error($"no closing bracket found in `$(exprStr)' "
+                           "(the closing bracket in format string is probably missing)");
         }
-        else if (str[idx] == '$')
-          parceSpliceEx(res, idx, true)
+          else when (exprStr.Trim().Length == 0)
+            Message.Error("expression without content");
+
+          def expr = if (isSimple) makeSplaceExpr(env, exprStr, !isIdentifier) 
+                     else  makeEllipsisSplaceExpr(env, exprStr, !isIdentifier);
+          loop (rtyIndent(res, expr))
+        }
+        else if (ch == '$')
+          loop (StrPart.Lit("$") :: res)
         else
         {
-          def str = str;
-          def get(index) { if (index < 0 || index >= str.Length) '\0' else str[index] }
-          def getElipseIndex(index)
-          { // If '..' prefix exists return it index. Otherwise return initial index.
-            def ch = get(index - 1);
+          //def index1 = index; _ = index1;
+          def variableName = parseIdentifier();
             
-            if (char.IsWhiteSpace(ch)) getElipseIndex(index - 1)
-            else if (get(index - 1) == '.' && get(index - 2) == '.') index - 2
-            else -1
+          if (variableName == "")
+          {
+            appendToBuilder(ch);
+            Message.Warning ("expected variable name or expression enclosed with (..) after $ in splice string");
+            loop (StrPart.Lit("$") :: res)
           }
-          def nextIdx   = str.IndexOf('$', idx);
-          def elipseIdx = getElipseIndex(nextIdx);
-          def nextIdx   = if (elipseIdx >= 0) elipseIdx else nextIdx;
-          def next_str = if (nextIdx == -1) str.Substring (idx)
-                         else str.Substring (idx, nextIdx - idx);
-          loop (<[ $(next_str : string) ]> :: res, nextIdx)
+          else
+          {
+            def expr = if (isSimple) makeSplaceExpr(env, variableName, false) 
+                       else  makeEllipsisSplaceExpr(env, variableName, false);
+            //def index1 = index; _ = index1;
+            loop (rtyIndent(res, expr))
         }
       }
-      and parceSpliceEx(res, idx, isSimple)
+      }
+
+      loop ([])
+    }
+  }
+}
+
+
+public module Ext
+{
+    /// <summary>Appends the string representation of a specified list items to the end of a <see cref="NStringBuilder"/> instance.</summary>
+    /// <returns>A reference to the NStringBuilder instance after the append operation has completed.</returns>
+    /// <param name="builder">A <see cref="NStringBuilder"/> instance pointer. </param>
+    /// <param name="l">A list. </param>
+    /// <param name="sep">The string used as element separator. </param>
+    public AppendSeq[T] (
+      this builder   : NStringBuilder, 
+           seq       : SCG.IEnumerable [T], 
+           seperator : string,
+           indent    : string,
+           convert   : T -> string
+    )
+      : void
       {
-        def str = str;
+      mutable firstTime = true;
 
-        when (idx + 1 >= str.Length)
+      Assert(true);
+      
+      foreach (elem in seq)
         {
-          //Diagnostics.Trace.Assert(false);
-          Message.Error ("lone `$' at the end of the format string");
-          Nemerle.Imperative.Return ([<[ "$" ]>]);
+        if (firstTime)
+          firstTime = false;
+        else
+        {
+          def pos = builder.Length;
+          _ = builder.Append(seperator);
+          _ = builder.Replace("\n", indent, pos, seperator.Length);
+        }
+        
+        def str = convert(elem).Replace("\n", indent);
+        _ = builder.Append(str);
+      }
         }
           
-        def nextIndex = idx + 1;
+    /// <summary>Appends the string representation of a specified list items to the end of a <see cref="NStringBuilder"/> instance.</summary>
+    /// <returns>A reference to the NStringBuilder instance after the append operation has completed.</returns>
+    /// <param name="builder">A <see cref="NStringBuilder"/> instance pointer. </param>
+    /// <param name="l">A list. </param>
+    /// <param name="sep">The string used as element separator. </param>
+    public AppendSeq[T] (
+      this builder   : NStringBuilder, 
+           seq       : SCG.IEnumerable [T], 
+           seperator : string,
+           indent    : string
+    )
+      : void
+    {
+      mutable firstTime = true;
+      
+      Assert(true);
           
-        if (str[nextIndex] == '(')
+      foreach (elem in seq)
         {
-          def end = find_end (1, idx + 2);
-          def expr = str.Substring (idx + 2, end - idx - 2);
-          def isComplexExpr = expr == "" || expr == "_" || seen_non_alnum || char.IsDigit(expr[0]);
-          def expr = if (isSimple) makeSplaceExpr(_env, expr, isComplexExpr) 
-                     else  makeEllipsisSplaceExpr(_env, expr, isComplexExpr);
-          loop (expr :: res, end + 1)
+        if (firstTime)
+          firstTime = false;
+        else
+        {
+          def pos = builder.Length;
+          _ = builder.Append(seperator);
+          _ = builder.Replace("\n", indent, pos, seperator.Length);
         }
-        else if (str[nextIndex] == '$')
-          loop (<[$("$" : string)]> :: res, idx + 2)
+        
+        def str = elem.ToString().Replace("\n", indent);
+        _ = builder.Append(str);
+      }
+    }
+    
+    /// <summary>Appends the string representation of a specified list items to the end of a <see cref="NStringBuilder"/> instance.</summary>
+    /// <returns>A reference to the NStringBuilder instance after the append operation has completed.</returns>
+    /// <param name="builder">A <see cref="NStringBuilder"/> instance pointer. </param>
+    /// <param name="l">A list. </param>
+    /// <param name="sep">The string used as element separator. </param>
+    public AppendSeq[T] (this builder : NStringBuilder, seq : SCG.IEnumerable [T], seperator : string) : void
+    {
+      Assert(false);
+      mutable firstTime = true;
+      
+      foreach (elem in seq)
+      {
+        if (firstTime)
+          firstTime = false;
         else
+          _ = builder.Append(seperator);
+        
+        _ = builder.Append(elem);
+      }
+    }
+
+    /// <summary>Appends the string representation of a specified list items to the end of a <see cref="NStringBuilder"/> instance.</summary>
+    /// <returns>A reference to the NStringBuilder instance after the append operation has completed.</returns>
+    /// <param name="builder">A <see cref="NStringBuilder"/> instance pointer. </param>
+    /// <param name="l">A list. </param>
+    /// <param name="sep">The string used as element separator. </param>
+    public AppendSeq[T] (
+      this builder   : NStringBuilder, 
+           seq       : SCG.IEnumerable [T], 
+           seperator : string,
+           convert   : T -> string
+    )
+      : void
         {
-          def end = find_end_normal (nextIndex);
-          def variable_name = str.Substring (nextIndex, end - idx - 1);
+      Assert(false);
+      mutable firstTime = true;
           
-          if (variable_name == "")
+      foreach (elem in seq)
           {
-            Message.Warning ("expected variable name or expression enclosed with (..) after $ in splice string");
-            loop (<[$("$" : string)]> :: res, nextIndex)
+        if (firstTime)
+          firstTime = false;
+        else
+          _ = builder.Append(seperator);
+        
+        def str = convert(elem);
+        _ = builder.Append(str);
           }
+    }
+
+    /// <summary>Appends the string representation of a specified list items to the end of a <see cref="NStringBuilder"/> instance.</summary>
+    /// <returns>A reference to the NStringBuilder instance after the append operation has completed.</returns>
+    /// <param name="builder">A <see cref="NStringBuilder"/> instance pointer. </param>
+    /// <param name="l">A list. </param>
+    /// <param name="sep">The string used as element separator. </param>
+    public AppendSeq[T] (
+      this builder   : NStringBuilder, 
+           seq       : SCG.IEnumerable [T], 
+           seperator : string,
+           indent    : string,
+           convert   : T -> void
+    )
+      : void
+    {
+      Assert(true);
+      mutable firstTime = true;
+      
+      foreach (elem in seq)
+      {
+        if (firstTime)
+          firstTime = false;
           else
           {
-            def expr = if (isSimple) makeSplaceExpr(_env, variable_name, false) 
-                       else  makeEllipsisSplaceExpr(_env, variable_name, false);
-            loop (expr :: res, end)
+          def pos = builder.Length;
+          _ = builder.Append(seperator);
+          _ = builder.Replace("\n", indent, pos, seperator.Length);
           }
+        
+        convert(elem);
         }
       }
 
-      loop ([], 0)
-    }
+    /// <summary>Appends the string representation of a specified list items to the end of a <see cref="NStringBuilder"/> instance.</summary>
+    /// <returns>A reference to the NStringBuilder instance after the append operation has completed.</returns>
+    /// <param name="builder">A <see cref="NStringBuilder"/> instance pointer. </param>
+    /// <param name="l">A list. </param>
+    /// <param name="sep">The string used as element separator. </param>
+    public AppendSeq[T] (this builder : NStringBuilder, seq : list [T], seperator : string) : void
+    {
+      _ = builder.AppendList(seq, seperator);
   }
 }

Modified: nemerle/trunk/ncc/testsuite/negative/tyenf.n
==============================================================================
--- nemerle/trunk/ncc/testsuite/negative/tyenf.n	(original)
+++ nemerle/trunk/ncc/testsuite/negative/tyenf.n	Wed Oct 17 16:12:01 2007
@@ -27,7 +27,7 @@
     _ = if (true) Fruit.Apple else null; // E: the `null' literal is not a valid value of type Fruit
     _ = if (true) null else Fruit.Apple; // E: the `null' literal is not a valid value of type Fruit
 
-    _ = $ "$(foo"; // E: runaway .* in format string
+    _ = $ "$(foo"; // E: no closing bracket found in `\(foo' \(the closing bracket in format string is probably missing\)
     _ = $ "$(foo +)"; // E: parse error near operator
     _ = $ "$()"; // E: expression without content
     _ = $ ""; // W: empty spliced string



More information about the svn mailing list