Mercurial Hosting > luan
annotate core/src/luan/impl/ThemeParser.java @ 594:e91e476186c7
theme indentation
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 03 Sep 2015 12:23:53 -0600 |
parents | 92c9fa5e39e6 |
children | 8370c4009cce |
rev | line source |
---|---|
584 | 1 package luan.impl; |
2 | |
3 import java.util.Map; | |
4 import java.util.HashMap; | |
5 import java.util.List; | |
6 import java.util.ArrayList; | |
7 import luan.LuanSource; | |
8 import luan.LuanTable; | |
9 import luan.LuanElement; | |
10 import luan.LuanState; | |
11 import luan.LuanFunction; | |
12 import luan.LuanException; | |
13 import luan.modules.PackageLuan; | |
14 | |
15 | |
16 public final class ThemeParser { | |
17 | |
18 public static LuanFunction compile(LuanState luan,LuanSource source) throws LuanException { | |
19 try { | |
20 FnDef fnDef = new ThemeParser(source).parse(); | |
21 final LuanStateImpl luanImpl = (LuanStateImpl)luan; | |
22 return new Closure(luanImpl,fnDef); | |
23 } catch(ParseException e) { | |
24 //e.printStackTrace(); | |
25 throw new LuanException(luan, e.getFancyMessage() ); | |
26 } | |
27 } | |
28 | |
29 private static final class Frame { | |
30 final Frame parent; | |
31 final List<String> symbols = new ArrayList<String>(); | |
32 int stackSize = 0; | |
33 final boolean isVarArg; | |
34 final List<String> upValueSymbols = new ArrayList<String>(); | |
35 final List<UpValue.Getter> upValueGetters = new ArrayList<UpValue.Getter>(); | |
36 | |
37 Frame() { | |
38 this.parent = null; | |
39 isVarArg = true; | |
40 } | |
41 | |
42 Frame(Frame parent) { | |
43 this.parent = parent; | |
44 isVarArg = false; | |
45 if( upValueIndex(MOD) != 0 ) | |
46 throw new RuntimeException(); | |
47 } | |
48 | |
49 int stackIndex(String name) { | |
50 int i = symbols.size(); | |
51 while( --i >= 0 ) { | |
52 if( symbols.get(i).equals(name) ) | |
53 return i; | |
54 } | |
55 return -1; | |
56 } | |
57 | |
58 int upValueIndex(String name) { | |
59 int i = upValueSymbols.size(); | |
60 while( --i >= 0 ) { | |
61 if( upValueSymbols.get(i).equals(name) ) | |
62 return i; | |
63 } | |
64 if( parent==null ) | |
65 return -1; | |
66 i = parent.stackIndex(name); | |
67 if( i != -1 ) { | |
68 upValueGetters.add(new UpValue.StackGetter(i)); | |
69 } else { | |
70 i = parent.upValueIndex(name); | |
71 if( i == -1 ) | |
72 return -1; | |
73 upValueGetters.add(new UpValue.NestedGetter(i)); | |
74 } | |
75 upValueSymbols.add(name); | |
76 return upValueSymbols.size() - 1; | |
77 } | |
78 | |
79 void addUpValueGetter(String name,UpValue.Getter upValueGetter) { | |
80 upValueSymbols.add(name); | |
81 upValueGetters.add(upValueGetter); | |
82 } | |
83 } | |
84 | |
85 private static final String IO = "-IO-"; | |
86 private static final String MOD = "-MOD-"; | |
87 private static final String ENV = "-ENV-"; | |
594 | 88 private static final String INDENT = "-INDENT-"; |
584 | 89 private static final UpValue.Getter[] NO_UP_VALUE_GETTERS = new UpValue.Getter[0]; |
90 | |
91 private final LuanSource source; | |
92 private final Parser parser; | |
93 private Frame frame = new Frame(); | |
94 | |
95 private ThemeParser(LuanSource source) { | |
96 this.source = source; | |
97 this.parser = new Parser(this.source); | |
98 } | |
99 | |
100 private LuanElement se(int start) { | |
101 return se(start,null); | |
102 } | |
103 | |
104 private LuanElement se(int start,String text) { | |
105 return new LuanElement(source,start,parser.currentIndex(),text); | |
106 } | |
107 | |
108 private int symbolsSize() { | |
109 return frame.symbols.size(); | |
110 } | |
111 | |
112 private void addSymbol(String name) { | |
113 frame.symbols.add(name); | |
114 if( frame.stackSize < symbolsSize() ) | |
115 frame.stackSize = symbolsSize(); | |
116 } | |
117 | |
118 private FnDef newFnDef(int start,Stmt stmt) { | |
119 return new FnDef( se(start), stmt, frame.stackSize, symbolsSize(), frame.isVarArg, frame.upValueGetters.toArray(NO_UP_VALUE_GETTERS) ); | |
120 } | |
121 | |
122 private int stackIndex(String name) { | |
123 return frame.stackIndex(name); | |
124 } | |
125 | |
594 | 126 private void popSymbols(int n) { |
127 List<String> symbols = frame.symbols; | |
128 while( n-- > 0 ) { | |
129 symbols.remove(symbols.size()-1); | |
130 } | |
131 } | |
132 | |
584 | 133 private int upValueIndex(String name) { |
134 return frame.upValueIndex(name); | |
135 } | |
136 | |
137 private ParseException exception(String msg) { | |
138 parser.failure(); | |
139 return parser.exception(msg); | |
140 } | |
141 | |
142 private Expr env() { | |
143 return new GetLocalVar(null,stackIndex(ENV)); | |
144 } | |
145 | |
146 private FnDef parse() throws ParseException { | |
147 List<Stmt> stmts = new ArrayList<Stmt>(); | |
148 int stackStart = symbolsSize(); | |
149 { | |
150 addSymbol(IO); | |
151 LuanElement se = se(0,"require 'luan:Io'"); | |
152 FnCall requireCall = new FnCall( se, new ConstExpr(se,PackageLuan.requireFn), new ConstExpr(se,"luan:Io") ); | |
153 SetStmt setStmt = new SetStmt( new SetLocalVar(stackIndex(IO)), new ExpressionsExpr(requireCall) ); | |
154 stmts.add(setStmt); | |
155 } | |
156 { | |
157 addSymbol(MOD); | |
158 LuanElement se = se(0,"local M = {}"); | |
594 | 159 TableExpr.Field indent = new TableExpr.Field(new ConstExpr(null,INDENT),new ConstExpr(null,"")); |
160 TableExpr tableExpr = new TableExpr( se, new TableExpr.Field[]{indent}, ExpList.emptyExpList ); | |
584 | 161 SetStmt setStmt = new SetStmt( new SetLocalVar(stackIndex(MOD)), tableExpr ); |
162 stmts.add(setStmt); | |
163 } | |
164 while( !parser.endOfInput() ) { | |
165 Stmt def = parseDef(); | |
166 if( def != null ) { | |
167 stmts.add(def); | |
168 } else { | |
169 parser.anyChar(); | |
170 } | |
171 } | |
172 stmts.add( new ReturnStmt(null,new GetLocalVar(null,stackIndex(MOD))) ); | |
173 Stmt block = new Block( stmts.toArray(new Stmt[0]), stackStart, symbolsSize() ); | |
174 FnDef fnDef = newFnDef(0,block); | |
175 return fnDef; | |
176 } | |
177 | |
178 private Stmt parseDef() throws ParseException { | |
179 int start = parser.begin(); | |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
180 if( !parser.match("{define:") ) |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
181 return parser.failure(null); |
594 | 182 String name = parseName(); |
183 if( name==null ) | |
184 throw exception("invalid block name"); | |
185 Map<String,String> attrs = parseAttrs(); | |
186 String spaces = ""; | |
187 if( BlankLine() ) { | |
188 while( BlankLine() ); | |
189 int startSpaces = parser.currentIndex(); | |
190 InlineSpaces(); | |
191 spaces = parser.textFrom(startSpaces); | |
192 } | |
193 if( !parser.match("}") ) | |
194 return null; | |
195 if( !attrs.isEmpty() ) | |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
196 throw exception("this block should have no attributes"); |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
197 Expr table = new GetLocalVar(null,stackIndex(MOD)); |
594 | 198 Settable fnName = new SetTableEntry(se(start),table,new ConstExpr(null,name)); |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
199 frame = new Frame(frame); |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
200 addSymbol(ENV); |
594 | 201 Stmt block = parseBody("define:"+name,spaces,EndOfLine()); |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
202 FnDef fnDef = newFnDef(start,block); |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
203 frame = frame.parent; |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
204 Stmt rtn = new SetStmt(fnName,fnDef); |
584 | 205 return parser.success(rtn); |
206 } | |
207 | |
594 | 208 private Stmt parseBody(String tagName,String spaces,boolean initIndent) throws ParseException { |
584 | 209 List<Stmt> stmts = new ArrayList<Stmt>(); |
210 int stackStart = symbolsSize(); | |
594 | 211 int start = parser.currentIndex(); |
212 { | |
213 addSymbol(INDENT); | |
214 final Expr env = env(); | |
215 Expr exp = new ExprImpl(se(start,"indent")) { | |
216 @Override public Object eval(LuanStateImpl luan) throws LuanException { | |
217 LuanTable tbl = (LuanTable)env.eval(luan); | |
218 String indent = (String)tbl.get(luan,INDENT); | |
219 if( indent==null ) throw new NullPointerException(); | |
220 return indent; | |
221 } | |
222 }; | |
223 // Expr exp = new IndexExpr( se(start,"indent"), env(), new ConstExpr(null,INDENT) ); | |
224 SetStmt setStmt = new SetStmt( new SetLocalVar(stackIndex(INDENT)), exp ); | |
225 stmts.add(setStmt); | |
226 } | |
227 boolean afterIndent = false; | |
228 if( initIndent && parser.match(spaces) ) { | |
229 addText(start,start,stmts,true); | |
230 start = parser.currentIndex(); | |
231 afterIndent = true; | |
232 } | |
233 int end = start; | |
234 while( !matchEndTag(tagName) ) { | |
584 | 235 if( parser.endOfInput() ) |
236 throw exception("unclosed block"); | |
594 | 237 Stmt block = parseBlock(spaces); |
584 | 238 if( block != null ) { |
594 | 239 addText(start,end,stmts,false); |
240 start = parser.currentIndex(); | |
584 | 241 stmts.add(block); |
594 | 242 afterIndent = false; |
584 | 243 continue; |
244 } | |
594 | 245 { |
246 String extraSpaces = null; | |
247 if( afterIndent ) { | |
248 int startSpaces = parser.currentIndex(); | |
249 InlineSpaces(); | |
250 extraSpaces = parser.textFrom(startSpaces); | |
251 end = parser.currentIndex(); | |
252 } | |
253 Stmt simpleTag = parseSimpleTag(extraSpaces); | |
254 if( simpleTag != null ) { | |
255 addText(start,end,stmts,false); | |
256 start = parser.currentIndex(); | |
257 stmts.add(simpleTag); | |
258 afterIndent = false; | |
259 continue; | |
260 } | |
261 if( extraSpaces!=null && extraSpaces.length() > 0 ) | |
262 continue; | |
263 } | |
264 if( EndOfLine() ) { | |
265 end = parser.currentIndex(); | |
266 afterIndent = false; | |
267 if( parser.match(spaces) ) { | |
268 addText(start,end,stmts,true); | |
269 start = parser.currentIndex(); | |
270 afterIndent = true; | |
271 } | |
584 | 272 continue; |
273 } | |
274 parser.anyChar(); | |
594 | 275 end = parser.currentIndex(); |
276 afterIndent = false; | |
584 | 277 } |
594 | 278 addText(start,end,stmts,false); |
279 Stmt block = new Block( stmts.toArray(new Stmt[0]), stackStart, symbolsSize() ); | |
280 popSymbols(1); | |
584 | 281 return block; |
282 } | |
283 | |
594 | 284 private boolean matchEndTag(String tagName) { |
285 parser.begin(); | |
286 if( !parser.match('{') ) | |
287 return parser.failure(); | |
288 Spaces(); | |
289 if( !(parser.match('/') && parser.match(tagName)) ) | |
290 return parser.failure(); | |
291 Spaces(); | |
292 if( !parser.match('}') ) | |
293 return parser.failure(); | |
294 return parser.success(); | |
584 | 295 } |
296 | |
594 | 297 private void addText(int start,int end,List<Stmt> stmts,boolean indent) { |
298 List<Expressions> args = new ArrayList<Expressions>(); | |
299 if( start < end ) { | |
300 String text = parser.text.substring(start,end); | |
301 args.add( new ConstExpr(null,text) ); | |
302 } | |
303 if( indent ) { | |
304 args.add( new GetLocalVar(null,stackIndex(INDENT)) ); | |
305 } | |
306 if( !args.isEmpty() ) { | |
307 Expr io = new GetUpVar(null,upValueIndex(IO)); | |
308 Expr stdoutExp = new IndexExpr( se(start,"stdout"), io, new ConstExpr(null,"stdout") ); | |
309 Expr writeExp = new IndexExpr( se(start,"write"), stdoutExp, new ConstExpr(null,"write") ); | |
310 FnCall writeCall = new FnCall( se(start), writeExp, ExpList.build(args) ); | |
311 stmts.add( new ExpressionsStmt(writeCall) ); | |
312 } | |
313 } | |
314 | |
315 private Stmt parseBlock(String spaces) throws ParseException { | |
584 | 316 int start = parser.begin(); |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
317 if( !parser.match("{block:") ) |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
318 return parser.failure(null); |
594 | 319 String name = parseName(); |
320 if( name==null ) | |
321 throw exception("invalid block name"); | |
322 Map<String,String> attrs = parseAttrs(); | |
323 if( !parser.match("}") ) | |
324 return null; | |
584 | 325 frame = new Frame(frame); |
326 addSymbol(ENV); | |
594 | 327 Stmt block = parseBody("block:"+name,spaces,false); |
584 | 328 FnDef fnDef = newFnDef(start,block); |
329 frame = frame.parent; | |
330 // String rtn = "<% env." + tag.name + "(" + (tag.attrs.isEmpty() ? "nil" : table(tag.attrs)) + ",env,function(env) %>" + block + "<% end) %>"; | |
331 Expr env = env(); | |
594 | 332 Expr fn = new IndexExpr( se(start,"block:"+name), env, new ConstExpr(null,name) ); |
584 | 333 List<Expressions> args = new ArrayList<Expressions>(); |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
334 args.add( env ); |
594 | 335 args.add( attrs.isEmpty() ? new ConstExpr(null,null) : table(attrs) ); |
584 | 336 args.add( fnDef ); |
337 FnCall fnCall = new FnCall( se(start), fn, ExpList.build(args) ); | |
338 Stmt rtn = new ExpressionsStmt(fnCall); | |
339 return parser.success(rtn); | |
340 } | |
341 | |
594 | 342 private Stmt parseSimpleTag(String spaces) throws ParseException { |
584 | 343 int start = parser.begin(); |
344 if( !parser.match("{") ) | |
345 return parser.failure(null); | |
346 String name = parseName(); | |
347 if( name==null ) | |
348 return parser.failure(null); | |
349 Map<String,String> attrs = parseAttrs(); | |
594 | 350 Spaces(); |
584 | 351 if( !parser.match("}") ) |
352 return parser.failure(null); | |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
353 // rtn = "<% env." + name + (attrs.isEmpty() ? "()" : table(attrs)) + " %>"; |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
354 Expr env = env(); |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
355 Expr fn = new IndexExpr( se(start,name), env, new ConstExpr(null,name) ); |
594 | 356 if( spaces!=null && spaces.length() > 0 ) { |
357 final Expr oldEnv = env; | |
358 final Expr oldIndentExpr = new GetLocalVar(null,stackIndex(INDENT)); | |
359 final String addSpaces = spaces; | |
360 env = new ExprImpl(se(start,"indent_env")) { | |
361 @Override public Object eval(LuanStateImpl luan) throws LuanException { | |
362 LuanTable mt = new LuanTable(); | |
363 mt.rawPut("__index",oldEnv.eval(luan)); | |
364 LuanTable tbl = new LuanTable(); | |
365 tbl.setMetatable(mt); | |
366 String oldIndent = (String)oldIndentExpr.eval(luan); | |
367 String newIndent = oldIndent + addSpaces; | |
368 tbl.rawPut(INDENT,newIndent); | |
369 return tbl; | |
370 } | |
371 }; | |
372 } | |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
373 List<Expressions> args = new ArrayList<Expressions>(); |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
374 args.add( env ); |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
375 if( !attrs.isEmpty() ) |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
376 args.add( table(attrs) ); |
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
377 FnCall fnCall = new FnCall( se(start), fn, ExpList.build(args) ); |
584 | 378 Stmt rtn = new ExpressionsStmt(fnCall); |
379 return parser.success(rtn); | |
380 } | |
381 | |
382 private TableExpr table(Map<String,String> attrs) { | |
383 List<TableExpr.Field> fields = new ArrayList<TableExpr.Field>(); | |
384 for( Map.Entry<String,String> entry : attrs.entrySet() ) { | |
385 ConstExpr key = new ConstExpr(null,entry.getKey()); | |
386 ConstExpr value = new ConstExpr(null,entry.getValue()); | |
387 fields.add( new TableExpr.Field(key,value) ); | |
388 } | |
389 return new TableExpr( null, fields.toArray(new TableExpr.Field[0]), ExpList.emptyExpList ); | |
390 } | |
391 | |
392 private Map<String,String> parseAttrs() { | |
393 Map<String,String> attrs = new HashMap<String,String>(); | |
394 while( parseAttr(attrs) ); | |
395 return attrs; | |
396 } | |
397 | |
398 private boolean parseAttr(Map<String,String> attrs) { | |
399 parser.begin(); | |
400 Spaces(); | |
401 String name = parseName(); | |
402 if( name==null ) | |
403 return parser.failure(); | |
404 Spaces(); | |
405 if( !parser.match('=') ) | |
406 return parser.failure(); | |
407 Spaces(); | |
408 if( !parser.match('"') ) | |
409 return parser.failure(); | |
410 int start = parser.currentIndex(); | |
411 while( parser.noneOf("\"}") ); | |
412 String val = parser.textFrom(start); | |
413 if( !parser.match('"') ) | |
414 return parser.failure(); | |
415 attrs.put(name,val); | |
416 return parser.success(); | |
417 } | |
418 | |
419 private void Spaces() { | |
420 while( parser.anyOf(" \t\r\n") ); | |
421 } | |
422 | |
594 | 423 private void InlineSpaces() { |
424 while( parser.anyOf(" \t") ); | |
425 } | |
426 | |
427 private void BlankLines() { | |
428 while( BlankLine() ); | |
429 } | |
430 | |
431 private boolean BlankLine() { | |
432 parser.begin(); | |
433 while( parser.anyOf(" \t") ); | |
434 return EndOfLine() ? parser.success() : parser.failure(); | |
435 } | |
436 | |
437 private boolean EndOfLine() { | |
438 return parser.match( "\r\n" ) || parser.match( '\r' ) || parser.match( '\n' ); | |
439 } | |
440 | |
584 | 441 private String parseName() { |
442 int start = parser.begin(); | |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
443 if( !NameChar() ) |
584 | 444 return parser.failure(null); |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
445 while( NameChar() ); |
584 | 446 String match = parser.textFrom(start); |
447 return parser.success(match); | |
448 } | |
449 | |
593
92c9fa5e39e6
remove theme "Get" and "Set", and add "define"
Franklin Schmidt <fschmidt@gmail.com>
parents:
587
diff
changeset
|
450 private boolean NameChar() { |
587 | 451 return parser.inCharRange('a', 'z') || parser.inCharRange('A', 'Z') |
452 || parser.inCharRange('0', '9') || parser.anyOf("-_"); | |
584 | 453 } |
454 | |
455 } |