package uk.org.smithfamily.utils.normaliser; import java.util.HashMap; import java.util.Map; /** * Converts INI visibility expressions into something Java can compile * */ public class ExpressionWrangler { // Cheat translation table. See usage in code below. private static Map<String, String> translations = new HashMap<String, String>(); static { // User defined translations.put("0", "false"); translations.put("RevLimCLTbased & 1", "(RevLimCLTbased & 1) != 0"); translations.put("RevLimOption & 4", "(RevLimOption & 4) != 0"); translations.put("RevLimOption & 1", "(RevLimOption & 1) != 0"); translations.put("!(status3 & 8)", "(status3 & 8) == 0"); translations.put("status3 & 8", "(status3 & 8) != 0"); translations.put("secondtrigopts & 0x1", "(secondtrigopts & 0x1) != 0"); translations.put("(launchlimopt & 1) && (launch_opt_on >0)", "(launchlimopt & 1) !=0 && (launch_opt_on >0)"); translations.put("spk_config_trig2 & 0x2", "(spk_config_trig2 & 0x2) != 0"); translations.put("((dualfuel_opt_out || dualfuel_opt_mode) && staged_extended_opts_use_v3) || ((nCylinders > 4) && (dualfuel_opt_out || dualfuel_opt_mode) && hardware_fuel && !(sequential & 0x1))", "((dualfuel_opt_out !=0 || dualfuel_opt_mode !=0) && staged_extended_opts_use_v3 != 0) || ((nCylinders > 4) && (dualfuel_opt_out !=0 || dualfuel_opt_mode !=0) && hardware_fuel !=0 && !((sequential & 0x1)!=0))"); translations.put("(staged_extended_opts_use_v3 && staged_first_param) || ((nCylinders > 4) && (staged_first_param) && (hardware_fuel) && !(sequential & 0x1))", "(staged_extended_opts_use_v3!=0 && staged_first_param!=0) || ((nCylinders > 4) && (staged_first_param!=0) && (hardware_fuel!=0) && !((sequential & 0x1)!=0))"); translations.put("(spk_config_trig2 & 0x2) && spk_mode0 == 4", "(spk_config_trig2 & 0x2)!=0 && spk_mode0 == 4"); translations.put("testop_coil && (status3 & 8) && testdwell", "testop_coil != 0 && ((status3 & 8) != 0) && testdwell != 0"); translations.put("(!(status3 & 8)) && (rpm == 0)", "((status3 & 8) == 0) && (rpm == 0)"); translations.put("testop_inj && (status3 & 8) && testpw && testinjcnt", "testop_inj !=0 && ((status3 & 8) != 0) && testpw !=0 && testinjcnt!=0"); // Menus translations.put("AE_options & 0x1", "(AE_options & 0x1) != 0"); translations.put("!(AE_options & 0x1)", "!((AE_options & 0x1) != 0)"); translations.put("NoiseFilterOpts & 1", "(NoiseFilterOpts & 1) != 0"); translations.put("(RevLimCLTbased & 1)", "(RevLimCLTbased & 1) != 0"); // Constants translations.put("wue_lpg?50:100", "(wue_lpg.equals(\"Yes\")) ? 50 : 100"); translations.put("maf_freq0", "maf_freq0"); translations.put("-rpmhigh", "-rpmhigh"); } /** * @param e The expression to evaluate * * @return true if the string looks like an expression, false otherwise */ public static boolean isExpression(String e) { e = e.trim(); // Too short to be an expression if (e.length() < 2) { return false; } // An expression need to start by "{" (unless there is a minus sign in front) and end by "}" if ((e.substring(0, 1).equals("{")) || (e.substring(0, 1).equals("-") && e.substring(1, 2).equals("{")) && e.substring(e.length() - 1, e.length()).equals("}")) { return true; } return false; } /** * Entry point. Take an INI visibility expression and attempt to convert it to Java * * @param e * @return */ public static String convertExpr(String e) { // Normalise space e = e.trim(); e = e.replaceAll(" ", " "); // Some expressions are just too much hard work, so use a cheat translation table to do the conversion if (translations.containsKey(e)) { return translations.get(e); } StringBuffer b = new StringBuffer(); // Are we processing something that looks like a variable? boolean inIdentifier = false; // Are we processing something that looks like a numeric constant (dec or hex) boolean inConstant = false; // Do we need to possibly add a comparison with zero to convert int to bool? boolean pendingEqualityTest = false; // Have we seen ==, >, <, >=, <= boolean seenComparator = false; // Have we seen && or || on our travels along the expression boolean seenLogical = false; // Did the identifier start with ! so that we need to change the sense of the comparison boolean negate = false; // Is there some shonky usage of bitwise & when it should be logical && boolean needAnAmp = false; // Walk the string for (int i = 0; i < e.length(); i++) { char c = e.charAt(i); if (c == '!' && e.charAt(i + 1) != '=' && e.charAt(i + 1) != '(') { negate = true; continue; } if (!seenLogical && (c == '&' || c == '|') && c == e.charAt(i + 1)) { seenLogical = true; } // Now fixup some dodgy bitwise vs logical anding in the INIs // where it is doing a bitwise & against a non constant. All constants appear // to start with a leading zero if (!seenLogical && c == '&' && e.charAt(i + 1) != '&' && e.charAt(i + 2) != '0') { needAnAmp = true; seenLogical = true; } if (c == '=' || c == '>' || c == '<' || (!seenLogical && (c == '&' || c == '|') && c != e.charAt(i + 1))) { seenComparator = true; } if (!inConstant && Character.isDigit(c)) { // Start of a constant inConstant = true; } if (inConstant && Character.isDigit(c) || c == 'x' || (c >= 'a' && c <= 'f')) { // We are still processing a potentially hex constant inConstant = true; } if (inConstant && !(Character.isDigit(c) || c == 'x' || (c >= 'a' && c <= 'f'))) { // And now we've fallen off the end. inConstant = false; } if (!inIdentifier && !inConstant && (Character.isLetterOrDigit(c) || c == '_')) { // Start of an identifier inIdentifier = true; pendingEqualityTest = true; seenLogical = false; seenComparator = false; } if (inIdentifier && !Character.isJavaIdentifierPart(c)) { // Fallen off the end of the identifier inIdentifier = false; } if (pendingEqualityTest && !inIdentifier && !seenComparator && (seenLogical || c == ')')) { // Convert the implicit to the explicit if (negate) { b.append("==0 "); } else { b.append("!=0 "); } pendingEqualityTest = false; negate = false; if (needAnAmp) { b.append('&'); } needAnAmp = false; } b.append(c); } if (pendingEqualityTest && !seenComparator) { // We've fallen off the end of the string, but it looks like we still need a comparison if (negate) { b.append("==0 "); } else { b.append("!=0 "); } } String result = b.toString(); return result; } /* * Test expressions */ private static String[] exprs = { "!(als_in_pin && als_opt_pwmout)", "v == 0x0e", "RevLimCLTbased & 1", " pwmIdle & (pwmidlewhen >1) ", "ax & (bx >1)","ay && !by", "(a & 0x7) != 5", "staged_first_param && (staged_first_param & 0x7) != 5", "staged_first_param && staged_transition_on && ((staged_first_param & 0x7) != 5)", "feature3_3 ", "injctl && extrainj", "(spk_mode0 > 3) && seq_inj", "(spk_mode0 > 3) && (seq_inj == 3)", "(spk_mode0 > 3) && (seq_inj == 3) && (injusetable == 0) && (injdualvalue)", "f5_0_tsf && f5_0_tsf_opt == 3", "testmode == 2 && testop_pwm && !extrainj" }; /** * Run tests from the command line, output before and after * * @param args */ public static void main(String[] args) { for (String e : exprs) { String result = convertExpr(e); System.out.println(String.format("%s : %s", e, result)); } } }