/** * Break on commas, except those inside quotes, e.g.: size(300, 200, PDF, "output,weirdname.pdf"); * No special handling implemented for escaped (\") quotes. */ private static StringList breakCommas(String contents) { StringList outgoing = new StringList(); boolean insideQuote = false; // The current word being read StringBuilder current = new StringBuilder(); char[] chars = contents.toCharArray(); for (int i = 0; i < chars.length; i++) { char c = chars[i]; if (insideQuote) { current.append(c); if (c == '\"') { insideQuote = false; } } else { if (c == ',') { if (current.length() != 0) { outgoing.append(current.toString()); current.setLength(0); } } else { current.append(c); if (c == '\"') { insideQuote = true; } } } } if (current.length() != 0) { outgoing.append(current.toString()); } return outgoing; }
/** * Parse a chunk of code and extract the size() command and its contents. Also goes after * fullScreen(), smooth(), and noSmooth(). * * @param code The code from the main tab in the sketch * @param fussy true if it should show an error message if bad size() * @return null if there was an error, otherwise an array (might contain some/all nulls) */ public static SurfaceInfo parseSketchSizeCustom(String code, boolean fussy) throws SketchException { // This matches against any uses of the size() function, whether numbers // or variables or whatever. This way, no warning is shown if size() isn't // actually used in the applet, which is the case especially for anyone // who is cutting/pasting from the reference. // String scrubbed = scrubComments(sketch.getCode(0).getProgram()); // String[] matches = PApplet.match(scrubbed, SIZE_REGEX); // String[] matches = PApplet.match(scrubComments(code), SIZE_REGEX); /* 1. no size() or fullScreen() method at all will use the non-overridden settings() method in PApplet 2. size() or fullScreen() found inside setup() (static mode sketch or otherwise) make sure that it uses numbers (or displayWidth/Height), copy into settings 3. size() or fullScreen() already in settings() don't mess with the sketch, don't insert any defaults really only need to deal with situation #2.. nothing to be done for 1 and 3 */ // if static mode sketch, all we need is regex // easy proxy for static in this case is whether [^\s]void\s is present String uncommented = scrubComments(code); Mode mode = parseMode(uncommented); String searchArea = null; switch (mode) { case JAVA: // it's up to the user searchArea = null; break; case ACTIVE: // active mode, limit scope to setup // Find setup() in global scope MatchResult setupMatch = findInCurrentScope(VOID_SETUP_REGEX, uncommented); if (setupMatch != null) { int start = uncommented.indexOf("{", setupMatch.end()); if (start >= 0) { // Find a closing brace MatchResult match = findInCurrentScope(CLOSING_BRACE, uncommented, start); if (match != null) { searchArea = uncommented.substring(start + 1, match.end() - 1); } else { throw new SketchException("Found a { that's missing a matching }", false); } } } break; case STATIC: // static mode, look everywhere searchArea = uncommented; break; } if (searchArea == null) { return new SurfaceInfo(); } StringList extraStatements = new StringList(); // First look for noSmooth() or smooth(N) so we can hoist it into settings. String[] smoothContents = matchMethod("smooth", searchArea); if (smoothContents != null) { extraStatements.append(smoothContents[0]); } String[] noContents = matchMethod("noSmooth", searchArea); if (noContents != null) { if (extraStatements.size() != 0) { throw new SketchException("smooth() and noSmooth() cannot be used in the same sketch"); } else { extraStatements.append(noContents[0]); } } String[] pixelDensityContents = matchMethod("pixelDensity", searchArea); if (pixelDensityContents != null) { extraStatements.append(pixelDensityContents[0]); } else { pixelDensityContents = matchDensityMess(searchArea); if (pixelDensityContents != null) { extraStatements.append(pixelDensityContents[0]); } } String[] sizeContents = matchMethod("size", searchArea); String[] fullContents = matchMethod("fullScreen", searchArea); // First check and make sure they aren't both being used, otherwise it'll // throw a confusing state exception error that one "can't be used here". if (sizeContents != null && fullContents != null) { throw new SketchException("size() and fullScreen() cannot be used in the same sketch", false); } // Get everything inside the parens for the size() method // String[] contents = PApplet.match(searchArea, SIZE_CONTENTS_REGEX); if (sizeContents != null) { StringList args = breakCommas(sizeContents[1]); SurfaceInfo info = new SurfaceInfo(); // info.statement = sizeContents[0]; info.addStatement(sizeContents[0]); // info.width = args.get(0).trim(); // info.height = args.get(1).trim(); // info.renderer = (args.size() >= 3) ? args.get(2).trim() : null; // info.path = (args.size() >= 4) ? args.get(3).trim() : null; setPrivateSurfaceInfoField(info, "width", (args.size() >= 1) ? args.get(0).trim() : null); setPrivateSurfaceInfoField(info, "height", (args.size() >= 2) ? args.get(1).trim() : null); setPrivateSurfaceInfoField(info, "renderer", (args.size() >= 3) ? args.get(2).trim() : null); setPrivateSurfaceInfoField(info, "path", (args.size() >= 4) ? args.get(3).trim() : null); // Trying to remember why we wanted to allow people to use displayWidth // as the height or displayHeight as the width, but maybe it's for // making a square sketch window? Not going to // if (info.hasOldSyntax()) { if (hasOldSyntax(info)) { // return null; throw new SketchException("Please update your code to continue.", false); } // if (info.hasBadSize() && fussy) { if (hasBadSize(info) && fussy) { // found a reference to size, but it didn't seem to contain numbers final String message = "The size of this sketch could not be determined from your code.\n" + "Use only numbers (not variables) for the size() command.\n" + "Read the size() reference for more details."; // Messages.showWarning("Could not find sketch size", message, null); showWarning("Could not find sketch size", message); // new Exception().printStackTrace(System.out); // return null; throw new SketchException("Please fix the size() line to continue.", false); } info.addStatements(extraStatements); // info.checkEmpty(); invokePrivateSurfaceInfoMethod(info, "checkEmpty", null); return info; // return new String[] { contents[0], width, height, renderer, path }; } // if no size() found, check for fullScreen() // contents = PApplet.match(searchArea, FULL_SCREEN_CONTENTS_REGEX); if (fullContents != null) { SurfaceInfo info = new SurfaceInfo(); // info.statement = fullContents[0]; info.addStatement(fullContents[0]); StringList args = breakCommas(fullContents[1]); if (args.size() > 0) { // might have no args String args0 = args.get(0).trim(); if (args.size() == 1) { // could be either fullScreen(1) or fullScreen(P2D), figure out which if (args0.equals("SPAN") || PApplet.parseInt(args0, -1) != -1) { // it's the display parameter, not the renderer // info.display = args0; setPrivateSurfaceInfoField(info, "display", args0); } else { // info.renderer = args0; setPrivateSurfaceInfoField(info, "renderer", args0); } } else if (args.size() == 2) { // info.renderer = args0; // info.display = args.get(1).trim(); setPrivateSurfaceInfoField(info, "renderer", args0); setPrivateSurfaceInfoField(info, "display", args.get(1).trim()); } else { throw new SketchException("That's too many parameters for fullScreen()"); } } // info.width = "displayWidth"; // info.height = "displayHeight"; setPrivateSurfaceInfoField(info, "width", "displayWidth"); setPrivateSurfaceInfoField(info, "height", "displayHeight"); // if (extraStatements.size() != 0) { // info.statement += extraStatements.join(" "); // } info.addStatements(extraStatements); // info.checkEmpty(); invokePrivateSurfaceInfoMethod(info, "checkEmpty", null); return info; } // Lint is telling me that this statement is never true... but I beg to differ if (sizeContents == null && fullContents == null) { /* * Default to fullscreen * * This isn't in Processing's implementation, but for some reason the prior version * of APDE also defaulted to fullscreen, so include this to keep it the same * * Plus, it makes more sense to default to fullscreen - who wants a 100 x 100 sketch * display area on a mobile device? */ SurfaceInfo info = new SurfaceInfo(); info.addStatement("fullScreen();"); setPrivateSurfaceInfoField(info, "width", "displayWidth"); setPrivateSurfaceInfoField(info, "height", "displayHeight"); info.addStatements(extraStatements); invokePrivateSurfaceInfoMethod(info, "checkEmpty", null); return info; } // Made it this far, but no size() or fullScreen(), and still // need to pull out the noSmooth() and smooth(N) methods. if (extraStatements.size() != 0) { SurfaceInfo info = new SurfaceInfo(); // info.statement = extraStatements.join(" "); info.addStatements(extraStatements); return info; } // not an error, just no size() specified // return new String[] { null, null, null, null, null }; return new SurfaceInfo(); }
/** * Parse a chunk of code and extract the size() command and its contents. Also goes after * fullScreen(), smooth(), and noSmooth(). * * @param code The code from the main tab in the sketch * @param fussy true if it should show an error message if bad size() * @return null if there was an error, otherwise an array (might contain some/all nulls) */ public static SurfaceInfo parseSketchSize(String code, boolean fussy) throws SketchException { // This matches against any uses of the size() function, whether numbers // or variables or whatever. This way, no warning is shown if size() isn't // actually used in the applet, which is the case especially for anyone // who is cutting/pasting from the reference. // String scrubbed = scrubComments(sketch.getCode(0).getProgram()); // String[] matches = PApplet.match(scrubbed, SIZE_REGEX); // String[] matches = PApplet.match(scrubComments(code), SIZE_REGEX); /* 1. no size() or fullScreen() method at all will use the non-overridden settings() method in PApplet 2. size() or fullScreen() found inside setup() (static mode sketch or otherwise) make sure that it uses numbers (or displayWidth/Height), copy into settings 3. size() or fullScreen() already in settings() don't mess with the sketch, don't insert any defaults really only need to deal with situation #2.. nothing to be done for 1 and 3 */ // if static mode sketch, all we need is regex // easy proxy for static in this case is whether [^\s]void\s is present String searchArea = scrubComments(code); String[] setupMatch = PApplet.match(searchArea, VOID_SETUP_REGEX); if (setupMatch != null) { String found = setupMatch[0]; int start = searchArea.indexOf(found) + found.length(); int openBrace = searchArea.indexOf("{", start); char[] c = searchArea.toCharArray(); int depth = 0; int closeBrace = -1; StringBuilder sb = new StringBuilder(); for (int i = openBrace; i < c.length; i++) { if (c[i] == '{') { depth++; } else if (c[i] == '}') { depth--; if (depth == 0) { closeBrace = ++i; break; } } else { sb.append(c[i]); } } if (closeBrace == -1) { throw new SketchException("Found a { that's missing a matching }", false); // return null; } searchArea = sb.toString(); } StringList extraStatements = new StringList(); // First look for noSmooth() or smooth(N) so we can hoist it into settings. String[] smoothContents = matchMethod("smooth", searchArea); if (smoothContents != null) { extraStatements.append(smoothContents[0]); } String[] noContents = matchMethod("noSmooth", searchArea); if (noContents != null) { if (extraStatements.size() != 0) { throw new SketchException("smooth() and noSmooth() cannot be used in the same sketch"); } else { extraStatements.append(noContents[0]); } } String[] pixelDensityContents = matchMethod("pixelDensity", searchArea); if (pixelDensityContents != null) { extraStatements.append(pixelDensityContents[0]); } String[] sizeContents = matchMethod("size", searchArea); String[] fullContents = matchMethod("fullScreen", searchArea); // First check and make sure they aren't both being used, otherwise it'll // throw a confusing state exception error that one "can't be used here". if (sizeContents != null && fullContents != null) { throw new SketchException("size() and fullScreen() cannot be used in the same sketch", false); } // Get everything inside the parens for the size() method // String[] contents = PApplet.match(searchArea, SIZE_CONTENTS_REGEX); if (sizeContents != null) { StringList args = breakCommas(sizeContents[1]); SurfaceInfo info = new SurfaceInfo(); info.statement = sizeContents[0]; info.width = args.get(0).trim(); info.height = args.get(1).trim(); info.renderer = (args.size() >= 3) ? args.get(2).trim() : null; info.path = (args.size() >= 4) ? args.get(3).trim() : null; // Trying to remember why we wanted to allow people to use displayWidth // as the height or displayHeight as the width, but maybe it's for // making a square sketch window? Not going to if (info.hasOldSyntax()) { // return null; throw new SketchException("Please update your code to continue.", false); } if (info.hasBadSize() && fussy) { // found a reference to size, but it didn't seem to contain numbers final String message = "The size of this sketch could not be determined from your code.\n" + "Use only numbers (not variables) for the size() command.\n" + "Read the size() reference for more details."; Base.showWarning("Could not find sketch size", message, null); // new Exception().printStackTrace(System.out); // return null; throw new SketchException("Please fix the size() line to continue.", false); } if (extraStatements.size() != 0) { info.statement += extraStatements.join(" "); } info.checkEmpty(); return info; // return new String[] { contents[0], width, height, renderer, path }; } // if no size() found, check for fullScreen() // contents = PApplet.match(searchArea, FULL_SCREEN_CONTENTS_REGEX); if (fullContents != null) { SurfaceInfo info = new SurfaceInfo(); info.statement = fullContents[0]; StringList args = breakCommas(fullContents[1]); if (args.size() > 0) { // might have no args String args0 = args.get(0).trim(); if (args.size() == 1) { // could be either fullScreen(1) or fullScreen(P2D), figure out which if (args0.equals("SPAN") || PApplet.parseInt(args0, -1) != -1) { // it's the display parameter, not the renderer info.display = args0; } else { info.renderer = args0; } } else if (args.size() == 2) { info.renderer = args0; info.display = args.get(1).trim(); } else { throw new SketchException("That's too many parameters for fullScreen()"); } } info.width = "displayWidth"; info.height = "displayHeight"; if (extraStatements.size() != 0) { info.statement += extraStatements.join(" "); } info.checkEmpty(); return info; } // Made it this far, but no size() or fullScreen(), and still // need to pull out the noSmooth() and smooth(N) methods. if (extraStatements.size() != 0) { SurfaceInfo info = new SurfaceInfo(); info.statement = extraStatements.join(" "); return info; } // not an error, just no size() specified // return new String[] { null, null, null, null, null }; return new SurfaceInfo(); }