package tk.eclipse.plugin.jseditor.editors; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * * @author Naoki Takezoe */ public class JavaScriptModel implements JavaScriptContext { private List<JavaScriptElement> _children = new ArrayList<JavaScriptElement>(); private List<JavaScriptComment> _comments = new ArrayList<JavaScriptComment>(); private JavaScriptContext _context; private int _endOffset; public JavaScriptModel(String source){ update(source); } public int getStartOffset(){ return 0; } public int getEndOffset(){ return _endOffset; } public JavaScriptContext getContextFromOffset(int offset){ return getContextFromOffset(this, offset); } private JavaScriptContext getContextFromOffset(JavaScriptContext context, int offset){ if(context.getStartOffset() < offset && context.getEndOffset() > offset){ JavaScriptElement[] children = context.getChildren(); for(int i=0;i<children.length;i++){ if(children[i] instanceof JavaScriptContext){ JavaScriptContext result = getContextFromOffset((JavaScriptContext)children[i], offset); if(result!=null){ return result; } } } return context; } return this; } /** * Updates model structure by the specified source code. * * @param source JavaScript */ public void update(String source){ this._children.clear(); this._comments.clear(); this._endOffset = source.length(); _context = this; boolean whitespace = true; char quote = 0; boolean escape = false; for(int i=0;i<source.length();i++){ char c = source.charAt(i); // String literal if(c=='"' || c=='\''){ if(!escape){ quote = (quote == c ? 0 : c); } escape = false; continue; } if(quote != 0){ escape = (c=='\\'); continue; } // skip comment if(c=='/' && source.length() > i+1){ char nc = source.charAt(i+1); if(nc == '/'){ int start = i; while(nc!='\r' && nc!='\n' && source.length() > i){ i++; nc = source.charAt(i+1); } _comments.add(new JavaScriptComment(start, i+1, source.substring(start, i+1))); } if(nc == '*'){ int start = i; i = source.indexOf("*/", i); if(i==-1){ break; } _comments.add(new JavaScriptComment(start, i+2, source.substring(start, i+2))); } } // var if(whitespace && c=='v'){ int result = parseVariable(source, i, _context); if(result!=0){ whitespace = true; i += result; continue; } } // function if(whitespace && c=='f'){ Object[] result = parseFunction(source, i, _context); if(result != null){ whitespace = true; i += ((Integer)result[0]).intValue(); _context = (JavaScriptFunction)result[1]; continue; } } // end function if(c=='}'){ if(_context.getParent()!=null){ if(_context instanceof JavaScriptFunction){ ((JavaScriptFunction)_context).setEndOffset(i); } _context = _context.getParent(); } } // whitespace if(c==' ' || c=='\t' || c=='\r' || c=='\n'){ whitespace = true; } else { whitespace = false; } } } private static int parseVariable(String source, int position, JavaScriptContext context){ Pattern pattern = Pattern.compile("var[\\s\r\n]+(.+?)[\\s\r\n]*?[;=]"); if(source.indexOf("var", position) == position){ Matcher matcher = pattern.matcher(source.substring(position)); if(matcher.find() && matcher.start()==0){ JavaScriptVariable var = new JavaScriptVariable(matcher.group(1), position); context.add(var); return matcher.end(); } } return 0; } private static Object[] parseFunction(String source, int position, JavaScriptContext context){ Pattern pattern = Pattern.compile("function[\\s\r\n]+?(.+?)[\\s\r\n]*?\\((.*?)\\)[\\s\r\n]*?\\{", Pattern.DOTALL); if(source.indexOf("function", position) == position){ Matcher matcher = pattern.matcher(source.substring(position)); if(matcher.find() && matcher.start()==0){ String args = matcher.group(2).replaceAll("[\\s\r\n]*,[\\s\r\n]*",", ").trim(); JavaScriptFunction func = new JavaScriptFunction(matcher.group(1), args, position); func.setParent(context); context.add(func); return new Object[]{Integer.valueOf(matcher.end()), func}; } } return null; } public void add(JavaScriptFunction func) { this._children.add(func); } public void add(JavaScriptVariable var) { this._children.add(var); } public JavaScriptElement[] getChildren(){ return this._children.toArray(new JavaScriptElement[this._children.size()]); } public JavaScriptElement[] getVisibleElements(){ return getChildren(); } public JavaScriptContext getParent(){ return null; } public JavaScriptComment[] getComments(){ return this._comments.toArray(new JavaScriptComment[this._comments.size()]); } }