You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2019/11/12 22:01:32 UTC

[commons-jexl] branch master updated: JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options) Task #JEXL-307 - Variable redeclaration option

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 753d6e6  JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options) Task #JEXL-307 - Variable redeclaration option
753d6e6 is described below

commit 753d6e634d94022871b481896db78e323d7ccfed
Author: henrib <he...@apache.org>
AuthorDate: Tue Nov 12 23:00:39 2019 +0100

    JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options)
    Task #JEXL-307 - Variable redeclaration option
---
 .../java/org/apache/commons/jexl3/JexlBuilder.java |  21 +-
 .../java/org/apache/commons/jexl3/JexlContext.java |  16 ++
 .../java/org/apache/commons/jexl3/JexlEngine.java  |  10 +-
 .../java/org/apache/commons/jexl3/JexlInfo.java    |  11 +
 .../java/org/apache/commons/jexl3/JexlOptions.java | 223 +++++++++++++++--
 .../org/apache/commons/jexl3/internal/Engine.java  |  68 ++++-
 .../apache/commons/jexl3/internal/Interpreter.java |  33 ++-
 .../commons/jexl3/internal/InterpreterBase.java    |   7 +-
 .../org/apache/commons/jexl3/internal/Options.java | 274 ---------------------
 .../org/apache/commons/jexl3/internal/Script.java  |  45 +++-
 .../jexl3/internal/TemplateInterpreter.java        |   2 +-
 .../org/apache/commons/jexl3/AntishCallTest.java   |  15 +-
 .../org/apache/commons/jexl3/Issues300Test.java    |  25 +-
 .../java/org/apache/commons/jexl3/JXLTTest.java    |   3 +-
 .../org/apache/commons/jexl3/JexlEvalContext.java  |   3 +-
 .../org/apache/commons/jexl3/JexlTestCase.java     |   2 +-
 .../java/org/apache/commons/jexl3/LexicalTest.java |  43 ++++
 17 files changed, 431 insertions(+), 370 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index 35c8d5f..744dfe1 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -18,7 +18,6 @@
 package org.apache.commons.jexl3;
 
 import org.apache.commons.jexl3.internal.Engine;
-import org.apache.commons.jexl3.internal.Options;
 import org.apache.commons.jexl3.introspection.JexlSandbox;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.logging.Log;
