mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 14:19:19 +01:00
893 lines
22 KiB
Java
893 lines
22 KiB
Java
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
Part of the Processing project - http://processing.org
|
|
Copyright (c) 2012-15 The Processing Foundation
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software Foundation, Inc.
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
package processing.mode.java.tweak;
|
|
|
|
import java.util.*;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
public class SketchParser {
|
|
public List<List<ColorControlBox>> colorBoxes;
|
|
public List<List<Handle>> allHandles;
|
|
|
|
int intVarCount;
|
|
int floatVarCount;
|
|
final String varPrefix = "tweakmode";
|
|
|
|
String[] codeTabs;
|
|
boolean requiresComment;
|
|
List<ColorMode> colorModes;
|
|
|
|
List<List<Range>> scientificNotations;
|
|
|
|
Range setupFunction;
|
|
|
|
List<List<Range>> commentBlocks;
|
|
List<int[]> curlyScopes;
|
|
|
|
|
|
public SketchParser(String[] codeTabs, boolean requiresComment) {
|
|
this.codeTabs = codeTabs;
|
|
this.requiresComment = requiresComment;
|
|
intVarCount = 0;
|
|
floatVarCount = 0;
|
|
|
|
// get all comment blocks
|
|
commentBlocks = new ArrayList<>();
|
|
for (String code : codeTabs) {
|
|
commentBlocks.add(getCommentBlocks(code));
|
|
}
|
|
|
|
// get setup function range (to ignore all numbers there)
|
|
setupFunction = new Range(getSetupStart(codeTabs[0]), getSetupEnd(codeTabs[0]));
|
|
|
|
// build curly scope for every character in the code
|
|
curlyScopes = new ArrayList<>();
|
|
for (String code : codeTabs) {
|
|
curlyScopes.add(getCurlyScopes(code));
|
|
}
|
|
|
|
// get all scientific notation (to ignore them)
|
|
scientificNotations = getAllScientificNotations();
|
|
|
|
// find, add, and sort all tweak-able numbers in the sketch
|
|
addAllNumbers();
|
|
|
|
// handle colors
|
|
colorModes = findAllColorModes();
|
|
//colorBoxes = new ArrayList[codeTabs.length];
|
|
createColorBoxes();
|
|
createColorBoxesForLights();
|
|
|
|
// If there is more than one color mode per context, allow only hex and
|
|
// webcolors in this context. Currently there is no notion of order of
|
|
// execution so we cannot know which color mode relate to a color.
|
|
handleMultipleColorModes();
|
|
}
|
|
|
|
|
|
private void addAllNumbers() {
|
|
allHandles = new ArrayList<>();
|
|
|
|
addAllDecimalNumbers();
|
|
addAllHexNumbers();
|
|
addAllWebColorNumbers();
|
|
for (List<Handle> handle : allHandles) {
|
|
//Collections.sort(allHandles[i], new HandleComparator());
|
|
Collections.sort(handle, new HandleComparator());
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get a list of all the numbers in this sketch
|
|
* @return
|
|
* list of all numbers in the sketch (excluding hexadecimals)
|
|
*/
|
|
private void addAllDecimalNumbers() {
|
|
// for every number found:
|
|
// save its type (int/float), name, value and position in code.
|
|
|
|
Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]\\d+\\.?\\d*");
|
|
for (int i = 0; i < codeTabs.length; i++) {
|
|
List<Handle> handles = new ArrayList<Handle>();
|
|
allHandles.add(handles);
|
|
|
|
String c = codeTabs[i];
|
|
Matcher m = p.matcher(c);
|
|
|
|
while (m.find()) {
|
|
boolean forceFloat = false;
|
|
int start = m.start()+1;
|
|
int end = m.end();
|
|
|
|
if (isInRangeList(start, commentBlocks.get(i))) {
|
|
// ignore comments
|
|
continue;
|
|
}
|
|
|
|
if (setupFunction.contains(start)) {
|
|
// ignore numbers in setup
|
|
continue;
|
|
}
|
|
|
|
if (requiresComment) {
|
|
// only add numbers that have the "// tweak" comment in their line
|
|
if (!lineHasTweakComment(start, c)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// ignore scientific notation (e.g. 1e-6)
|
|
boolean found = false;
|
|
for (Range r : scientificNotations.get(i)) {
|
|
if (r.contains(start)) {
|
|
found=true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
continue;
|
|
}
|
|
|
|
// remove any 'f' after the number
|
|
if (c.charAt(end) == 'f') {
|
|
forceFloat = true;
|
|
end++;
|
|
}
|
|
|
|
// if its a negative, include the '-' sign
|
|
if (c.charAt(start-1) == '-') {
|
|
if (isNegativeSign(start-2, c)) {
|
|
start--;
|
|
}
|
|
}
|
|
|
|
// special case for ignoring (0x...). will be handled later
|
|
if (c.charAt(m.end()) == 'x' ||
|
|
c.charAt(m.end()) == 'X') {
|
|
continue;
|
|
}
|
|
|
|
// special case for ignoring number inside a string ("")
|
|
if (isInsideString(start, c))
|
|
continue;
|
|
|
|
// beware of the global assignment (bug from 26.07.2013)
|
|
if (isGlobal(m.start(), i))
|
|
continue;
|
|
|
|
int line = countLines(c.substring(0, start)) - 1; // zero based
|
|
String value = c.substring(start, end);
|
|
if (value.contains(".") || forceFloat) {
|
|
// consider this as a float
|
|
String name = varPrefix + "_float[" + floatVarCount +"]";
|
|
int decimalDigits = getNumDigitsAfterPoint(value);
|
|
handles.add(new Handle("float", name, floatVarCount, value, i, line, start, end, decimalDigits));
|
|
floatVarCount++;
|
|
} else {
|
|
// consider this as an int
|
|
String name = varPrefix + "_int[" + intVarCount +"]";
|
|
handles.add(new Handle("int", name, intVarCount, value, i, line, start, end, 0));
|
|
intVarCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get a list of all the hexadecimal numbers in the code
|
|
* @return
|
|
* list of all hexadecimal numbers in the sketch
|
|
*/
|
|
private void addAllHexNumbers() {
|
|
// for every number found:
|
|
// save its type (int/float), name, value and position in code.
|
|
Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]0x[A-Fa-f0-9]+");
|
|
for (int i = 0; i < codeTabs.length; i++) {
|
|
String c = codeTabs[i];
|
|
Matcher m = p.matcher(c);
|
|
|
|
while (m.find()) {
|
|
int start = m.start()+1;
|
|
int end = m.end();
|
|
|
|
if (isInRangeList(start, commentBlocks.get(i))) {
|
|
// ignore comments
|
|
continue;
|
|
}
|
|
|
|
if (setupFunction.contains(start)) {
|
|
// ignore number in setup
|
|
continue;
|
|
}
|
|
|
|
if (requiresComment) {
|
|
// only add numbers that have the "// tweak" comment in their line
|
|
if (!lineHasTweakComment(start, c)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// special case for ignoring number inside a string ("")
|
|
if (isInsideString(start, c)) {
|
|
continue;
|
|
}
|
|
|
|
// beware of the global assignment (bug from 26.07.2013)
|
|
if (isGlobal(m.start(), i)) {
|
|
continue;
|
|
}
|
|
|
|
int line = countLines(c.substring(0, start)) - 1; // zero based
|
|
String value = c.substring(start, end);
|
|
String name = varPrefix + "_int[" + intVarCount + "]";
|
|
Handle handle;
|
|
try {
|
|
handle = new Handle("hex", name, intVarCount, value, i, line, start, end, 0);
|
|
}
|
|
catch (NumberFormatException e) {
|
|
// don't add this number
|
|
continue;
|
|
}
|
|
allHandles.get(i).add(handle);
|
|
intVarCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get a list of all the webcolors (#) numbers in the code
|
|
* list of all hexadecimal numbers in the sketch
|
|
*/
|
|
private void addAllWebColorNumbers() {
|
|
Pattern p = Pattern.compile("#[A-Fa-f0-9]{6}");
|
|
for (int i=0; i<codeTabs.length; i++)
|
|
{
|
|
String c = codeTabs[i];
|
|
Matcher m = p.matcher(c);
|
|
|
|
while (m.find())
|
|
{
|
|
int start = m.start();
|
|
int end = m.end();
|
|
|
|
if (isInRangeList(start, commentBlocks.get(i))) {
|
|
// ignore comments
|
|
continue;
|
|
}
|
|
|
|
if (setupFunction.contains(start)) {
|
|
// ignore number in setup
|
|
continue;
|
|
}
|
|
|
|
if (requiresComment) {
|
|
// only add numbers that have the "// tweak" comment in their line
|
|
if (!lineHasTweakComment(start, c)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// special case for ignoring number inside a string ("")
|
|
if (isInsideString(start, c)) {
|
|
continue;
|
|
}
|
|
|
|
// beware of the global assignment (bug from 26.07.2013)
|
|
if (isGlobal(m.start(), i)) {
|
|
continue;
|
|
}
|
|
|
|
int line = countLines(c.substring(0, start)) - 1; // zero based
|
|
String value = c.substring(start, end);
|
|
String name = varPrefix + "_int[" + intVarCount + "]";
|
|
Handle handle;
|
|
try {
|
|
handle = new Handle("webcolor", name, intVarCount, value, i, line, start, end, 0);
|
|
}
|
|
catch (NumberFormatException e) {
|
|
// don't add this number
|
|
continue;
|
|
}
|
|
allHandles.get(i).add(handle);
|
|
intVarCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private ArrayList<ColorMode> findAllColorModes() {
|
|
ArrayList<ColorMode> modes = new ArrayList<ColorMode>();
|
|
|
|
for (int i=0; i<codeTabs.length; i++) {
|
|
String tab = codeTabs[i];
|
|
int index = -1;
|
|
// search for a call to colorMode function
|
|
while ((index = tab.indexOf("colorMode", index+1)) > -1) {
|
|
// found colorMode at index
|
|
|
|
if (isInRangeList(index, commentBlocks.get(i))) {
|
|
// ignore comments
|
|
continue;
|
|
}
|
|
|
|
index += 9;
|
|
int parOpen = tab.indexOf('(', index);
|
|
if (parOpen < 0) {
|
|
continue;
|
|
}
|
|
|
|
int parClose = tab.indexOf(')', parOpen+1);
|
|
if (parClose < 0) {
|
|
continue;
|
|
}
|
|
|
|
// add this mode
|
|
String modeDesc = tab.substring(parOpen+1, parClose);
|
|
String context = getObject(index-9, tab);
|
|
modes.add(ColorMode.fromString(context, modeDesc));
|
|
}
|
|
}
|
|
return modes;
|
|
}
|
|
|
|
|
|
private void createColorBoxes() {
|
|
colorBoxes = new ArrayList<>();
|
|
// search tab for the functions: 'color', 'fill', 'stroke', 'background', 'tint'
|
|
Pattern p = Pattern.compile("color\\(|color\\s\\(|fill[\\(\\s]|stroke[\\(\\s]|background[\\(\\s]|tint[\\(\\s]");
|
|
|
|
for (int i = 0; i < codeTabs.length; i++) {
|
|
List<ColorControlBox> colorBox = new ArrayList<ColorControlBox>();
|
|
colorBoxes.add(colorBox);
|
|
|
|
String tab = codeTabs[i];
|
|
Matcher m = p.matcher(tab);
|
|
|
|
while (m.find()) {
|
|
ArrayList<Handle> colorHandles = new ArrayList<Handle>();
|
|
|
|
// look for the '(' and ')' positions
|
|
int openPar = tab.indexOf("(", m.start());
|
|
int closePar = tab.indexOf(")", m.end());
|
|
if (openPar < 0 || closePar < 0) {
|
|
// ignore this color
|
|
continue;
|
|
}
|
|
|
|
if (isInRangeList(m.start(), commentBlocks.get(i))) {
|
|
// ignore colors in a comment
|
|
continue;
|
|
}
|
|
|
|
if (setupFunction.contains(m.start())) {
|
|
// ignore number in setup
|
|
continue;
|
|
}
|
|
|
|
// look for handles inside the parenthesis
|
|
for (Handle handle : allHandles.get(i)) {
|
|
if (handle.startChar > openPar &&
|
|
handle.endChar <= closePar) {
|
|
// we have a match
|
|
colorHandles.add(handle);
|
|
}
|
|
}
|
|
|
|
if (colorHandles.size() > 0) {
|
|
/* make sure there is no other stuff between '()' like variables.
|
|
* subtract all handle values from string inside parenthesis and
|
|
* check there is no garbage left
|
|
*/
|
|
String insidePar = tab.substring(openPar+1, closePar);
|
|
for (Handle h : colorHandles) {
|
|
insidePar = insidePar.replaceFirst(h.strValue, "");
|
|
}
|
|
|
|
// make sure there is only ' ' and ',' left in the string.
|
|
boolean garbage = false;
|
|
for (int j=0; j<insidePar.length(); j++) {
|
|
if (insidePar.charAt(j) != ' ' && insidePar.charAt(j) != ',') {
|
|
// don't add this color box because we can not know the
|
|
// real value of this color
|
|
garbage = true;
|
|
}
|
|
}
|
|
|
|
// create a new color box
|
|
if (!garbage) {
|
|
// find the context of the color (e.g. this.fill() or <object>.fill())
|
|
String context = getObject(m.start(), tab);
|
|
ColorMode cmode = getColorModeForContext(context);
|
|
|
|
// not adding color operations for modes we couldn't understand
|
|
ColorControlBox newCCB = new ColorControlBox(context, cmode, colorHandles);
|
|
|
|
if (cmode.unrecognizedMode) {
|
|
// the color mode is unrecognizable add only if is a hex or webcolor
|
|
if (newCCB.isHex) {
|
|
colorBox.add(newCCB);
|
|
}
|
|
} else {
|
|
colorBox.add(newCCB);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private void createColorBoxesForLights() {
|
|
// search code for light color and material color functions.
|
|
Pattern p = Pattern.compile("ambientLight[\\(\\s]|directionalLight[\\(\\s]"+
|
|
"|pointLight[\\(\\s]|spotLight[\\(\\s]|lightSpecular[\\(\\s]"+
|
|
"|specular[\\(\\s]|ambient[\\(\\s]|emissive[\\(\\s]");
|
|
|
|
for (int i=0; i<codeTabs.length; i++) {
|
|
String tab = codeTabs[i];
|
|
Matcher m = p.matcher(tab);
|
|
|
|
while (m.find()) {
|
|
ArrayList<Handle> colorHandles = new ArrayList<Handle>();
|
|
|
|
// look for the '(' and ')' positions
|
|
int openPar = tab.indexOf("(", m.start());
|
|
int closePar = tab.indexOf(")", m.end());
|
|
if (openPar < 0 || closePar < 0) {
|
|
// ignore this color
|
|
continue;
|
|
}
|
|
|
|
if (isInRangeList(m.start(), commentBlocks.get(i))) {
|
|
// ignore colors in a comment
|
|
continue;
|
|
}
|
|
|
|
if (setupFunction.contains(m.start())) {
|
|
// ignore number in setup
|
|
continue;
|
|
}
|
|
|
|
// put 'colorParamsEnd' after three parameters inside the parenthesis or at the close
|
|
int colorParamsEnd = openPar;
|
|
int commas=3;
|
|
while (commas-- > 0) {
|
|
colorParamsEnd=tab.indexOf(",", colorParamsEnd+1);
|
|
if (colorParamsEnd < 0 ||
|
|
colorParamsEnd > closePar) {
|
|
colorParamsEnd = closePar;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (Handle handle : allHandles.get(i)) {
|
|
if (handle.startChar > openPar &&
|
|
handle.endChar <= colorParamsEnd) {
|
|
// we have a match
|
|
colorHandles.add(handle);
|
|
}
|
|
}
|
|
|
|
if (colorHandles.size() > 0) {
|
|
/* make sure there is no other stuff between '()' like variables.
|
|
* subtract all handle values from string inside parenthesis and
|
|
* check there is no garbage left
|
|
*/
|
|
String insidePar = tab.substring(openPar+1, colorParamsEnd);
|
|
for (Handle h : colorHandles) {
|
|
insidePar = insidePar.replaceFirst(h.strValue, "");
|
|
}
|
|
|
|
// make sure there is only ' ' and ',' left in the string.
|
|
boolean garbage = false;
|
|
for (int j=0; j<insidePar.length(); j++) {
|
|
if (insidePar.charAt(j) != ' ' && insidePar.charAt(j) != ',') {
|
|
// don't add this color box because we can not know the
|
|
// real value of this color
|
|
garbage = true;
|
|
}
|
|
}
|
|
|
|
// create a new color box
|
|
if (!garbage) {
|
|
// find the context of the color (e.g. this.fill() or <object>.fill())
|
|
String context = getObject(m.start(), tab);
|
|
ColorMode cmode = getColorModeForContext(context);
|
|
|
|
// not adding color operations for modes we couldn't understand
|
|
ColorControlBox newCCB = new ColorControlBox(context, cmode, colorHandles);
|
|
|
|
if (cmode.unrecognizedMode) {
|
|
// the color mode is unrecognizable add only if is a hex or webcolor
|
|
if (newCCB.isHex) {
|
|
colorBoxes.get(i).add(newCCB);
|
|
}
|
|
} else {
|
|
colorBoxes.get(i).add(newCCB);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private ColorMode getColorModeForContext(String context) {
|
|
for (ColorMode cm: colorModes) {
|
|
if (cm.drawContext.equals(context)) {
|
|
return cm;
|
|
}
|
|
}
|
|
|
|
// if none found, create the default color mode for this context and return it
|
|
ColorMode newMode = new ColorMode(context);
|
|
colorModes.add(newMode);
|
|
return newMode;
|
|
}
|
|
|
|
|
|
private void handleMultipleColorModes() {
|
|
// count how many color modes per context
|
|
Map<String, Integer> modeCount = new HashMap<String, Integer>();
|
|
for (ColorMode cm : colorModes) {
|
|
Integer prev = modeCount.get(cm.drawContext);
|
|
if (prev == null) {
|
|
prev = 0;
|
|
}
|
|
modeCount.put(cm.drawContext, prev+1);
|
|
}
|
|
|
|
// find the contexts that have more than one color mode
|
|
ArrayList<String> multipleContexts = new ArrayList<String>();
|
|
Set<String> allContexts = modeCount.keySet();
|
|
for (String context : allContexts) {
|
|
if (modeCount.get(context) > 1) {
|
|
multipleContexts.add(context);
|
|
}
|
|
}
|
|
|
|
// keep only hex and web color boxes in color calls
|
|
// that belong to 'multipleContexts' contexts
|
|
for (int i = 0; i < codeTabs.length; i++) {
|
|
List<ColorControlBox> toDelete = new ArrayList<ColorControlBox>();
|
|
for (String context : multipleContexts) {
|
|
for (ColorControlBox ccb : colorBoxes.get(i)) {
|
|
if (ccb.drawContext.equals(context) && !ccb.isHex) {
|
|
toDelete.add(ccb);
|
|
}
|
|
}
|
|
}
|
|
colorBoxes.get(i).removeAll(toDelete);
|
|
}
|
|
}
|
|
|
|
|
|
private List<List<Range>> getAllScientificNotations() {
|
|
List<List<Range>> notations = new ArrayList<>();
|
|
|
|
Pattern p = Pattern.compile("[+\\-]?(?:0|[1-9]\\d*)(?:\\.\\d*)?[eE][+\\-]?\\d+");
|
|
for (String code : codeTabs) {
|
|
List<Range> notation = new ArrayList<Range>();
|
|
Matcher m = p.matcher(code);
|
|
while (m.find()) {
|
|
notation.add(new Range(m.start(), m.end()));
|
|
}
|
|
notations.add(notation);
|
|
}
|
|
return notations;
|
|
}
|
|
|
|
|
|
static public boolean containsTweakComment(String[] codeTabs) {
|
|
for (String tab : codeTabs) {
|
|
if (hasTweakComment(tab)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static private boolean lineHasTweakComment(int pos, String code) {
|
|
int lineEnd = getEndOfLine(pos, code);
|
|
if (lineEnd < 0) {
|
|
return false;
|
|
}
|
|
|
|
String line = code.substring(pos, lineEnd);
|
|
return hasTweakComment(line);
|
|
}
|
|
|
|
|
|
static private boolean hasTweakComment(String code) {
|
|
Pattern p = Pattern.compile("\\/\\/.*tweak", Pattern.CASE_INSENSITIVE);
|
|
Matcher m = p.matcher(code);
|
|
return m.find();
|
|
}
|
|
|
|
|
|
static private boolean isNegativeSign(int pos, String code) {
|
|
// go back and look for ,{[(=?+-/*%<>:&|^!~
|
|
for (int i = pos; i >= 0; i--) {
|
|
char c = code.charAt(i);
|
|
if (c != ' ' && c != '\t') {
|
|
return (c==',' || c=='{' || c=='[' || c=='(' ||
|
|
c=='=' || c=='?' || c=='+' || c=='-' ||
|
|
c=='/' || c=='*' || c=='%' || c=='<' ||
|
|
c=='>' || c==':' || c=='&' || c=='|' ||
|
|
c=='^' || c=='!' || c=='~');
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static private int getNumDigitsAfterPoint(String number) {
|
|
Pattern p = Pattern.compile("\\.[0-9]+");
|
|
Matcher m = p.matcher(number);
|
|
|
|
if (m.find()) {
|
|
return m.end() - m.start() - 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static private int countLines(String str) {
|
|
String[] lines = str.split("\r\n|\n\r|\n|\r");
|
|
return lines.length;
|
|
}
|
|
|
|
|
|
/**
|
|
* Are we inside a string? (TODO: ignore comments in the code)
|
|
* @param pos
|
|
* position in the code
|
|
* @param code
|
|
* the code
|
|
* @return
|
|
*/
|
|
static private boolean isInsideString(int pos, String code) {
|
|
int quoteNum = 0; // count '"'
|
|
|
|
for (int c = pos; c>=0 && code.charAt(c) != '\n'; c--) {
|
|
if (code.charAt(c) == '"') {
|
|
quoteNum++;
|
|
}
|
|
}
|
|
|
|
if (quoteNum%2 == 1) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Builds an int array for every tab that represents the scope depth at each character
|
|
*
|
|
* @return
|
|
*/
|
|
static private int[] getCurlyScopes(String code)
|
|
{
|
|
List<Range> comments = getCommentBlocks(code);
|
|
|
|
int[] scopes = new int[code.length()];
|
|
int curlyScope = 0;
|
|
boolean arrayAssignmentMaybeCommingFlag = false;
|
|
int arrayAssignmentCurlyScope = 0;
|
|
for (int pos=0; pos<code.length(); pos++) {
|
|
scopes[pos] = curlyScope;
|
|
|
|
if (isInRangeList(pos, comments)) {
|
|
// we are inside a comment, ignore and move on
|
|
continue;
|
|
}
|
|
|
|
if (code.charAt(pos) == '{') {
|
|
if (arrayAssignmentMaybeCommingFlag ||
|
|
arrayAssignmentCurlyScope>0) {
|
|
// this is an array assignment
|
|
arrayAssignmentCurlyScope++;
|
|
arrayAssignmentMaybeCommingFlag = false;
|
|
}
|
|
else {
|
|
curlyScope++;
|
|
}
|
|
}
|
|
else if (code.charAt(pos) == '}') {
|
|
if (arrayAssignmentCurlyScope>0) {
|
|
arrayAssignmentCurlyScope--;
|
|
}
|
|
else {
|
|
curlyScope--;
|
|
}
|
|
}
|
|
else if (code.charAt(pos) == '=') {
|
|
arrayAssignmentMaybeCommingFlag = true;
|
|
}
|
|
else if (!isWhiteSpace(code.charAt(pos))) {
|
|
arrayAssignmentMaybeCommingFlag = false;
|
|
}
|
|
}
|
|
|
|
return scopes;
|
|
}
|
|
|
|
static private boolean isWhiteSpace(char c) {
|
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
|
}
|
|
|
|
/**
|
|
* Is this a global position?
|
|
* @param pos position
|
|
* @param codeTabIndex index of the code in codeTabs
|
|
* @return
|
|
* true if the position 'pos' is in global scope in the code 'codeTabs[codeTabIndex]'
|
|
*
|
|
*/
|
|
private boolean isGlobal(int pos, int codeTabIndex) {
|
|
return (curlyScopes.get(codeTabIndex)[pos]==0);
|
|
};
|
|
|
|
static private List<Range> getCommentBlocks(String code) {
|
|
List<Range> commentBlocks = new ArrayList<Range>();
|
|
|
|
int lastBlockStart=0;
|
|
boolean lookForEnd = false;
|
|
for (int pos=0; pos<code.length()-1; pos++) {
|
|
if (lookForEnd) {
|
|
// we have a start, look for the end
|
|
if (code.charAt(pos) == '*' && code.charAt(pos+1) == '/') {
|
|
commentBlocks.add(new Range(lastBlockStart, pos+1));
|
|
lookForEnd = false;
|
|
}
|
|
}
|
|
else {
|
|
if (code.charAt(pos) == '/' && code.charAt(pos+1) == '*') {
|
|
// we found a block start
|
|
lastBlockStart = pos;
|
|
lookForEnd = true;
|
|
}
|
|
else if (code.charAt(pos) == '/' && code.charAt(pos+1) == '/') {
|
|
// we found a line comment
|
|
commentBlocks.add(new Range(pos, getEndOfLine(pos, code)));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return commentBlocks;
|
|
}
|
|
|
|
private static boolean isInRangeList(int pos, List<Range> rangeList) {
|
|
for (Range r : rangeList) {
|
|
if (r.contains(pos)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static private int getEndOfLine(int pos, String code) {
|
|
return code.indexOf("\n", pos);
|
|
}
|
|
|
|
/** returns the object name (what comes before the '.') of the function starting at 'pos'
|
|
*
|
|
* @param pos
|
|
* @param code
|
|
* @return
|
|
*/
|
|
static private String getObject(int pos, String code) {
|
|
boolean readObject = false;
|
|
String obj = "this";
|
|
|
|
while (pos-- >= 0) {
|
|
if (code.charAt(pos) == '.') {
|
|
if (!readObject) {
|
|
obj = "";
|
|
readObject = true;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
else if (code.charAt(pos) == ' ' || code.charAt(pos) == '\t') {
|
|
break;
|
|
}
|
|
else if (readObject) {
|
|
obj = code.charAt(pos) + obj;
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
|
|
static public int getSetupStart(String code) {
|
|
Pattern p = Pattern.compile("void[\\s\\t\\r\\n]*setup[\\s\\t]*\\(\\)[\\s\\t\\r\\n]*\\{");
|
|
Matcher m = p.matcher(code);
|
|
|
|
if (m.find()) {
|
|
return m.end();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static public int getSetupEnd(String code) {
|
|
List<Range> comments = getCommentBlocks(code);
|
|
|
|
int setupStart = getSetupStart(code);
|
|
if (setupStart == -1) {
|
|
return -1;
|
|
}
|
|
|
|
// count brackets to look for setup end
|
|
int bracketCount=1;
|
|
int pos = setupStart;
|
|
while (bracketCount>0 && pos<code.length()) {
|
|
|
|
if (isInRangeList(pos, comments)) {
|
|
// in a comment, ignore and move on
|
|
pos++;
|
|
continue;
|
|
}
|
|
|
|
if (code.charAt(pos) == '{') {
|
|
bracketCount++;
|
|
}
|
|
else if (code.charAt(pos) == '}') {
|
|
bracketCount--;
|
|
}
|
|
|
|
pos++;
|
|
}
|
|
|
|
if (bracketCount == 0) {
|
|
return pos-1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static class Range {
|
|
int start;
|
|
int end;
|
|
|
|
Range(int s, int e) {
|
|
start = s;
|
|
end = e;
|
|
}
|
|
|
|
boolean contains(int v) {
|
|
return v >= start && v < end;
|
|
}
|
|
}
|
|
}
|