package org.geogebra.common.plugin.script;
import java.util.ArrayList;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.commands.AlgebraProcessor;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.main.App;
import org.geogebra.common.plugin.Event;
import org.geogebra.common.plugin.ScriptError;
import org.geogebra.common.plugin.ScriptType;
import org.geogebra.common.util.StringUtil;
/**
* @author arno Script class for GgbScript scripts
*/
public class GgbScript extends Script {
private AlgebraProcessor proc;
/**
* @param app
* the script's application
* @param scriptText
* the script's source code
*/
public GgbScript(App app, String scriptText) {
super(app, scriptText);
this.proc = app.getKernel().getAlgebraProcessor();
}
@Override
public String getText() {
return script2LocalizedScript(app, text);
}
@Override
public boolean run(Event evt) throws ScriptError {
String scriptText;
if (text == null) {
return true;
}
if (evt.argument == null) {
scriptText = text;
} else {
scriptText = text.replaceAll("%0", evt.argument);
}
String[] lines = scriptText.split("\n");
boolean success = true;
for (int i = 0; i < lines.length; i++) {
String line = lines[i].trim();
if ("".equals(line) || line.charAt(0) == '#') {
continue;
}
try {
GeoElementND[] res = proc
.processAlgebraCommandNoExceptionHandling(line, false,
new ScriptErrorHandler(app, evt, i), false,
null);
success = success && res != null;
} catch (Throwable e) {
}
}
return success;
}
public static String script2LocalizedScript(App app, String st) {
final String[] starr = splitScriptByCommands(st);
boolean isAssignment = false;
for (String string : starr) {
if (":=".equals(string)) {
isAssignment = true;
break;
}
}
final StringBuilder retone = new StringBuilder();
for (int i = 0; i < starr.length; i++) {
if ((i % 2) == 0 || isFunction(starr, i, app)) {
retone.append(starr[i]);
// app.getFunction("nroot")
} else if (app.getParserFunctions().isFunction(starr[i])) {
retone.append(app.getLocalization().getFunction(starr[i]));
} else {
// Log.debug("NOT FUNCTION" + starr[i]);
// do not translate name of function
// see #4391
if (i == 1 && isAssignment) {
retone.append(starr[i]);
} else {
retone.append(app.getLocalization().getCommand(starr[i]));
}
}
}
return retone.toString();
}
private static boolean isFunction(String[] starr, int i, App app) {
if (i >= starr.length - 1 || starr[i + 1].startsWith("[")) {
return false;
}
if (app.getKernel().lookupLabel(starr[i]) != null) {
return true;
}
return false;
}
/**
* Delocalize a script
*
* @param app
* the application
* @param st
* the script text
* @return the text of the delocalized script
*/
public static String localizedScript2Script(App app, String st) {
final String[] starr = splitScriptByCommands(st);
boolean isAssignment = false;
for (String string : starr) {
if (":=".equals(string)) {
isAssignment = true;
break;
}
}
final StringBuilder retone = new StringBuilder();
for (int i = 0; i < starr.length; i++) {
if ((i % 2) == 0) {
retone.append(starr[i]);
} else {
// allow English language command in French scripts
if (isFunction(starr, i, app)) {
retone.append(starr[i]);
}
// do not translate function name
// e.g. for German panel in Gerade(t)
// do not translate Gerade to Line
// needed for #4391
else if (app.getInternalCommand(starr[i]) != null) {
if (i == 1 && isAssignment) {
retone.append(starr[i]);
} else {
retone.append(app.getInternalCommand(starr[i]));
}
} else if (app.getParserFunctions()
.getInternal(app.getLocalization(), starr[i]) != null) {
retone.append(app.getParserFunctions()
.getInternal(app.getLocalization(), starr[i]));
} else {
// fallback for wrong call in English already
// or if someone writes an English command into an
// other language script
retone.append(starr[i]);
}
}
}
return retone.toString();
}
/**
* This method should split a GeoGebra script into the following format: ""
* or "something"; "command"; "something"; "command"; "something"; ...
*
* @param st
* String GeoGebra script
* @return String [] the GeoGebra script split into and array
*/
private static String[] splitScriptByCommands(final String st) {
StringBuilder retone = new StringBuilder();
final ArrayList<String> ret = new ArrayList<String>();
// as the other algorithms would be too complicated,
// just go from the end of the string and advance character by character
// at first count the number of "s to decide how to start the algorithm
int countapo = 0;
for (int j = 0; j < st.length(); j++) {
if (st.charAt(j) == '"') {
countapo++;
}
}
boolean in_string = false;
if (MyDouble.isOdd(countapo)) {
in_string = true;
}
boolean before_bracket = false;
boolean just_before_bracket = false;
for (int i = st.length() - 1; i >= 0; i--) {
if (in_string) {
if (st.charAt(i) == '"') {
in_string = false;
}
} else if (just_before_bracket) {
if (StringUtil.isLetterOrDigitOrUnderscore(st.charAt(i))) {
ret.add(0, retone.toString());
retone = new StringBuilder();
just_before_bracket = false;
before_bracket = true;
} else if (!bracketAt(st, i) && (st.charAt(i) != ' ')) {
just_before_bracket = false;
before_bracket = false;
if (st.charAt(i) == '"') {
in_string = true;
}
}
} else if (before_bracket) {
if (!StringUtil.isLetterOrDigitOrUnderscore(st.charAt(i))) {
ret.add(0, retone.toString());
retone = new StringBuilder();
before_bracket = false;
if (st.charAt(i) == '"') {
in_string = true;
} else if (bracketAt(st, i)) {
just_before_bracket = true;
}
}
} else {
if (st.charAt(i) == '"') {
in_string = true;
} else if (bracketAt(st, i)) {
just_before_bracket = true;
}
}
retone.insert(0, st.charAt(i));
}
ret.add(0, retone.toString());
if (before_bracket) {
ret.add(0, "");
}
final String[] ex = { "" };
return ret.toArray(ex);
}
private static boolean bracketAt(String st, int i) {
return (st.charAt(i) == '[') || (st.charAt(i) == '(');
}
@Override
public ScriptType getType() {
return ScriptType.GGBSCRIPT;
}
@Override
public Script copy() {
return new GgbScript(app, text);
}
/**
* The text of this script is modified by changing every whole word oldLabel
* to newLabel.
*
* @return whether any renaming happened
*/
@Override
public boolean renameGeo(String oldLabel, String newLabel) {
if (oldLabel == null || "".equals(oldLabel) || newLabel == null
|| "".equals(newLabel)) {
return false;
}
ArrayList<String> work = StringUtil.wholeWordTokenize(text);
boolean ret = false;
int numChars = 0, lengthChars;
String forLength1, forLength2;
for (int i = 1; i < work.size(); i += 2) {
if (work.get(i - 1) != null) {
// this is even, so will not be changed,
// because only odd places are checked and replaced
numChars += work.get(i - 1).length();
}
if (oldLabel.equals(work.get(i))) {
// in theory, i+1 is always less than work.size(),
// because it is an odd number, and in theory,
// there is at least a non-null element there
// but better to check...
if (i + 1 < work.size() && work.get(i + 1) != null) {
if ((work.get(i + 1).length() > 0)
&& '[' == work.get(i + 1).charAt(0)) {
// Now it's still possible that oldLabel
// is used as a command name here,
// so we have to rule out that possibility first.
// Luckily, command names are always followed
// by a [, as far as we know, so it is easy.
numChars += oldLabel.length();
continue;
}
}
// We also have to rule out the case when
// the string is used as a "string",
// or used as e.g. "blabla !!* string ---+ ".
// The easiest way to do that is to count the
// number of " signs in the string until our
// string, except the \" signs, and if it's odd,
// then we're probably in a string.
// For this computation, we use numChars.
forLength1 = text.substring(0, numChars).replaceAll("\\\"", "");// String:
// /"
forLength2 = forLength1.replaceAll("\"", "");// String: "
lengthChars = forLength1.length() - forLength2.length();
if (MyDouble.isOdd(lengthChars)) {
// there is an odd number of living " signs before this
// whole-word token, and this means that we're in a
// string, so this whole-word token doesn't matter
numChars += oldLabel.length();
continue;
}
// If the string equals to "e" or "i",
// and we get here, then they mean their
// geo labels, but only after they get
// them, and if they get them in the script,
// then some original "e" or "i" constants
// may be renamed as well... however, we
// guess it's no problem as in the second
// running of the script they should all mean
// the geos, and it's the bug of the code elsewhere
// numChars should be reassigned after it is used,
// but still before "work" is redefined
if (work.get(i) != null) {
numChars += work.get(i).length();
}
// this is really something to be renamed!
// ...or not? TODO: check
work.set(i, newLabel);
ret = true;
} else if (work.get(i) != null) {
numChars += work.get(i).length();
}
}
text = StringUtil.joinTokens(work, null);
return ret;
}
}