TemplateEngine.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. * Tencent is pleased to support the open source community by making xLua available.
  3. * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
  4. * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
  5. * http://opensource.org/licenses/MIT
  6. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
  7. */
  8. #if USE_UNI_LUA
  9. using LuaAPI = UniLua.Lua;
  10. using RealStatePtr = UniLua.ILuaState;
  11. using LuaCSFunction = UniLua.CSharpFunctionDelegate;
  12. #else
  13. using LuaAPI = XLua.LuaDLL.Lua;
  14. using RealStatePtr = System.IntPtr;
  15. using LuaCSFunction = XLua.LuaDLL.lua_CSFunction;
  16. #endif
  17. using System;
  18. using System.Linq;
  19. using System.Text.RegularExpressions;
  20. using System.Collections.Generic;
  21. using System.Collections;
  22. using System.Text;
  23. using XLua;
  24. namespace XLua.TemplateEngine
  25. {
  26. public enum TokenType
  27. {
  28. Code, Eval, Text
  29. }
  30. public class Chunk
  31. {
  32. public TokenType Type {get; private set;}
  33. public string Text { get; private set; }
  34. public Chunk(TokenType type, string text)
  35. {
  36. Type = type;
  37. Text = text;
  38. }
  39. }
  40. class TemplateFormatException : Exception
  41. {
  42. public TemplateFormatException(string message)
  43. {
  44. }
  45. }
  46. public class Parser
  47. {
  48. public static string RegexString
  49. {
  50. get;
  51. private set;
  52. }
  53. static Parser()
  54. {
  55. RegexString = GetRegexString();
  56. }
  57. /// <summary>
  58. /// Replaces special characters with their literal representation.
  59. /// </summary>
  60. /// <returns>Resulting string.</returns>
  61. /// <param name="input">Input string.</param>
  62. static string EscapeString(string input)
  63. {
  64. var output = input
  65. .Replace("\\", @"\\")
  66. .Replace("\'", @"\'")
  67. .Replace("\"", @"\""")
  68. .Replace("\n", @"\n")
  69. .Replace("\t", @"\t")
  70. .Replace("\r", @"\r")
  71. .Replace("\b", @"\b")
  72. .Replace("\f", @"\f")
  73. .Replace("\a", @"\a")
  74. .Replace("\v", @"\v")
  75. .Replace("\0", @"\0");
  76. /* var surrogateMin = (char)0xD800;
  77. var surrogateMax = (char)0xDFFF;
  78. for (char sur = surrogateMin; sur <= surrogateMax; sur++)
  79. output.Replace(sur, '\uFFFD');*/
  80. return output;
  81. }
  82. static string GetRegexString()
  83. {
  84. string regexBadUnopened = @"(?<error>((?!<%).)*%>)";
  85. string regexText = @"(?<text>((?!<%).)+)";
  86. string regexNoCode = @"(?<nocode><%=?%>)";
  87. string regexCode = @"<%(?<code>[^=]((?!<%|%>).)*)%>";
  88. string regexEval = @"<%=(?<eval>((?!<%|%>).)*)%>";
  89. string regexBadUnclosed = @"(?<error><%.*)";
  90. string regexBadEmpty = @"(?<error>^$)";
  91. return '(' + regexBadUnopened
  92. + '|' + regexText
  93. + '|' + regexNoCode
  94. + '|' + regexCode
  95. + '|' + regexEval
  96. + '|' + regexBadUnclosed
  97. + '|' + regexBadEmpty
  98. + ")*";
  99. }
  100. /// <summary>
  101. /// Parses the string into regex groups,
  102. /// stores group:value pairs in List of Chunk
  103. /// <returns>List of group:value pairs.</returns>;
  104. /// </summary>
  105. public static List<Chunk> Parse(string snippet)
  106. {
  107. Regex templateRegex = new Regex(
  108. RegexString,
  109. RegexOptions.ExplicitCapture | RegexOptions.Singleline
  110. );
  111. Match matches = templateRegex.Match(snippet);
  112. if (matches.Groups["error"].Length > 0)
  113. {
  114. throw new TemplateFormatException("Messed up brackets");
  115. }
  116. List<Chunk> Chunks = matches.Groups["code"].Captures
  117. .Cast<Capture>()
  118. .Select(p => new { Type = TokenType.Code, p.Value, p.Index })
  119. .Concat(matches.Groups["text"].Captures
  120. .Cast<Capture>()
  121. .Select(p => new { Type = TokenType.Text, Value = EscapeString(p.Value), p.Index }))
  122. .Concat(matches.Groups["eval"].Captures
  123. .Cast<Capture>()
  124. .Select(p => new { Type = TokenType.Eval, p.Value, p.Index }))
  125. .OrderBy(p => p.Index)
  126. .Select(m => new Chunk(m.Type, m.Value))
  127. .ToList();
  128. if (Chunks.Count == 0)
  129. {
  130. throw new TemplateFormatException("Empty template");
  131. }
  132. return Chunks;
  133. }
  134. }
  135. public class LuaTemplate
  136. {
  137. public static string ComposeCode(List<Chunk> chunks)
  138. {
  139. StringBuilder code = new StringBuilder();
  140. code.Append("local __text_gen = {}\r\n");
  141. foreach (var chunk in chunks)
  142. {
  143. switch (chunk.Type)
  144. {
  145. case TokenType.Text:
  146. code.Append("table.insert(__text_gen, \"" + chunk.Text + "\")\r\n");
  147. break;
  148. case TokenType.Eval:
  149. code.Append("table.insert(__text_gen, tostring(" + chunk.Text + "))\r\n");
  150. break;
  151. case TokenType.Code:
  152. code.Append(chunk.Text + "\r\n");
  153. break;
  154. }
  155. }
  156. code.Append("return table.concat(__text_gen)\r\n");
  157. //UnityEngine.Debug.Log("code compose:"+code.ToString());
  158. return code.ToString();
  159. }
  160. public static LuaFunction Compile(LuaEnv luaenv, string snippet)
  161. {
  162. return luaenv.LoadString(ComposeCode(Parser.Parse(snippet)), "luatemplate");
  163. }
  164. public static string Execute(LuaFunction compiledTemplate, LuaTable parameters)
  165. {
  166. compiledTemplate.SetEnv(parameters);
  167. object[] result = compiledTemplate.Call();
  168. System.Diagnostics.Debug.Assert(result.Length == 1);
  169. return result[0].ToString();
  170. }
  171. public static string Execute(LuaFunction compiledTemplate)
  172. {
  173. object[] result = compiledTemplate.Call();
  174. System.Diagnostics.Debug.Assert(result.Length == 1);
  175. return result[0].ToString();
  176. }
  177. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  178. public static int Compile(RealStatePtr L)
  179. {
  180. string snippet = LuaAPI.lua_tostring(L, 1);
  181. string code;
  182. try
  183. {
  184. code = ComposeCode(Parser.Parse(snippet));
  185. }
  186. catch (Exception e)
  187. {
  188. return LuaAPI.luaL_error(L, String.Format("template compile error:{0}\r\n", e.Message));
  189. }
  190. //UnityEngine.Debug.Log("code=" + code);
  191. if (LuaAPI.luaL_loadbuffer(L, code, "luatemplate") != 0)
  192. {
  193. return LuaAPI.lua_error(L);
  194. }
  195. return 1;
  196. }
  197. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  198. public static int Execute(RealStatePtr L)
  199. {
  200. if (!LuaAPI.lua_isfunction(L, 1))
  201. {
  202. return LuaAPI.luaL_error(L, "invalid compiled template, function needed!\r\n");
  203. }
  204. if (LuaAPI.lua_istable(L, 2))
  205. {
  206. LuaAPI.lua_setfenv(L, 1);
  207. }
  208. LuaAPI.lua_pcall(L, 0, 1, 0);
  209. return 1;
  210. }
  211. static LuaCSFunction templateCompileFunction = Compile;
  212. static LuaCSFunction templateExecuteFunction = Execute;
  213. public static void OpenLib(RealStatePtr L)
  214. {
  215. LuaAPI.lua_newtable(L);
  216. LuaAPI.xlua_pushasciistring(L, "compile");
  217. LuaAPI.lua_pushstdcallcfunction(L, templateCompileFunction);
  218. LuaAPI.lua_rawset(L, -3);
  219. LuaAPI.xlua_pushasciistring(L, "execute");
  220. LuaAPI.lua_pushstdcallcfunction(L, templateExecuteFunction);
  221. LuaAPI.lua_rawset(L, -3);
  222. if (0 != LuaAPI.xlua_setglobal(L, "template"))
  223. {
  224. throw new Exception("call xlua_setglobal fail!");
  225. }
  226. }
  227. }
  228. }