@@ -81,7 +80,7 @@ public class JexlBuilder {
     private Boolean cancellable = null;
     
     /** The options. */
-    private final Options options = new Options();
+    private final JexlOptions options = new JexlOptions();
 
     /** Whether getVariables considers all potential equivalent syntactic forms. */
     private Boolean collectAll = null;
@@ -109,23 +108,7 @@ public class JexlBuilder {
 
     /** The features. */
     private JexlFeatures features = null;
-        
-    /**
-     * Sets the default (static, shared) option flags.
-     * <p>
-     * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
-     * engine; this method should only be used for testing / validation.
-     * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
-     * The possible flag names are:
-     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
-     * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation 
-     * may ease validating JEXL3.2 in your environment.
-     * @param flags the flags to set 
-     */
-    public static void setDefaultOptions(String...flags) {
-        Options.setDefaultFlags(flags);
-    }
-    
+
     /**
      * Sets the JexlUberspect instance the engine will use.
      *
diff --git a/src/main/java/org/apache/commons/jexl3/JexlContext.java b/src/main/java/org/apache/commons/jexl3/JexlContext.java
index fdc85a3..ce730f7 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlContext.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlContext.java
@@ -159,4 +159,20 @@ public interface JexlContext {
          */
         JexlOptions getEngineOptions();
     }
+     
+    /**
+     * A marker interface of the JexlContext that processes pragmas.
+     * It is called by the engine before interpreter creation; as a marker of
+     * JexlContext, it is expected to have access and interact with the context
+     * instance.
+     * @since 3.2
+     */
+    interface PragmaProcessor {
+        /**
+         * Process one pragma.
+         * @param key the key
+         * @param value the value
+         */
+        void processPragma(String key, Object value);
+    }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
index bc41a82..7b9d198 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
@@ -352,7 +352,7 @@ public abstract class JexlEngine {
      * @return A {@link JexlScript} which can be executed using a {@link JexlContext}
      * @throws JexlException if there is a problem parsing the script
      */
-    public abstract JexlScript createScript(JexlFeatures features, JexlInfo info, String source, String[] names);
+    public abstract JexlScript createScript(JexlFeatures features, JexlInfo info, String source, String... names);
 
     /**
      * Creates a JexlScript from a String containing valid JEXL syntax.
@@ -365,7 +365,7 @@ public abstract class JexlEngine {
      * @return A {@link JexlScript} which can be executed using a {@link JexlContext}
      * @throws JexlException if there is a problem parsing the script
      */
-    public final JexlScript createScript(JexlInfo info, String source, String[] names) {
+    public final JexlScript createScript(JexlInfo info, String source, String... names) {
         return createScript(null, info, source, names);
     }
 
@@ -378,7 +378,7 @@ public abstract class JexlEngine {
      * @throws JexlException if there is a problem parsing the script.
      */
     public final JexlScript createScript(String scriptText) {
-        return createScript(null, null, scriptText, null);
+        return createScript(null, null, scriptText, (String[]) null);
     }
 
     /**
@@ -404,7 +404,7 @@ public abstract class JexlEngine {
      * @throws JexlException if there is a problem reading or parsing the script.
      */
     public final JexlScript createScript(File scriptFile) {
-        return createScript(null, null, readSource(scriptFile), null);
+        return createScript(null, null, readSource(scriptFile), (String[]) null);
     }
 
     /**
@@ -445,7 +445,7 @@ public abstract class JexlEngine {
      * @throws JexlException if there is a problem reading or parsing the script.
      */
     public final JexlScript createScript(URL scriptUrl) {
-        return createScript(null, readSource(scriptUrl), null);
+        return createScript(null, readSource(scriptUrl), (String[]) null);
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/JexlInfo.java b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
index 19a4a72..296138b 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlInfo.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
@@ -17,6 +17,8 @@
 
 package org.apache.commons.jexl3;
 
+import org.apache.commons.jexl3.internal.Script;
+
 /**
  * Helper class to carry information such as a url/file name, line and column for
  * debugging information reporting.
@@ -182,5 +184,14 @@ public class JexlInfo {
     public final int getColumn() {
         return column;
     }
+    
+    /**
+     * Gets the info from a script.
+     * @param script the script
+     * @return the info
+     */
+    public static JexlInfo from(JexlScript script) {
+        return script instanceof Script? ((Script) script).getInfo() :  null;
+    }
 }
 
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index dc35074..c650e9b 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -18,6 +18,7 @@
 package org.apache.commons.jexl3;
 
 import java.math.MathContext;
+import org.apache.commons.jexl3.internal.Engine;
 
 /**
  * Flags and properties that can alter the evaluation behavior.
@@ -35,31 +36,150 @@ import java.math.MathContext;
  * <p>This interface replaces the now deprecated JexlEngine.Options.
  * @since 3.2
  */
-public interface JexlOptions {
+public final class JexlOptions {
+    /** The local shade bit. */
+    private static final int SHADE = 6;
+    /** The antish var bit. */
+    private static final int ANTISH = 5;
+    /** The lexical scope bit. */
+    private static final int LEXICAL = 4;
+    /** The safe bit. */
+    private static final int SAFE = 3;
+    /** The silent bit. */
+    private static final int SILENT = 2;
+    /** The strict bit. */
+    private static final int STRICT = 1;
+    /** The cancellable bit. */
+    private static final int CANCELLABLE = 0;
+    /** The flags names ordered. */
+    private static final String[] NAMES = {
+        "cancellable", "strict", "silent", "safe", "lexical", "antish", "lexicalShade"
+    };
+    /** Default mask .*/
+    private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE;
+    /** The arithmetic math context. */
+    private MathContext mathContext = null;
+    /** The arithmetic math scale. */
+    private int mathScale = Integer.MIN_VALUE;
+    /** The arithmetic strict math flag. */
+    private boolean strictArithmetic = true;
+    /** The default flags, all but safe. */
+    private int flags = DEFAULT;
+
+    /**
+     * Sets the value of a flag in a mask.
+     * @param ordinal the flag ordinal
+     * @param mask the flags mask
+     * @param value true or false
+     * @return the new flags mask value
+     */
+    private static int set(int ordinal, int mask, boolean value) {
+        return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
+    }
+
+    /**
+     * Checks the value of a flag in the mask.
+     * @param ordinal the flag ordinal
+     * @param mask the flags mask
+     * @return the mask value with this flag or-ed in
+     */
+    private static boolean isSet(int ordinal, int mask) {
+        return (mask & 1 << ordinal) != 0;
+    }
+        
+    /**
+     * Default ctor.
+     */
+    public JexlOptions() {}
+            
+    /**
+     * Sets the default (static, shared) option flags.
+     * <p>
+     * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
+     * engine; this method should only be used for testing / validation.
+     * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
+     * The possible flag names are:
+     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
+     * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation 
+     * may ease validating JEXL3.2 in your environment.
+     * @param flags the flags to set 
+     */
+    public static void setDefaultFlags(String...flags) {
+        DEFAULT = parseFlags(DEFAULT, flags);
+    }
+        
+    /**
+     * Parses flags by name.
+     * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
+     * The possible flag names are:
+     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
+     * @param mask the initial mask state
+     * @param flags the flags to set 
+     * @return the flag mask updated
+     */
+    public static int parseFlags(int mask, String...flags) {
+        for(String name : flags) {
+            boolean b = true;
+            if (name.charAt(0) == '+') {
+                name = name.substring(1);
+            } else if (name.charAt(0) == '-') {
+                name = name.substring(1);
+                b = false;
+            }
+            for(int flag = 0; flag < NAMES.length; ++flag) {
+                if (NAMES[flag].equals(name)) {
+                    if (b) {
+                        mask |= (1 << flag);
+                    } else {
+                        mask &= ~(1 << flag);
+                    }
+                    break;
+                }
+            }
+        }
+        return mask;
+    }
+    
+    /**
+     * Sets this option flags using the +/- syntax.
+     * @param opts the option flags
+     */
+    public void setFlags(String[] opts) {
+        flags = parseFlags(flags, opts);
+    }
+    
     /**
      * The MathContext instance used for +,-,/,*,% operations on big decimals.
      * @return the math context
      */
-    MathContext getMathContext();
+    public MathContext getMathContext() {
+        return mathContext;
+    }
 
     /**
      * The BigDecimal scale used for comparison and coercion operations.
      * @return the scale
      */
-    int getMathScale();
+    public int getMathScale() {
+        return mathScale;
+    }
 
     /**
      * Checks whether evaluation will attempt resolving antish variable names.
      * @return true if antish variables are solved, false otherwise
      */
-    boolean isAntish();
+    public boolean isAntish() {
+        return isSet(ANTISH, flags);
+    }
     
     /**
      * Checks whether evaluation will throw JexlException.Cancel (true) or
      * return null (false) if interrupted.
      * @return true when cancellable, false otherwise
      */
-    boolean isCancellable();
+    public boolean isCancellable() {
+        return isSet(CANCELLABLE, flags);
+    }
 
     /**
      * Checks whether runtime variable scope is lexical.
@@ -67,7 +187,9 @@ public interface JexlOptions {
      * Redefining a variable in the same lexical unit will generate errors.
      * @return true if scope is lexical, false otherwise
      */
-    boolean isLexical();
+    public boolean isLexical() {
+        return isSet(LEXICAL, flags);
+    }
     
     /**
      * Checks whether local variables shade global ones.
@@ -79,122 +201,169 @@ public interface JexlOptions {
      * raise an error.
      * @return true if lexical shading is applied, false otherwise
      */
-    boolean isLexicalShade();
+    public boolean isLexicalShade() {
+        return isSet(SHADE, flags);
+    }
     
     /**
      * Checks whether the engine considers null in navigation expression as
      * errors during evaluation..
      * @return true if safe, false otherwise
      */
-    boolean isSafe();
+    public boolean isSafe() {
+        return isSet(SAFE, flags);
+    }
 
     /**
      * Checks whether the engine will throw a {@link JexlException} when an
      * error is encountered during evaluation.
      * @return true if silent, false otherwise
      */
-    boolean isSilent();
+    public boolean isSilent() {
+        return isSet(SILENT, flags);
+    }
 
     /**
      * Checks whether the engine considers unknown variables, methods and
      * constructors as errors during evaluation.
      * @return true if strict, false otherwise
      */
-    boolean isStrict();
+    public boolean isStrict() {
+        return isSet(STRICT, flags);
+    }
 
     /**
      * Checks whether the arithmetic triggers errors during evaluation when null
      * is used as an operand.
      * @return true if strict, false otherwise
      */
-    boolean isStrictArithmetic();
+    public boolean isStrictArithmetic() {
+        return strictArithmetic;
+    }
     
     /**
      * Sets whether the engine will attempt solving antish variable names from 
      * context.
      * @param flag true if antish variables are solved, false otherwise
      */
-    void setAntish(boolean flag);
+    public void setAntish(boolean flag) {
+        flags = set(ANTISH, flags, flag);
+    }
 
     /**
      * Sets whether the engine will throw JexlException.Cancel (true) or return
      * null (false) when interrupted during evaluation.
      * @param flag true when cancellable, false otherwise
      */
-    void setCancellable(boolean flag);
+    public void setCancellable(boolean flag) {
+        flags = set(CANCELLABLE, flags, flag);
+    }
     
     /**
      * Sets whether the engine uses a strict block lexical scope during
      * evaluation.
      * @param flag true if lexical scope is used, false otherwise
      */
-    void setLexical(boolean flag);
+    public void setLexical(boolean flag) {
+        flags = set(LEXICAL, flags, flag);
+    }   
     
     /**
      * Sets whether the engine strictly shades global variables.
      * Local symbols shade globals after definition and creating global
      * variables is prohibited evaluation.
+     * If setting to lexical shade, lexical is also set.
      * @param flag true if creation is allowed, false otherwise
      */
-    void setLexicalShade(boolean flag);
+    public void setLexicalShade(boolean flag) {
+        flags = set(SHADE, flags, flag);
+        if (flag) {
+            flags = set(LEXICAL, flags, true);
+        }
+    }
 
     /**
      * Sets the arithmetic math context.
      * @param mcontext the context
      */
-    void setMathContext(MathContext mcontext);
+    public void setMathContext(MathContext mcontext) {
+        this.mathContext = mcontext;
+    }
 
     /**
      * Sets the arithmetic math scale.
      * @param mscale the scale
      */
-    void setMathScale(int mscale);
+    public void setMathScale(int mscale) {
+        this.mathScale = mscale;
+    }
 
     /**
      * Sets whether the engine considers null in navigation expression as errors
      * during evaluation.
      * @param flag true if safe, false otherwise
      */
-    void setSafe(boolean flag);
+    public void setSafe(boolean flag) {
+        flags = set(SAFE, flags, flag);
+    } 
 
     /**
      * Sets whether the engine will throw a {@link JexlException} when an error
      * is encountered during evaluation.
      * @param flag true if silent, false otherwise
      */
-    void setSilent(boolean flag);
+    public void setSilent(boolean flag) {
+        flags = set(SILENT, flags, flag);
+    }
 
     /**
      * Sets whether the engine considers unknown variables, methods and
      * constructors as errors during evaluation.
      * @param flag true if strict, false otherwise
      */
-    void setStrict(boolean flag);
+    public void setStrict(boolean flag) {
+        flags = set(STRICT, flags, flag);
+    }
 
     /**
      * Sets the strict arithmetic flag.
      * @param stricta true or false
      */
-    void setStrictArithmetic(boolean stricta);
+    public void setStrictArithmetic(boolean stricta) {
+        this.strictArithmetic = stricta;
+    }
 
     /**
      * Set options from engine.
      * @param jexl the engine
      * @return this instance
-     */
-    JexlOptions set(JexlEngine jexl);
+     */        
+    public JexlOptions set(JexlEngine jexl) {
+        if (jexl instanceof Engine) {
+            ((Engine) jexl).optionsSet(this);
+        }
+        return this;
+    }
 
     /**
      * Set options from options.
-     * @param options the options
+     * @param src the options
      * @return this instance
      */
-    JexlOptions set(JexlOptions options);
-        
+    public JexlOptions set(JexlOptions src) {
+        mathContext = src.mathContext;
+        mathScale = src.mathScale;
+        strictArithmetic = src.strictArithmetic;
+        flags = src.flags;
+        return this;
+    }
+
     /**
      * Creates a copy of this instance.
      * @return a copy
      */
-    JexlOptions copy();
+    public JexlOptions copy() {
+        return new JexlOptions().set(this);
+    }
     
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index d752cca..4895578 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -74,6 +74,10 @@ public class Engine extends JexlEngine {
         private UberspectHolder() {}
     }
     /**
+     * The name of the options pragma.
+     */
+    protected static final String PRAGMA_OPTIONS = "jexl.options";
+    /**
      * The Log to which all JexlEngine messages will be logged.
      */
     protected final Log logger;
@@ -347,18 +351,62 @@ public class Engine extends JexlEngine {
             cache.clear();
         }
     }
-
+       
+    /**
+     * Sets options from this engine options.
+     * @param opts the options to set
+     * @return the options
+     */
+    public JexlOptions optionsSet(JexlOptions opts) {
+        if (opts != null) {
+            opts.set(options);
+        }
+        return opts;
+    }
+    
+    /**
+     * Creates a script evaluation options.
+     * <p>This also calls the pragma processor if any
+     * @param script the script
+     * @param context the context
+     * @return the options
+     */
+    protected JexlOptions createOptions(Script script, JexlContext context) {
+        JexlOptions opts = options(context);
+        Map<String, Object> pragmas = script.getPragmas();
+        if (pragmas != null) {
+            JexlContext.PragmaProcessor processor =
+                    context instanceof JexlContext.PragmaProcessor
+                    ? (JexlContext.PragmaProcessor) context
+                    : null;
+            for(Map.Entry<String, Object> pragma : pragmas.entrySet()) {
+                String key = pragma.getKey();
+                Object value = pragma.getValue();
+                if (PRAGMA_OPTIONS.equals(key) && opts != null) {
+                    if (value instanceof String) {
+                        String[] vs = ((String) value).split(" ");
+                        opts.setFlags(vs);
+                    }
+                }
+                if (processor != null) {
+                    processor.processPragma(key, value);
+                }
+            }
+        }
+        return opts;
+    }
+    
     /**
      * Creates an interpreter.
      * @param context a JexlContext; if null, the empty context is used instead.
      * @param frame   the interpreter frame
+     * @param opts    the evaluation options
      * @return an Interpreter
      */
-    protected Interpreter createInterpreter(JexlContext context, Frame frame) {
-        return new Interpreter(this, context, frame);
+    protected Interpreter createInterpreter(JexlContext context, Frame frame, JexlOptions opts) {
+        return new Interpreter(this, opts, context, frame);
     }
 
-
     @Override
     public Script createExpression(JexlInfo info, String expression) {
         return createScript(expressionFeatures, info, expression, null);
@@ -371,8 +419,12 @@ public class Engine extends JexlEngine {
         }
         String source = trimSource(scriptText);
         Scope scope = names == null ? null : new Scope(null, names);
-        ASTJexlScript tree = parse(info, features == null? scriptFeatures : features, source, scope);
-        return new Script(this, source, tree);
+        JexlFeatures ftrs = features == null? scriptFeatures : features;
+        ASTJexlScript tree = parse(info, ftrs, source, scope);
+        // when parsing lexical, try hard to run lexical
+        return ftrs.isLexical()
+            ? new Script.Lexical(this, source, tree)
+            : new Script(this, source, tree);
     }
 
     /**
@@ -405,7 +457,7 @@ public class Engine extends JexlEngine {
             final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
             final JexlNode node = script.jjtGetChild(0);
             final Frame frame = script.createFrame(bean);
-            final Interpreter interpreter = createInterpreter(context, frame);
+            final Interpreter interpreter = createInterpreter(context, frame, null);
             return interpreter.visitLexicalNode(node, null);
         } catch (JexlException xjexl) {
             if (silent) {
@@ -434,7 +486,7 @@ public class Engine extends JexlEngine {
             final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
             final JexlNode node = script.jjtGetChild(0);
             final Frame frame = script.createFrame(bean, value);
-            final Interpreter interpreter = createInterpreter(context, frame);
+            final Interpreter interpreter = createInterpreter(context, frame, null);
             interpreter.visitLexicalNode(node, null);
         } catch (JexlException xjexl) {
             if (silent) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index ed7efa7..9c27f61 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -17,13 +17,18 @@
 //CSOFF: FileLength
 package org.apache.commons.jexl3.internal;
 
+import java.util.Iterator;
+import java.util.concurrent.Callable;
+
 import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlOperator;
+import org.apache.commons.jexl3.JexlOptions;
 import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.JxltEngine;
+
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 
@@ -105,9 +110,6 @@ import org.apache.commons.jexl3.parser.ASTWhileStatement;
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.Node;
 
-import java.util.Iterator;
-import java.util.concurrent.Callable;
-
 /**
  * An interpreter of JEXL syntax.
  *
@@ -130,11 +132,12 @@ public class Interpreter extends InterpreterBase {
     /**
      * Creates an interpreter.
      * @param engine   the engine creating this interpreter
-     * @param aContext the context to evaluate expression
-     * @param eFrame   the interpreter evaluation frame
+     * @param aContext the evaluation context, global variables, methods and functions
+     * @param opts     the evaluation options, flags modifying evaluation behavior
+     * @param eFrame   the evaluation frame, arguments and local variables
      */
-    protected Interpreter(Engine engine, JexlContext aContext, Frame eFrame) {
-        super(engine, aContext);
+    protected Interpreter(Engine engine, JexlOptions opts, JexlContext aContext, Frame eFrame) {
+        super(engine, opts, aContext);
         this.frame = eFrame;
     }
 
@@ -1122,7 +1125,7 @@ public class Interpreter extends InterpreterBase {
         JexlNode objectNode = null;
         JexlNode ptyNode = null;
         StringBuilder ant = null;
-        boolean antish = !(parent instanceof ASTReference) && options.isAntish();
+        boolean antish = !(parent instanceof ASTReference);
         int v = 1;
         main:
         for (int c = 0; c < numChildren; c++) {
@@ -1172,15 +1175,21 @@ public class Interpreter extends InterpreterBase {
                     if (first instanceof ASTIdentifier) {
                         ASTIdentifier afirst = (ASTIdentifier) first;
                         ant = new StringBuilder(afirst.getName());
-                        // skip the first node case since it was trialed in jjtAccept above and returned null
-                        if (c == 0) {
-                            continue;
-                        }
+                        // skip the else...*
                     } else {
                         // not an identifier, not antish
                         ptyNode = objectNode;
                         break main;
                     }
+                    // *... and continue
+                    if (!options.isAntish()) {
+                        antish = false;
+                        continue;
+                    }
+                    // skip the first node case since it was trialed in jjtAccept above and returned null
+                    if (c == 0) {
+                        continue;
+                    }
                 }
                 // catch up to current node
                 for (; v <= c; ++v) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index 74c8f8f..504c1c1 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -78,16 +78,17 @@ public abstract class InterpreterBase extends ParserVisitor {
     /**
      * Creates an interpreter base.
      * @param engine   the engine creating this interpreter
-     * @param aContext the context to evaluate expression
+     * @param opts     the evaluation options
+     * @param aContext the evaluation context
      */
-    protected InterpreterBase(Engine engine, JexlContext aContext) {
+    protected InterpreterBase(Engine engine, JexlOptions opts, JexlContext aContext) {
         this.jexl = engine;
         this.logger = jexl.logger;
         this.uberspect = jexl.uberspect;
         this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
         this.cache = engine.cache != null;
         JexlArithmetic jexla = jexl.arithmetic;
-        this.options = jexl.options(context);
+        this.options = opts == null? engine.options(aContext) : opts;
         this.arithmetic = jexla.options(options);
         if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass())) {
             logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Options.java b/src/main/java/org/apache/commons/jexl3/internal/Options.java
deleted file mode 100644
index 04dd182..0000000
--- a/src/main/java/org/apache/commons/jexl3/internal/Options.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.jexl3.internal;
-
-import org.apache.commons.jexl3.JexlOptions;
-import org.apache.commons.jexl3.JexlEngine;
-
-import java.math.MathContext;
-
-/**
- * A basic implementation of JexlOptions.
- * <p>Thread safety is only guaranteed if no modifications (call set* method)
- * occurs in different threads; note however that using the 'callable()' method to
- * allow a script to run as such will use a copy.
- */
-public class Options implements JexlOptions {
-    /** The local shade bit. */
-    protected static final int SHADE = 6;
-    /** The antish var bit. */
-    protected static final int ANTISH = 5;
-    /** The lexical scope bit. */
-    protected static final int LEXICAL = 4;
-    /** The safe bit. */
-    protected static final int SAFE = 3;
-    /** The silent bit. */
-    protected static final int SILENT = 2;
-    /** The strict bit. */
-    protected static final int STRICT = 1;
-    /** The cancellable bit. */
-    protected static final int CANCELLABLE = 0;
-    /** The flags names ordered. */
-    private static final String[] NAMES = {
-        "cancellable", "strict", "silent", "safe", "lexical", "antish", "lexicalShade"
-    };
-    /** Default mask .*/
-    protected static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE;
-    /** The arithmetic math context. */
-    private MathContext mathContext = null;
-    /** The arithmetic math scale. */
-    private int mathScale = Integer.MIN_VALUE;
-    /** The arithmetic strict math flag. */
-    private boolean strictArithmetic = true;
-    /** The default flags, all but safe. */
-    private int flags = DEFAULT;
-
-    /**
-     * Sets the value of a flag in a mask.
-     * @param ordinal the flag ordinal
-     * @param mask the flags mask
-     * @param value true or false
-     * @return the new flags mask value
-     */
-    protected static int set(int ordinal, int mask, boolean value) {
-        return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
-    }
-
-    /**
-     * Checks the value of a flag in the mask.
-     * @param ordinal the flag ordinal
-     * @param mask the flags mask
-     * @return the mask value with this flag or-ed in
-     */
-    protected static boolean isSet(int ordinal, int mask) {
-        return (mask & 1 << ordinal) != 0;
-    }
-        
-    /**
-     * Default ctor.
-     */
-    public Options() {}
-    
-    /**
-     * Sets the default flags.
-     * <p>Used by tests to force default options.
-     * @param flags the flags to set 
-     */
-    public static void setDefaultFlags(String...flags) {
-        DEFAULT = parseFlags(DEFAULT, flags);
-    }
-        
-    /**
-     * Parses flags by name.
-     * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
-     * The possible flag names are:
-     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
-     * @param mask the initial mask state
-     * @param flags the flags to set 
-     * @return the flag mask updated
-     */
-    public static int parseFlags(int mask, String...flags) {
-        for(String name : flags) {
-            boolean b = true;
-            if (name.charAt(0) == '+') {
-                name = name.substring(1);
-            } else if (name.charAt(0) == '-') {
-                name = name.substring(1);
-                b = false;
-            }
-            for(int flag = 0; flag < NAMES.length; ++flag) {
-                if (NAMES[flag].equals(name)) {
-                    if (b) {
-                        mask |= (1 << flag);
-                    } else {
-                        mask &= ~(1 << flag);
-                    }
-                    break;
-                }
-            }
-        }
-        return mask;
-    }
-
-    @Override
-    public Options set(JexlEngine jexl) {
-        if (jexl instanceof Engine) {
-            set(((Engine) jexl).options);
-        } else {
-            mathContext = jexl.getArithmetic().getMathContext();
-            mathScale = jexl.getArithmetic().getMathScale();
-            strictArithmetic = jexl.getArithmetic().isStrict();
-            set(STRICT, flags, jexl.isStrict());
-            set(SILENT, flags, jexl.isSilent());
-            set(SAFE, flags, jexl.isSafe());
-            set(CANCELLABLE, flags, jexl.isCancellable());
-        }
-        return this;
-    }
-    
-    @Override
-    public Options set(JexlOptions opts) {
-        if (opts instanceof Options) {
-            Options src = (Options) opts;
-            mathContext = src.mathContext;
-            mathScale = src.mathScale;
-            strictArithmetic = src.strictArithmetic;
-            flags = src.flags;
-        } else {
-            mathContext = opts.getMathContext();
-            mathScale = opts.getMathScale();
-            strictArithmetic = opts.isStrict();
-            int mask = DEFAULT;
-            mask = set(STRICT, mask, opts.isStrict());
-            mask = set(SILENT, mask, opts.isSilent());
-            mask = set(SAFE, mask, opts.isSafe());
-            mask = set(CANCELLABLE, mask, opts.isCancellable());
-            mask = set(LEXICAL, mask, opts.isLexical());
-            mask = set(SHADE, mask, opts.isLexicalShade());
-            mask = set(ANTISH, mask, opts.isAntish());
-            flags = mask;
-        }
-        return this;
-    }
-    
-    @Override
-    public Options copy() {
-        return new Options().set(this);
-    }
-    
-    @Override
-    public void setAntish(boolean flag) {
-        flags = set(ANTISH, flags, flag);
-    }
-
-    @Override
-    public void setMathContext(MathContext mcontext) {
-        this.mathContext = mcontext;
-    }
-
-    @Override
-    public void setMathScale(int mscale) {
-        this.mathScale = mscale;
-    }
-
-    @Override
-    public void setStrictArithmetic(boolean stricta) {
-        this.strictArithmetic = stricta;
-    }
-
-    @Override
-    public void setStrict(boolean flag) {
-        flags = set(STRICT, flags, flag);
-    }
-            
-    @Override
-    public void setSafe(boolean flag) {
-        flags = set(SAFE, flags, flag);
-    }    
-
-    @Override
-    public void setSilent(boolean flag) {
-        flags = set(SILENT, flags, flag);
-    }
-            
-    @Override
-    public void setCancellable(boolean flag) {
-        flags = set(CANCELLABLE, flags, flag);
-    }
-            
-    @Override
-    public void setLexical(boolean flag) {
-        flags = set(LEXICAL, flags, flag);
-    }    
-    
-    @Override
-    public void setLexicalShade(boolean flag) {
-        flags = set(SHADE, flags, flag);
-    }
-    
-    @Override
-    public boolean isAntish() {
-        return isSet(ANTISH, flags);
-    }
-    
-    @Override
-    public boolean isSilent() {
-        return isSet(SILENT, flags);
-    }
-
-    @Override
-    public boolean isStrict() {
-        return isSet(STRICT, flags);
-    }
-    
-    @Override
-    public boolean isSafe() {
-        return isSet(SAFE, flags);
-    }
-
-    @Override
-    public boolean isCancellable() {
-        return isSet(CANCELLABLE, flags);
-    }
-     
-    @Override
-    public boolean isLexical() {
-        return isSet(LEXICAL, flags);
-    }
-       
-    @Override
-    public boolean isLexicalShade() {
-        return isSet(SHADE, flags);
-    }
-
-    @Override
-    public boolean isStrictArithmetic() {
-        return strictArithmetic;
-    }
-
-    @Override
-    public MathContext getMathContext() {
-        return mathContext;
-    }
-    
-    @Override
-    public int getMathScale() {
-        return mathScale;
-    }
-    
-}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Script.java b/src/main/java/org/apache/commons/jexl3/internal/Script.java
index 62ecaca..593ad08 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Script.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java
@@ -17,6 +17,8 @@
 package org.apache.commons.jexl3.internal;
 
 import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.JexlInfo;
+import org.apache.commons.jexl3.JexlOptions;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.JexlExpression;
@@ -96,7 +98,39 @@ public class Script implements JexlScript, JexlExpression {
     protected Frame createFrame(Object[] args) {
         return script.createFrame(args);
     }
+    
+    /**
+     * Creates this script options for evaluation.
+     * <p>This also calls the pragma processor 
+     * @param context the context
+     * @return the options
+     */
+    protected JexlOptions createOptions(JexlContext context) {
+        return jexl.createOptions(this, context);
+    }
+    
+    /**
+     * A lexical script, ensures options are lexical.
+     */
+    public static class Lexical extends Script {
+        /**
+         * Sole ctor.
+         * @param engine the engine
+         * @param expr the source.
+         * @param ref the ast
+         */
+        protected Lexical(Engine engine, String expr, ASTJexlScript ref) {
+            super(engine, expr, ref);
+        }
 
+        @Override
+        public JexlOptions createOptions(JexlContext ctxt) {
+            JexlOptions opts = super.createOptions(ctxt);
+            opts.setLexical(true);
+            return opts;
+        }
+    }
+    
     /**
      * Creates this script interpreter.
      * @param context the context
@@ -104,7 +138,7 @@ public class Script implements JexlScript, JexlExpression {
      * @return  the interpreter
      */
     protected Interpreter createInterpreter(JexlContext context, Frame frame) {
-        return jexl.createInterpreter(context, frame);
+        return jexl.createInterpreter(context, frame, createOptions(context));
     }
 
     /**
@@ -215,6 +249,13 @@ public class Script implements JexlScript, JexlExpression {
     public String[] getLocalVariables() {
         return script.getLocalVariables();
     }
+    
+    /**
+     * @return the info
+     */
+    public JexlInfo getInfo() {
+        return script.jexlInfo();
+    }
 
     /**
      * Gets this script variables.
@@ -259,7 +300,7 @@ public class Script implements JexlScript, JexlExpression {
      */
     @Override
     public Callable callable(JexlContext context, Object... args) {
-        return new Callable(jexl.createInterpreter(context, script.createFrame(args)));
+        return new Callable(createInterpreter(context, script.createFrame(args)));
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
index 17b0ff9..7babe6f 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
@@ -48,7 +48,7 @@ public class TemplateInterpreter extends Interpreter {
      */
     TemplateInterpreter(Engine jexl,
             JexlContext jcontext, Frame jframe, TemplateExpression[] expressions, Writer out) {
-        super(jexl, jcontext, jframe);
+        super(jexl, null, jcontext, jframe);
         exprs = expressions;
         writer = out;
         block = new LexicalFrame(frame, null);
diff --git a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
index 5f23b11..8397ce0 100644
--- a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
+++ b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
@@ -173,7 +173,8 @@ public class AntishCallTest extends JexlTestCase {
     // JEXL-300
     @Test
     public void testSafeAnt() throws Exception {
-        JexlContext ctxt = new MapContext();
+        JexlEvalContext ctxt = new JexlEvalContext();
+        JexlOptions options = ctxt.getEngineOptions();
         ctxt.set("x.y.z", 42);
         JexlScript script;
         Object result;
@@ -182,6 +183,18 @@ public class AntishCallTest extends JexlTestCase {
         result = script.execute(ctxt);
         Assert.assertEquals(42, result);
         Assert.assertEquals(42, ctxt.get("x.y.z"));
+        
+        options.setAntish(false);
+        try {
+            result = script.execute(ctxt);
+            Assert.fail("antish var shall not be resolved");
+        } catch(JexlException.Variable xvar) {
+            Assert.assertTrue("x".equals(xvar.getVariable()));
+        } catch(JexlException xother) {
+            Assert.assertTrue(xother != null);
+        } finally {
+            options.setAntish(true);
+        }
                 
         result = null;
         script = JEXL.createScript("x?.y?.z");
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index d2b45ec..913133b 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -324,25 +324,24 @@ public class Issues300Test {
         Assert.assertEquals(52, result);
     }
 
-    public static class ClazzB {
-        public int methodB()  {
-            return 42;
-        }
-    }
-    public static class ClazzA {
-        public ClazzB methodA() {
-            return new ClazzB();
-        }
-    }
 
     @Test
-    public void test317Tentative() throws Exception {
+    public void test317() throws Exception {
         JexlEngine jexl = new JexlBuilder().strict(true).create();
         JexlContext ctxt = new MapContext();
         JexlScript script;
         Object result;
-        script = jexl.createScript("x.methodA().methodB()", "x");
-        result = script.execute(ctxt, new ClazzA());
+        JexlInfo info = new JexlInfo("test317", 1, 1);
+        script = jexl.createScript(info, "var f = "
+                + "()-> {x + x }; f",
+                "x");
+        result = script.execute(ctxt, 21);
+        Assert.assertTrue(result instanceof JexlScript);
+        script = (JexlScript) result;
+        info = JexlInfo.from(script);
+        Assert.assertNotNull(info);
+        Assert.assertEquals("test317", info.getName());
+        result = script.execute(ctxt, 21);
         Assert.assertEquals(42, result);
     }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index c59193d..3986c2e 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -19,7 +19,6 @@ package org.apache.commons.jexl3;
 import org.apache.commons.jexl3.internal.TemplateDebugger;
 import org.apache.commons.jexl3.internal.TemplateScript;
 import org.apache.commons.jexl3.internal.Debugger;
-import org.apache.commons.jexl3.internal.Options;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -806,7 +805,7 @@ public class JXLTTest extends JexlTestCase {
         }
         
         JexlOptions newOptions() {
-            options = new Options();
+            options = new JexlOptions();
             return options;
         }
     }
diff --git a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
index 55fdcad..f0c233c 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
@@ -19,7 +19,6 @@ package org.apache.commons.jexl3;
 import java.util.Collections;
 import java.util.Map;
 import org.apache.commons.jexl3.annotations.NoJexl;
-import org.apache.commons.jexl3.internal.Options;
 
 /**
  * A JEXL evaluation environment wrapping variables, namespace and options.
@@ -35,7 +34,7 @@ public class JexlEvalContext implements
     /** The namespace. */
     private final JexlContext.NamespaceResolver ns; 
     /** The options. */
-    private final JexlOptions options = new Options();
+    private final JexlOptions options = new JexlOptions();
 
     
     
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
index f8d00d3..294ea4a 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
@@ -35,7 +35,7 @@ public class JexlTestCase {
     // important can be identified by the builder  calling lexical(...).
     static {
         //JexlBuilder.setDefaultOptions("+safe", "-lexical"); 
-        JexlBuilder.setDefaultOptions("-safe", "+lexical");
+        JexlOptions.setDefaultFlags("-safe", "+lexical");
     }
     /** No parameters signature for test run. */
     private static final Class<?>[] NO_PARMS = {};
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
index cf8943e..891393a 100644
--- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -19,6 +19,7 @@ package org.apache.commons.jexl3;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.Set;
+import org.apache.commons.jexl3.internal.LexicalScope;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -344,4 +345,46 @@ public class LexicalTest {
             Assert.assertNotNull(xany);
         }
     }
+    
+    @Test
+    public void testPragmaOptions() throws Exception {
+        // same as 6d but using a pragma
+        String str = "#pragma jexl.options '+strict +lexical +lexicalShade -safe'\n"
+                + "i = 0; for (var i : [42]) i; i";
+        JexlEngine jexl = new JexlBuilder().strict(false).create();
+        JexlScript e = jexl.createScript(str);
+        JexlContext ctxt = new MapContext();
+        try {
+            Object o = e.execute(ctxt);
+            Assert.fail("i should be shaded");
+        } catch (JexlException xany) {
+            Assert.assertNotNull(xany);
+        }
+    }
+    
+    @Test
+    public void testPragmaNoop() throws Exception {
+        // unknow pragma
+        String str = "#pragma jexl.options 'no effect'\ni = -42; for (var i : [42]) i; i";
+        JexlEngine jexl = new JexlBuilder().lexical(false).strict(true).create();
+        JexlScript e = jexl.createScript(str);
+        JexlContext ctxt = new MapContext();
+        Object result = e.execute(ctxt);
+        Assert.assertEquals(42, result);
+    }
+    
+    
+    @Test
+    public void testScopeFrame() throws Exception {
+        LexicalScope scope = new LexicalScope(null);
+        for(int i = 0; i < 128; i += 2) {
+            Assert.assertTrue(scope.declareSymbol(i));
+            Assert.assertFalse(scope.declareSymbol(i));
+        }
+        for(int i = 0; i < 128; i += 2) {
+            Assert.assertTrue(scope.hasSymbol(i));
+            Assert.assertFalse(scope.hasSymbol(i + 1));
+        }
+    }
+    
 }


Re: [commons-jexl] branch master updated: JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options) Task #JEXL-307 - Variable redeclaration option

Posted by henrib <he...@apache.org>.
JexlOptions is introduced in 3.2, this class has not been released yet.
I understand your reaction but I believe it is not warranted in this case.

I also believe 'internal' classes are clearly out of the API contract - they
are documented as such.
If you believe there are already showstoppers that warrant a package change,
aka jexl4, I should not even try releasing 3.2; please let me know.

Cheers




--
Sent from: http://apache-commons.680414.n4.nabble.com/Commons-Dev-f680415.html

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org


Re: [commons-jexl] branch master updated: JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options) Task #JEXL-307 - Variable redeclaration option

Posted by sebb <se...@gmail.com>.
If you are referring to the class

org.apache.commons.jexl3.internal.Options

Is binary compat required for an internal class?

On Wed, 13 Nov 2019 at 10:20, Gary Gregory <ga...@gmail.com> wrote:

> I do not think your changes to JexlOptions are binary compatible so -1 :-(
>
> Gary
>
> On Tue, Nov 12, 2019, 17:01 <he...@apache.org> wrote:
>
> > This is an automated email from the ASF dual-hosted git repository.
> >
> > henrib pushed a commit to branch master
> > in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
> >
> >
> > The following commit(s) were added to refs/heads/master by this push:
> >      new 753d6e6  JEXL-307: tidy API (made JexlOptions a concrete non
> > derivable class), try to ensure lexical interpretation on lexical
> feature,
> > allow runtime options to be controlled through pragma (jexl.options) Task
> > #JEXL-307 - Variable redeclaration option
> > 753d6e6 is described below
> >
> > commit 753d6e634d94022871b481896db78e323d7ccfed
> > Author: henrib <he...@apache.org>
> > AuthorDate: Tue Nov 12 23:00:39 2019 +0100
> >
> >     JEXL-307: tidy API (made JexlOptions a concrete non derivable class),
> > try to ensure lexical interpretation on lexical feature, allow runtime
> > options to be controlled through pragma (jexl.options)
> >     Task #JEXL-307 - Variable redeclaration option
>


> <snip>

Re: [commons-jexl] branch master updated: JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options) Task #JEXL-307 - Variable redeclaration option

Posted by Gary Gregory <ga...@gmail.com>.
I do not think your changes to JexlOptions are binary compatible so -1 :-(

Gary

On Tue, Nov 12, 2019, 17:01 <he...@apache.org> wrote:

> This is an automated email from the ASF dual-hosted git repository.
>
> henrib pushed a commit to branch master
> in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
>
>
> The following commit(s) were added to refs/heads/master by this push:
>      new 753d6e6  JEXL-307: tidy API (made JexlOptions a concrete non
> derivable class), try to ensure lexical interpretation on lexical feature,
> allow runtime options to be controlled through pragma (jexl.options) Task
> #JEXL-307 - Variable redeclaration option
> 753d6e6 is described below
>
> commit 753d6e634d94022871b481896db78e323d7ccfed
> Author: henrib <he...@apache.org>
> AuthorDate: Tue Nov 12 23:00:39 2019 +0100
>
>     JEXL-307: tidy API (made JexlOptions a concrete non derivable class),
> try to ensure lexical interpretation on lexical feature, allow runtime
> options to be controlled through pragma (jexl.options)
>     Task #JEXL-307 - Variable redeclaration option
> ---
>  .../java/org/apache/commons/jexl3/JexlBuilder.java |  21 +-
>  .../java/org/apache/commons/jexl3/JexlContext.java |  16 ++
>  .../java/org/apache/commons/jexl3/JexlEngine.java  |  10 +-
>  .../java/org/apache/commons/jexl3/JexlInfo.java    |  11 +
>  .../java/org/apache/commons/jexl3/JexlOptions.java | 223 +++++++++++++++--
>  .../org/apache/commons/jexl3/internal/Engine.java  |  68 ++++-
>  .../apache/commons/jexl3/internal/Interpreter.java |  33 ++-
>  .../commons/jexl3/internal/InterpreterBase.java    |   7 +-
>  .../org/apache/commons/jexl3/internal/Options.java | 274
> ---------------------
>  .../org/apache/commons/jexl3/internal/Script.java  |  45 +++-
>  .../jexl3/internal/TemplateInterpreter.java        |   2 +-
>  .../org/apache/commons/jexl3/AntishCallTest.java   |  15 +-
>  .../org/apache/commons/jexl3/Issues300Test.java    |  25 +-
>  .../java/org/apache/commons/jexl3/JXLTTest.java    |   3 +-
>  .../org/apache/commons/jexl3/JexlEvalContext.java  |   3 +-
>  .../org/apache/commons/jexl3/JexlTestCase.java     |   2 +-
>  .../java/org/apache/commons/jexl3/LexicalTest.java |  43 ++++
>  17 files changed, 431 insertions(+), 370 deletions(-)
>
> diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
> b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
> index 35c8d5f..744dfe1 100644
> --- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
> +++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
> @@ -18,7 +18,6 @@
>  package org.apache.commons.jexl3;
>
>  import org.apache.commons.jexl3.internal.Engine;
> -import org.apache.commons.jexl3.internal.Options;
>  import org.apache.commons.jexl3.introspection.JexlSandbox;
>  import org.apache.commons.jexl3.introspection.JexlUberspect;
>  import org.apache.commons.logging.Log;
> @@ -81,7 +80,7 @@ public class JexlBuilder {
>      private Boolean cancellable = null;
>
>      /** The options. */
> -    private final Options options = new Options();
> +    private final JexlOptions options = new JexlOptions();
>
>      /** Whether getVariables considers all potential equivalent syntactic
> forms. */
>      private Boolean collectAll = null;
> @@ -109,23 +108,7 @@ public class JexlBuilder {
>
>      /** The features. */
>      private JexlFeatures features = null;
> -
> -    /**
> -     * Sets the default (static, shared) option flags.
> -     * <p>
> -     * Whenever possible, we recommend using JexlBuilder methods to
> unambiguously instantiate a JEXL
> -     * engine; this method should only be used for testing / validation.
> -     * <p>A '+flag' or 'flag' will set the option named 'flag' as true,
> '-flag' set as false.
> -     * The possible flag names are:
> -     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
> -     * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL
> engine creation
> -     * may ease validating JEXL3.2 in your environment.
> -     * @param flags the flags to set
> -     */
> -    public static void setDefaultOptions(String...flags) {
> -        Options.setDefaultFlags(flags);
> -    }
> -
> +
>      /**
>       * Sets the JexlUberspect instance the engine will use.
>       *
> diff --git a/src/main/java/org/apache/commons/jexl3/JexlContext.java
> b/src/main/java/org/apache/commons/jexl3/JexlContext.java
> index fdc85a3..ce730f7 100644
> --- a/src/main/java/org/apache/commons/jexl3/JexlContext.java
> +++ b/src/main/java/org/apache/commons/jexl3/JexlContext.java
> @@ -159,4 +159,20 @@ public interface JexlContext {
>           */
>          JexlOptions getEngineOptions();
>      }
> +
> +    /**
> +     * A marker interface of the JexlContext that processes pragmas.
> +     * It is called by the engine before interpreter creation; as a
> marker of
> +     * JexlContext, it is expected to have access and interact with the
> context
> +     * instance.
> +     * @since 3.2
> +     */
> +    interface PragmaProcessor {
> +        /**
> +         * Process one pragma.
> +         * @param key the key
> +         * @param value the value
> +         */
> +        void processPragma(String key, Object value);
> +    }
>  }
> diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
> b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
> index bc41a82..7b9d198 100644
> --- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
> +++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
> @@ -352,7 +352,7 @@ public abstract class JexlEngine {
>       * @return A {@link JexlScript} which can be executed using a {@link
> JexlContext}
>       * @throws JexlException if there is a problem parsing the script
>       */
> -    public abstract JexlScript createScript(JexlFeatures features,
> JexlInfo info, String source, String[] names);
> +    public abstract JexlScript createScript(JexlFeatures features,
> JexlInfo info, String source, String... names);
>
>      /**
>       * Creates a JexlScript from a String containing valid JEXL syntax.
> @@ -365,7 +365,7 @@ public abstract class JexlEngine {
>       * @return A {@link JexlScript} which can be executed using a {@link
> JexlContext}
>       * @throws JexlException if there is a problem parsing the script
>       */
> -    public final JexlScript createScript(JexlInfo info, String source,
> String[] names) {
> +    public final JexlScript createScript(JexlInfo info, String source,
> String... names) {
>          return createScript(null, info, source, names);
>      }
>
> @@ -378,7 +378,7 @@ public abstract class JexlEngine {
>       * @throws JexlException if there is a problem parsing the script.
>       */
>      public final JexlScript createScript(String scriptText) {
> -        return createScript(null, null, scriptText, null);
> +        return createScript(null, null, scriptText, (String[]) null);
>      }
>
>      /**
> @@ -404,7 +404,7 @@ public abstract class JexlEngine {
>       * @throws JexlException if there is a problem reading or parsing the
> script.
>       */
>      public final JexlScript createScript(File scriptFile) {
> -        return createScript(null, null, readSource(scriptFile), null);
> +        return createScript(null, null, readSource(scriptFile),
> (String[]) null);
>      }
>
>      /**
> @@ -445,7 +445,7 @@ public abstract class JexlEngine {
>       * @throws JexlException if there is a problem reading or parsing the
> script.
>       */
>      public final JexlScript createScript(URL scriptUrl) {
> -        return createScript(null, readSource(scriptUrl), null);
> +        return createScript(null, readSource(scriptUrl), (String[]) null);
>      }
>
>      /**
> diff --git a/src/main/java/org/apache/commons/jexl3/JexlInfo.java
> b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
> index 19a4a72..296138b 100644
> --- a/src/main/java/org/apache/commons/jexl3/JexlInfo.java
> +++ b/src/main/java/org/apache/commons/jexl3/JexlInfo.java
> @@ -17,6 +17,8 @@
>
>  package org.apache.commons.jexl3;
>
> +import org.apache.commons.jexl3.internal.Script;
> +
>  /**
>   * Helper class to carry information such as a url/file name, line and
> column for
>   * debugging information reporting.
> @@ -182,5 +184,14 @@ public class JexlInfo {
>      public final int getColumn() {
>          return column;
>      }
> +
> +    /**
> +     * Gets the info from a script.
> +     * @param script the script
> +     * @return the info
> +     */
> +    public static JexlInfo from(JexlScript script) {
> +        return script instanceof Script? ((Script) script).getInfo() :
> null;
> +    }
>  }
>
> diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
> b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
> index dc35074..c650e9b 100644
> --- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
> +++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
> @@ -18,6 +18,7 @@
>  package org.apache.commons.jexl3;
>
>  import java.math.MathContext;
> +import org.apache.commons.jexl3.internal.Engine;
>
>  /**
>   * Flags and properties that can alter the evaluation behavior.
> @@ -35,31 +36,150 @@ import java.math.MathContext;
>   * <p>This interface replaces the now deprecated JexlEngine.Options.
>   * @since 3.2
>   */
> -public interface JexlOptions {
> +public final class JexlOptions {
> +    /** The local shade bit. */
> +    private static final int SHADE = 6;
> +    /** The antish var bit. */
> +    private static final int ANTISH = 5;
> +    /** The lexical scope bit. */
> +    private static final int LEXICAL = 4;
> +    /** The safe bit. */
> +    private static final int SAFE = 3;
> +    /** The silent bit. */
> +    private static final int SILENT = 2;
> +    /** The strict bit. */
> +    private static final int STRICT = 1;
> +    /** The cancellable bit. */
> +    private static final int CANCELLABLE = 0;
> +    /** The flags names ordered. */
> +    private static final String[] NAMES = {
> +        "cancellable", "strict", "silent", "safe", "lexical", "antish",
> "lexicalShade"
> +    };
> +    /** Default mask .*/
> +    private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1
> << ANTISH | 1 << SAFE;
> +    /** The arithmetic math context. */
> +    private MathContext mathContext = null;
> +    /** The arithmetic math scale. */
> +    private int mathScale = Integer.MIN_VALUE;
> +    /** The arithmetic strict math flag. */
> +    private boolean strictArithmetic = true;
> +    /** The default flags, all but safe. */
> +    private int flags = DEFAULT;
> +
> +    /**
> +     * Sets the value of a flag in a mask.
> +     * @param ordinal the flag ordinal
> +     * @param mask the flags mask
> +     * @param value true or false
> +     * @return the new flags mask value
> +     */
> +    private static int set(int ordinal, int mask, boolean value) {
> +        return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
> +    }
> +
> +    /**
> +     * Checks the value of a flag in the mask.
> +     * @param ordinal the flag ordinal
> +     * @param mask the flags mask
> +     * @return the mask value with this flag or-ed in
> +     */
> +    private static boolean isSet(int ordinal, int mask) {
> +        return (mask & 1 << ordinal) != 0;
> +    }
> +
> +    /**
> +     * Default ctor.
> +     */
> +    public JexlOptions() {}
> +
> +    /**
> +     * Sets the default (static, shared) option flags.
> +     * <p>
> +     * Whenever possible, we recommend using JexlBuilder methods to
> unambiguously instantiate a JEXL
> +     * engine; this method should only be used for testing / validation.
> +     * <p>A '+flag' or 'flag' will set the option named 'flag' as true,
> '-flag' set as false.
> +     * The possible flag names are:
> +     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
> +     * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL
> engine creation
> +     * may ease validating JEXL3.2 in your environment.
> +     * @param flags the flags to set
> +     */
> +    public static void setDefaultFlags(String...flags) {
> +        DEFAULT = parseFlags(DEFAULT, flags);
> +    }
> +
> +    /**
> +     * Parses flags by name.
> +     * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
> +     * The possible flag names are:
> +     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
> +     * @param mask the initial mask state
> +     * @param flags the flags to set
> +     * @return the flag mask updated
> +     */
> +    public static int parseFlags(int mask, String...flags) {
> +        for(String name : flags) {
> +            boolean b = true;
> +            if (name.charAt(0) == '+') {
> +                name = name.substring(1);
> +            } else if (name.charAt(0) == '-') {
> +                name = name.substring(1);
> +                b = false;
> +            }
> +            for(int flag = 0; flag < NAMES.length; ++flag) {
> +                if (NAMES[flag].equals(name)) {
> +                    if (b) {
> +                        mask |= (1 << flag);
> +                    } else {
> +                        mask &= ~(1 << flag);
> +                    }
> +                    break;
> +                }
> +            }
> +        }
> +        return mask;
> +    }
> +
> +    /**
> +     * Sets this option flags using the +/- syntax.
> +     * @param opts the option flags
> +     */
> +    public void setFlags(String[] opts) {
> +        flags = parseFlags(flags, opts);
> +    }
> +
>      /**
>       * The MathContext instance used for +,-,/,*,% operations on big
> decimals.
>       * @return the math context
>       */
> -    MathContext getMathContext();
> +    public MathContext getMathContext() {
> +        return mathContext;
> +    }
>
>      /**
>       * The BigDecimal scale used for comparison and coercion operations.
>       * @return the scale
>       */
> -    int getMathScale();
> +    public int getMathScale() {
> +        return mathScale;
> +    }
>
>      /**
>       * Checks whether evaluation will attempt resolving antish variable
> names.
>       * @return true if antish variables are solved, false otherwise
>       */
> -    boolean isAntish();
> +    public boolean isAntish() {
> +        return isSet(ANTISH, flags);
> +    }
>
>      /**
>       * Checks whether evaluation will throw JexlException.Cancel (true) or
>       * return null (false) if interrupted.
>       * @return true when cancellable, false otherwise
>       */
> -    boolean isCancellable();
> +    public boolean isCancellable() {
> +        return isSet(CANCELLABLE, flags);
> +    }
>
>      /**
>       * Checks whether runtime variable scope is lexical.
> @@ -67,7 +187,9 @@ public interface JexlOptions {
>       * Redefining a variable in the same lexical unit will generate
> errors.
>       * @return true if scope is lexical, false otherwise
>       */
> -    boolean isLexical();
> +    public boolean isLexical() {
> +        return isSet(LEXICAL, flags);
> +    }
>
>      /**
>       * Checks whether local variables shade global ones.
> @@ -79,122 +201,169 @@ public interface JexlOptions {
>       * raise an error.
>       * @return true if lexical shading is applied, false otherwise
>       */
> -    boolean isLexicalShade();
> +    public boolean isLexicalShade() {
> +        return isSet(SHADE, flags);
> +    }
>
>      /**
>       * Checks whether the engine considers null in navigation expression
> as
>       * errors during evaluation..
>       * @return true if safe, false otherwise
>       */
> -    boolean isSafe();
> +    public boolean isSafe() {
> +        return isSet(SAFE, flags);
> +    }
>
>      /**
>       * Checks whether the engine will throw a {@link JexlException} when
> an
>       * error is encountered during evaluation.
>       * @return true if silent, false otherwise
>       */
> -    boolean isSilent();
> +    public boolean isSilent() {
> +        return isSet(SILENT, flags);
> +    }
>
>      /**
>       * Checks whether the engine considers unknown variables, methods and
>       * constructors as errors during evaluation.
>       * @return true if strict, false otherwise
>       */
> -    boolean isStrict();
> +    public boolean isStrict() {
> +        return isSet(STRICT, flags);
> +    }
>
>      /**
>       * Checks whether the arithmetic triggers errors during evaluation
> when null
>       * is used as an operand.
>       * @return true if strict, false otherwise
>       */
> -    boolean isStrictArithmetic();
> +    public boolean isStrictArithmetic() {
> +        return strictArithmetic;
> +    }
>
>      /**
>       * Sets whether the engine will attempt solving antish variable names
> from
>       * context.
>       * @param flag true if antish variables are solved, false otherwise
>       */
> -    void setAntish(boolean flag);
> +    public void setAntish(boolean flag) {
> +        flags = set(ANTISH, flags, flag);
> +    }
>
>      /**
>       * Sets whether the engine will throw JexlException.Cancel (true) or
> return
>       * null (false) when interrupted during evaluation.
>       * @param flag true when cancellable, false otherwise
>       */
> -    void setCancellable(boolean flag);
> +    public void setCancellable(boolean flag) {
> +        flags = set(CANCELLABLE, flags, flag);
> +    }
>
>      /**
>       * Sets whether the engine uses a strict block lexical scope during
>       * evaluation.
>       * @param flag true if lexical scope is used, false otherwise
>       */
> -    void setLexical(boolean flag);
> +    public void setLexical(boolean flag) {
> +        flags = set(LEXICAL, flags, flag);
> +    }
>
>      /**
>       * Sets whether the engine strictly shades global variables.
>       * Local symbols shade globals after definition and creating global
>       * variables is prohibited evaluation.
> +     * If setting to lexical shade, lexical is also set.
>       * @param flag true if creation is allowed, false otherwise
>       */
> -    void setLexicalShade(boolean flag);
> +    public void setLexicalShade(boolean flag) {
> +        flags = set(SHADE, flags, flag);
> +        if (flag) {
> +            flags = set(LEXICAL, flags, true);
> +        }
> +    }
>
>      /**
>       * Sets the arithmetic math context.
>       * @param mcontext the context
>       */
> -    void setMathContext(MathContext mcontext);
> +    public void setMathContext(MathContext mcontext) {
> +        this.mathContext = mcontext;
> +    }
>
>      /**
>       * Sets the arithmetic math scale.
>       * @param mscale the scale
>       */
> -    void setMathScale(int mscale);
> +    public void setMathScale(int mscale) {
> +        this.mathScale = mscale;
> +    }
>
>      /**
>       * Sets whether the engine considers null in navigation expression as
> errors
>       * during evaluation.
>       * @param flag true if safe, false otherwise
>       */
> -    void setSafe(boolean flag);
> +    public void setSafe(boolean flag) {
> +        flags = set(SAFE, flags, flag);
> +    }
>
>      /**
>       * Sets whether the engine will throw a {@link JexlException} when an
> error
>       * is encountered during evaluation.
>       * @param flag true if silent, false otherwise
>       */
> -    void setSilent(boolean flag);
> +    public void setSilent(boolean flag) {
> +        flags = set(SILENT, flags, flag);
> +    }
>
>      /**
>       * Sets whether the engine considers unknown variables, methods and
>       * constructors as errors during evaluation.
>       * @param flag true if strict, false otherwise
>       */
> -    void setStrict(boolean flag);
> +    public void setStrict(boolean flag) {
> +        flags = set(STRICT, flags, flag);
> +    }
>
>      /**
>       * Sets the strict arithmetic flag.
>       * @param stricta true or false
>       */
> -    void setStrictArithmetic(boolean stricta);
> +    public void setStrictArithmetic(boolean stricta) {
> +        this.strictArithmetic = stricta;
> +    }
>
>      /**
>       * Set options from engine.
>       * @param jexl the engine
>       * @return this instance
> -     */
> -    JexlOptions set(JexlEngine jexl);
> +     */
> +    public JexlOptions set(JexlEngine jexl) {
> +        if (jexl instanceof Engine) {
> +            ((Engine) jexl).optionsSet(this);
> +        }
> +        return this;
> +    }
>
>      /**
>       * Set options from options.
> -     * @param options the options
> +     * @param src the options
>       * @return this instance
>       */
> -    JexlOptions set(JexlOptions options);
> -
> +    public JexlOptions set(JexlOptions src) {
> +        mathContext = src.mathContext;
> +        mathScale = src.mathScale;
> +        strictArithmetic = src.strictArithmetic;
> +        flags = src.flags;
> +        return this;
> +    }
> +
>      /**
>       * Creates a copy of this instance.
>       * @return a copy
>       */
> -    JexlOptions copy();
> +    public JexlOptions copy() {
> +        return new JexlOptions().set(this);
> +    }
>
>  }
> \ No newline at end of file
> diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
> b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
> index d752cca..4895578 100644
> --- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
> +++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
> @@ -74,6 +74,10 @@ public class Engine extends JexlEngine {
>          private UberspectHolder() {}
>      }
>      /**
> +     * The name of the options pragma.
> +     */
> +    protected static final String PRAGMA_OPTIONS = "jexl.options";
> +    /**
>       * The Log to which all JexlEngine messages will be logged.
>       */
>      protected final Log logger;
> @@ -347,18 +351,62 @@ public class Engine extends JexlEngine {
>              cache.clear();
>          }
>      }
> -
> +
> +    /**
> +     * Sets options from this engine options.
> +     * @param opts the options to set
> +     * @return the options
> +     */
> +    public JexlOptions optionsSet(JexlOptions opts) {
> +        if (opts != null) {
> +            opts.set(options);
> +        }
> +        return opts;
> +    }
> +
> +    /**
> +     * Creates a script evaluation options.
> +     * <p>This also calls the pragma processor if any
> +     * @param script the script
> +     * @param context the context
> +     * @return the options
> +     */
> +    protected JexlOptions createOptions(Script script, JexlContext
> context) {
> +        JexlOptions opts = options(context);
> +        Map<String, Object> pragmas = script.getPragmas();
> +        if (pragmas != null) {
> +            JexlContext.PragmaProcessor processor =
> +                    context instanceof JexlContext.PragmaProcessor
> +                    ? (JexlContext.PragmaProcessor) context
> +                    : null;
> +            for(Map.Entry<String, Object> pragma : pragmas.entrySet()) {
> +                String key = pragma.getKey();
> +                Object value = pragma.getValue();
> +                if (PRAGMA_OPTIONS.equals(key) && opts != null) {
> +                    if (value instanceof String) {
> +                        String[] vs = ((String) value).split(" ");
> +                        opts.setFlags(vs);
> +                    }
> +                }
> +                if (processor != null) {
> +                    processor.processPragma(key, value);
> +                }
> +            }
> +        }
> +        return opts;
> +    }
> +
>      /**
>       * Creates an interpreter.
>       * @param context a JexlContext; if null, the empty context is used
> instead.
>       * @param frame   the interpreter frame
> +     * @param opts    the evaluation options
>       * @return an Interpreter
>       */
> -    protected Interpreter createInterpreter(JexlContext context, Frame
> frame) {
> -        return new Interpreter(this, context, frame);
> +    protected Interpreter createInterpreter(JexlContext context, Frame
> frame, JexlOptions opts) {
> +        return new Interpreter(this, opts, context, frame);
>      }
>
> -
>      @Override
>      public Script createExpression(JexlInfo info, String expression) {
>          return createScript(expressionFeatures, info, expression, null);
> @@ -371,8 +419,12 @@ public class Engine extends JexlEngine {
>          }
>          String source = trimSource(scriptText);
>          Scope scope = names == null ? null : new Scope(null, names);
> -        ASTJexlScript tree = parse(info, features == null? scriptFeatures
> : features, source, scope);
> -        return new Script(this, source, tree);
> +        JexlFeatures ftrs = features == null? scriptFeatures : features;
> +        ASTJexlScript tree = parse(info, ftrs, source, scope);
> +        // when parsing lexical, try hard to run lexical
> +        return ftrs.isLexical()
> +            ? new Script.Lexical(this, source, tree)
> +            : new Script(this, source, tree);
>      }
>
>      /**
> @@ -405,7 +457,7 @@ public class Engine extends JexlEngine {
>              final ASTJexlScript script = parse(null, PROPERTY_FEATURES,
> src, scope);
>              final JexlNode node = script.jjtGetChild(0);
>              final Frame frame = script.createFrame(bean);
> -            final Interpreter interpreter = createInterpreter(context,
> frame);
> +            final Interpreter interpreter = createInterpreter(context,
> frame, null);
>              return interpreter.visitLexicalNode(node, null);
>          } catch (JexlException xjexl) {
>              if (silent) {
> @@ -434,7 +486,7 @@ public class Engine extends JexlEngine {
>              final ASTJexlScript script = parse(null, PROPERTY_FEATURES,
> src, scope);
>              final JexlNode node = script.jjtGetChild(0);
>              final Frame frame = script.createFrame(bean, value);
> -            final Interpreter interpreter = createInterpreter(context,
> frame);
> +            final Interpreter interpreter = createInterpreter(context,
> frame, null);
>              interpreter.visitLexicalNode(node, null);
>          } catch (JexlException xjexl) {
>              if (silent) {
> diff --git
> a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
> b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
> index ed7efa7..9c27f61 100644
> --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
> +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
> @@ -17,13 +17,18 @@
>  //CSOFF: FileLength
>  package org.apache.commons.jexl3.internal;
>
> +import java.util.Iterator;
> +import java.util.concurrent.Callable;
> +
>  import org.apache.commons.jexl3.JexlArithmetic;
>  import org.apache.commons.jexl3.JexlContext;
>  import org.apache.commons.jexl3.JexlEngine;
>  import org.apache.commons.jexl3.JexlException;
>  import org.apache.commons.jexl3.JexlOperator;
> +import org.apache.commons.jexl3.JexlOptions;
>  import org.apache.commons.jexl3.JexlScript;
>  import org.apache.commons.jexl3.JxltEngine;
> +
>  import org.apache.commons.jexl3.introspection.JexlMethod;
>  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
>
> @@ -105,9 +110,6 @@ import
> org.apache.commons.jexl3.parser.ASTWhileStatement;
>  import org.apache.commons.jexl3.parser.JexlNode;
>  import org.apache.commons.jexl3.parser.Node;
>
> -import java.util.Iterator;
> -import java.util.concurrent.Callable;
> -
>  /**
>   * An interpreter of JEXL syntax.
>   *
> @@ -130,11 +132,12 @@ public class Interpreter extends InterpreterBase {
>      /**
>       * Creates an interpreter.
>       * @param engine   the engine creating this interpreter
> -     * @param aContext the context to evaluate expression
> -     * @param eFrame   the interpreter evaluation frame
> +     * @param aContext the evaluation context, global variables, methods
> and functions
> +     * @param opts     the evaluation options, flags modifying evaluation
> behavior
> +     * @param eFrame   the evaluation frame, arguments and local variables
>       */
> -    protected Interpreter(Engine engine, JexlContext aContext, Frame
> eFrame) {
> -        super(engine, aContext);
> +    protected Interpreter(Engine engine, JexlOptions opts, JexlContext
> aContext, Frame eFrame) {
> +        super(engine, opts, aContext);
>          this.frame = eFrame;
>      }
>
> @@ -1122,7 +1125,7 @@ public class Interpreter extends InterpreterBase {
>          JexlNode objectNode = null;
>          JexlNode ptyNode = null;
>          StringBuilder ant = null;
> -        boolean antish = !(parent instanceof ASTReference) &&
> options.isAntish();
> +        boolean antish = !(parent instanceof ASTReference);
>          int v = 1;
>          main:
>          for (int c = 0; c < numChildren; c++) {
> @@ -1172,15 +1175,21 @@ public class Interpreter extends InterpreterBase {
>                      if (first instanceof ASTIdentifier) {
>                          ASTIdentifier afirst = (ASTIdentifier) first;
>                          ant = new StringBuilder(afirst.getName());
> -                        // skip the first node case since it was trialed
> in jjtAccept above and returned null
> -                        if (c == 0) {
> -                            continue;
> -                        }
> +                        // skip the else...*
>                      } else {
>                          // not an identifier, not antish
>                          ptyNode = objectNode;
>                          break main;
>                      }
> +                    // *... and continue
> +                    if (!options.isAntish()) {
> +                        antish = false;
> +                        continue;
> +                    }
> +                    // skip the first node case since it was trialed in
> jjtAccept above and returned null
> +                    if (c == 0) {
> +                        continue;
> +                    }
>                  }
>                  // catch up to current node
>                  for (; v <= c; ++v) {
> diff --git
> a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
> b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
> index 74c8f8f..504c1c1 100644
> --- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
> +++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
> @@ -78,16 +78,17 @@ public abstract class InterpreterBase extends
> ParserVisitor {
>      /**
>       * Creates an interpreter base.
>       * @param engine   the engine creating this interpreter
> -     * @param aContext the context to evaluate expression
> +     * @param opts     the evaluation options
> +     * @param aContext the evaluation context
>       */
> -    protected InterpreterBase(Engine engine, JexlContext aContext) {
> +    protected InterpreterBase(Engine engine, JexlOptions opts,
> JexlContext aContext) {
>          this.jexl = engine;
>          this.logger = jexl.logger;
>          this.uberspect = jexl.uberspect;
>          this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
>          this.cache = engine.cache != null;
>          JexlArithmetic jexla = jexl.arithmetic;
> -        this.options = jexl.options(context);
> +        this.options = opts == null? engine.options(aContext) : opts;
>          this.arithmetic = jexla.options(options);
>          if (arithmetic != jexla &&
> !arithmetic.getClass().equals(jexla.getClass())) {
>              logger.warn("expected arithmetic to be " +
> jexla.getClass().getSimpleName()
> diff --git a/src/main/java/org/apache/commons/jexl3/internal/Options.java
> b/src/main/java/org/apache/commons/jexl3/internal/Options.java
> deleted file mode 100644
> index 04dd182..0000000
> --- a/src/main/java/org/apache/commons/jexl3/internal/Options.java
> +++ /dev/null
> @@ -1,274 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements.  See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache License, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License.  You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> - * See the License for the specific language governing permissions and
> - * limitations under the License.
> - */
> -
> -package org.apache.commons.jexl3.internal;
> -
> -import org.apache.commons.jexl3.JexlOptions;
> -import org.apache.commons.jexl3.JexlEngine;
> -
> -import java.math.MathContext;
> -
> -/**
> - * A basic implementation of JexlOptions.
> - * <p>Thread safety is only guaranteed if no modifications (call set*
> method)
> - * occurs in different threads; note however that using the 'callable()'
> method to
> - * allow a script to run as such will use a copy.
> - */
> -public class Options implements JexlOptions {
> -    /** The local shade bit. */
> -    protected static final int SHADE = 6;
> -    /** The antish var bit. */
> -    protected static final int ANTISH = 5;
> -    /** The lexical scope bit. */
> -    protected static final int LEXICAL = 4;
> -    /** The safe bit. */
> -    protected static final int SAFE = 3;
> -    /** The silent bit. */
> -    protected static final int SILENT = 2;
> -    /** The strict bit. */
> -    protected static final int STRICT = 1;
> -    /** The cancellable bit. */
> -    protected static final int CANCELLABLE = 0;
> -    /** The flags names ordered. */
> -    private static final String[] NAMES = {
> -        "cancellable", "strict", "silent", "safe", "lexical", "antish",
> "lexicalShade"
> -    };
> -    /** Default mask .*/
> -    protected static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1
> << ANTISH | 1 << SAFE;
> -    /** The arithmetic math context. */
> -    private MathContext mathContext = null;
> -    /** The arithmetic math scale. */
> -    private int mathScale = Integer.MIN_VALUE;
> -    /** The arithmetic strict math flag. */
> -    private boolean strictArithmetic = true;
> -    /** The default flags, all but safe. */
> -    private int flags = DEFAULT;
> -
> -    /**
> -     * Sets the value of a flag in a mask.
> -     * @param ordinal the flag ordinal
> -     * @param mask the flags mask
> -     * @param value true or false
> -     * @return the new flags mask value
> -     */
> -    protected static int set(int ordinal, int mask, boolean value) {
> -        return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
> -    }
> -
> -    /**
> -     * Checks the value of a flag in the mask.
> -     * @param ordinal the flag ordinal
> -     * @param mask the flags mask
> -     * @return the mask value with this flag or-ed in
> -     */
> -    protected static boolean isSet(int ordinal, int mask) {
> -        return (mask & 1 << ordinal) != 0;
> -    }
> -
> -    /**
> -     * Default ctor.
> -     */
> -    public Options() {}
> -
> -    /**
> -     * Sets the default flags.
> -     * <p>Used by tests to force default options.
> -     * @param flags the flags to set
> -     */
> -    public static void setDefaultFlags(String...flags) {
> -        DEFAULT = parseFlags(DEFAULT, flags);
> -    }
> -
> -    /**
> -     * Parses flags by name.
> -     * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
> -     * The possible flag names are:
> -     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
> -     * @param mask the initial mask state
> -     * @param flags the flags to set
> -     * @return the flag mask updated
> -     */
> -    public static int parseFlags(int mask, String...flags) {
> -        for(String name : flags) {
> -            boolean b = true;
> -            if (name.charAt(0) == '+') {
> -                name = name.substring(1);
> -            } else if (name.charAt(0) == '-') {
> -                name = name.substring(1);
> -                b = false;
> -            }
> -            for(int flag = 0; flag < NAMES.length; ++flag) {
> -                if (NAMES[flag].equals(name)) {
> -                    if (b) {
> -                        mask |= (1 << flag);
> -                    } else {
> -                        mask &= ~(1 << flag);
> -                    }
> -                    break;
> -                }
> -            }
> -        }
> -        return mask;
> -    }
> -
> -    @Override
> -    public Options set(JexlEngine jexl) {
> -        if (jexl instanceof Engine) {
> -            set(((Engine) jexl).options);
> -        } else {
> -            mathContext = jexl.getArithmetic().getMathContext();
> -            mathScale = jexl.getArithmetic().getMathScale();
> -            strictArithmetic = jexl.getArithmetic().isStrict();
> -            set(STRICT, flags, jexl.isStrict());
> -            set(SILENT, flags, jexl.isSilent());
> -            set(SAFE, flags, jexl.isSafe());
> -            set(CANCELLABLE, flags, jexl.isCancellable());
> -        }
> -        return this;
> -    }
> -
> -    @Override
> -    public Options set(JexlOptions opts) {
> -        if (opts instanceof Options) {
> -            Options src = (Options) opts;
> -            mathContext = src.mathContext;
> -            mathScale = src.mathScale;
> -            strictArithmetic = src.strictArithmetic;
> -            flags = src.flags;
> -        } else {
> -            mathContext = opts.getMathContext();
> -            mathScale = opts.getMathScale();
> -            strictArithmetic = opts.isStrict();
> -            int mask = DEFAULT;
> -            mask = set(STRICT, mask, opts.isStrict());
> -            mask = set(SILENT, mask, opts.isSilent());
> -            mask = set(SAFE, mask, opts.isSafe());
> -            mask = set(CANCELLABLE, mask, opts.isCancellable());
> -            mask = set(LEXICAL, mask, opts.isLexical());
> -            mask = set(SHADE, mask, opts.isLexicalShade());
> -            mask = set(ANTISH, mask, opts.isAntish());
> -            flags = mask;
> -        }
> -        return this;
> -    }
> -
> -    @Override
> -    public Options copy() {
> -        return new Options().set(this);
> -    }
> -
> -    @Override
> -    public void setAntish(boolean flag) {
> -        flags = set(ANTISH, flags, flag);
> -    }
> -
> -    @Override
> -    public void setMathContext(MathContext mcontext) {
> -        this.mathContext = mcontext;
> -    }
> -
> -    @Override
> -    public void setMathScale(int mscale) {
> -        this.mathScale = mscale;
> -    }
> -
> -    @Override
> -    public void setStrictArithmetic(boolean stricta) {
> -        this.strictArithmetic = stricta;
> -    }
> -
> -    @Override
> -    public void setStrict(boolean flag) {
> -        flags = set(STRICT, flags, flag);
> -    }
> -
> -    @Override
> -    public void setSafe(boolean flag) {
> -        flags = set(SAFE, flags, flag);
> -    }
> -
> -    @Override
> -    public void setSilent(boolean flag) {
> -        flags = set(SILENT, flags, flag);
> -    }
> -
> -    @Override
> -    public void setCancellable(boolean flag) {
> -        flags = set(CANCELLABLE, flags, flag);
> -    }
> -
> -    @Override
> -    public void setLexical(boolean flag) {
> -        flags = set(LEXICAL, flags, flag);
> -    }
> -
> -    @Override
> -    public void setLexicalShade(boolean flag) {
> -        flags = set(SHADE, flags, flag);
> -    }
> -
> -    @Override
> -    public boolean isAntish() {
> -        return isSet(ANTISH, flags);
> -    }
> -
> -    @Override
> -    public boolean isSilent() {
> -        return isSet(SILENT, flags);
> -    }
> -
> -    @Override
> -    public boolean isStrict() {
> -        return isSet(STRICT, flags);
> -    }
> -
> -    @Override
> -    public boolean isSafe() {
> -        return isSet(SAFE, flags);
> -    }
> -
> -    @Override
> -    public boolean isCancellable() {
> -        return isSet(CANCELLABLE, flags);
> -    }
> -
> -    @Override
> -    public boolean isLexical() {
> -        return isSet(LEXICAL, flags);
> -    }
> -
> -    @Override
> -    public boolean isLexicalShade() {
> -        return isSet(SHADE, flags);
> -    }
> -
> -    @Override
> -    public boolean isStrictArithmetic() {
> -        return strictArithmetic;
> -    }
> -
> -    @Override
> -    public MathContext getMathContext() {
> -        return mathContext;
> -    }
> -
> -    @Override
> -    public int getMathScale() {
> -        return mathScale;
> -    }
> -
> -}
> diff --git a/src/main/java/org/apache/commons/jexl3/internal/Script.java
> b/src/main/java/org/apache/commons/jexl3/internal/Script.java
> index 62ecaca..593ad08 100644
> --- a/src/main/java/org/apache/commons/jexl3/internal/Script.java
> +++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java
> @@ -17,6 +17,8 @@
>  package org.apache.commons.jexl3.internal;
>
>  import org.apache.commons.jexl3.JexlContext;
> +import org.apache.commons.jexl3.JexlInfo;
> +import org.apache.commons.jexl3.JexlOptions;
>  import org.apache.commons.jexl3.JexlEngine;
>  import org.apache.commons.jexl3.JexlScript;
>  import org.apache.commons.jexl3.JexlExpression;
> @@ -96,7 +98,39 @@ public class Script implements JexlScript,
> JexlExpression {
>      protected Frame createFrame(Object[] args) {
>          return script.createFrame(args);
>      }
> +
> +    /**
> +     * Creates this script options for evaluation.
> +     * <p>This also calls the pragma processor
> +     * @param context the context
> +     * @return the options
> +     */
> +    protected JexlOptions createOptions(JexlContext context) {
> +        return jexl.createOptions(this, context);
> +    }
> +
> +    /**
> +     * A lexical script, ensures options are lexical.
> +     */
> +    public static class Lexical extends Script {
> +        /**
> +         * Sole ctor.
> +         * @param engine the engine
> +         * @param expr the source.
> +         * @param ref the ast
> +         */
> +        protected Lexical(Engine engine, String expr, ASTJexlScript ref) {
> +            super(engine, expr, ref);
> +        }
>
> +        @Override
> +        public JexlOptions createOptions(JexlContext ctxt) {
> +            JexlOptions opts = super.createOptions(ctxt);
> +            opts.setLexical(true);
> +            return opts;
> +        }
> +    }
> +
>      /**
>       * Creates this script interpreter.
>       * @param context the context
> @@ -104,7 +138,7 @@ public class Script implements JexlScript,
> JexlExpression {
>       * @return  the interpreter
>       */
>      protected Interpreter createInterpreter(JexlContext context, Frame
> frame) {
> -        return jexl.createInterpreter(context, frame);
> +        return jexl.createInterpreter(context, frame,
> createOptions(context));
>      }
>
>      /**
> @@ -215,6 +249,13 @@ public class Script implements JexlScript,
> JexlExpression {
>      public String[] getLocalVariables() {
>          return script.getLocalVariables();
>      }
> +
> +    /**
> +     * @return the info
> +     */
> +    public JexlInfo getInfo() {
> +        return script.jexlInfo();
> +    }
>
>      /**
>       * Gets this script variables.
> @@ -259,7 +300,7 @@ public class Script implements JexlScript,
> JexlExpression {
>       */
>      @Override
>      public Callable callable(JexlContext context, Object... args) {
> -        return new Callable(jexl.createInterpreter(context,
> script.createFrame(args)));
> +        return new Callable(createInterpreter(context,
> script.createFrame(args)));
>      }
>
>      /**
> diff --git
> a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
> b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
> index 17b0ff9..7babe6f 100644
> ---
> a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
> +++
> b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
> @@ -48,7 +48,7 @@ public class TemplateInterpreter extends Interpreter {
>       */
>      TemplateInterpreter(Engine jexl,
>              JexlContext jcontext, Frame jframe, TemplateExpression[]
> expressions, Writer out) {
> -        super(jexl, jcontext, jframe);
> +        super(jexl, null, jcontext, jframe);
>          exprs = expressions;
>          writer = out;
>          block = new LexicalFrame(frame, null);
> diff --git a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
> b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
> index 5f23b11..8397ce0 100644
> --- a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
> +++ b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
> @@ -173,7 +173,8 @@ public class AntishCallTest extends JexlTestCase {
>      // JEXL-300
>      @Test
>      public void testSafeAnt() throws Exception {
> -        JexlContext ctxt = new MapContext();
> +        JexlEvalContext ctxt = new JexlEvalContext();
> +        JexlOptions options = ctxt.getEngineOptions();
>          ctxt.set("x.y.z", 42);
>          JexlScript script;
>          Object result;
> @@ -182,6 +183,18 @@ public class AntishCallTest extends JexlTestCase {
>          result = script.execute(ctxt);
>          Assert.assertEquals(42, result);
>          Assert.assertEquals(42, ctxt.get("x.y.z"));
> +
> +        options.setAntish(false);
> +        try {
> +            result = script.execute(ctxt);
> +            Assert.fail("antish var shall not be resolved");
> +        } catch(JexlException.Variable xvar) {
> +            Assert.assertTrue("x".equals(xvar.getVariable()));
> +        } catch(JexlException xother) {
> +            Assert.assertTrue(xother != null);
> +        } finally {
> +            options.setAntish(true);
> +        }
>
>          result = null;
>          script = JEXL.createScript("x?.y?.z");
> diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
> b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
> index d2b45ec..913133b 100644
> --- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
> +++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
> @@ -324,25 +324,24 @@ public class Issues300Test {
>          Assert.assertEquals(52, result);
>      }
>
> -    public static class ClazzB {
> -        public int methodB()  {
> -            return 42;
> -        }
> -    }
> -    public static class ClazzA {
> -        public ClazzB methodA() {
> -            return new ClazzB();
> -        }
> -    }
>
>      @Test
> -    public void test317Tentative() throws Exception {
> +    public void test317() throws Exception {
>          JexlEngine jexl = new JexlBuilder().strict(true).create();
>          JexlContext ctxt = new MapContext();
>          JexlScript script;
>          Object result;
> -        script = jexl.createScript("x.methodA().methodB()", "x");
> -        result = script.execute(ctxt, new ClazzA());
> +        JexlInfo info = new JexlInfo("test317", 1, 1);
> +        script = jexl.createScript(info, "var f = "
> +                + "()-> {x + x }; f",
> +                "x");
> +        result = script.execute(ctxt, 21);
> +        Assert.assertTrue(result instanceof JexlScript);
> +        script = (JexlScript) result;
> +        info = JexlInfo.from(script);
> +        Assert.assertNotNull(info);
> +        Assert.assertEquals("test317", info.getName());
> +        result = script.execute(ctxt, 21);
>          Assert.assertEquals(42, result);
>      }
>  }
> diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
> b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
> index c59193d..3986c2e 100644
> --- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
> +++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
> @@ -19,7 +19,6 @@ package org.apache.commons.jexl3;
>  import org.apache.commons.jexl3.internal.TemplateDebugger;
>  import org.apache.commons.jexl3.internal.TemplateScript;
>  import org.apache.commons.jexl3.internal.Debugger;
> -import org.apache.commons.jexl3.internal.Options;
>
>  import org.apache.commons.logging.Log;
>  import org.apache.commons.logging.LogFactory;
> @@ -806,7 +805,7 @@ public class JXLTTest extends JexlTestCase {
>          }
>
>          JexlOptions newOptions() {
> -            options = new Options();
> +            options = new JexlOptions();
>              return options;
>          }
>      }
> diff --git a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
> b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
> index 55fdcad..f0c233c 100644
> --- a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
> +++ b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
> @@ -19,7 +19,6 @@ package org.apache.commons.jexl3;
>  import java.util.Collections;
>  import java.util.Map;
>  import org.apache.commons.jexl3.annotations.NoJexl;
> -import org.apache.commons.jexl3.internal.Options;
>
>  /**
>   * A JEXL evaluation environment wrapping variables, namespace and
> options.
> @@ -35,7 +34,7 @@ public class JexlEvalContext implements
>      /** The namespace. */
>      private final JexlContext.NamespaceResolver ns;
>      /** The options. */
> -    private final JexlOptions options = new Options();
> +    private final JexlOptions options = new JexlOptions();
>
>
>
> diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
> b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
> index f8d00d3..294ea4a 100644
> --- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
> +++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
> @@ -35,7 +35,7 @@ public class JexlTestCase {
>      // important can be identified by the builder  calling lexical(...).
>      static {
>          //JexlBuilder.setDefaultOptions("+safe", "-lexical");
> -        JexlBuilder.setDefaultOptions("-safe", "+lexical");
> +        JexlOptions.setDefaultFlags("-safe", "+lexical");
>      }
>      /** No parameters signature for test run. */
>      private static final Class<?>[] NO_PARMS = {};
> diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
> b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
> index cf8943e..891393a 100644
> --- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java
> +++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
> @@ -19,6 +19,7 @@ package org.apache.commons.jexl3;
>  import java.io.StringReader;
>  import java.io.StringWriter;
>  import java.util.Set;
> +import org.apache.commons.jexl3.internal.LexicalScope;
>  import org.junit.Assert;
>  import org.junit.Test;
>
> @@ -344,4 +345,46 @@ public class LexicalTest {
>              Assert.assertNotNull(xany);
>          }
>      }
> +
> +    @Test
> +    public void testPragmaOptions() throws Exception {
> +        // same as 6d but using a pragma
> +        String str = "#pragma jexl.options '+strict +lexical
> +lexicalShade -safe'\n"
> +                + "i = 0; for (var i : [42]) i; i";
> +        JexlEngine jexl = new JexlBuilder().strict(false).create();
> +        JexlScript e = jexl.createScript(str);
> +        JexlContext ctxt = new MapContext();
> +        try {
> +            Object o = e.execute(ctxt);
> +            Assert.fail("i should be shaded");
> +        } catch (JexlException xany) {
> +            Assert.assertNotNull(xany);
> +        }
> +    }
> +
> +    @Test
> +    public void testPragmaNoop() throws Exception {
> +        // unknow pragma
> +        String str = "#pragma jexl.options 'no effect'\ni = -42; for (var
> i : [42]) i; i";
> +        JexlEngine jexl = new
> JexlBuilder().lexical(false).strict(true).create();
> +        JexlScript e = jexl.createScript(str);
> +        JexlContext ctxt = new MapContext();
> +        Object result = e.execute(ctxt);
> +        Assert.assertEquals(42, result);
> +    }
> +
> +
> +    @Test
> +    public void testScopeFrame() throws Exception {
> +        LexicalScope scope = new LexicalScope(null);
> +        for(int i = 0; i < 128; i += 2) {
> +            Assert.assertTrue(scope.declareSymbol(i));
> +            Assert.assertFalse(scope.declareSymbol(i));
> +        }
> +        for(int i = 0; i < 128; i += 2) {
> +            Assert.assertTrue(scope.hasSymbol(i));
> +            Assert.assertFalse(scope.hasSymbol(i + 1));
> +        }
> +    }
> +
>  }
>
>