added {...}?<msg="foo"> option

[git-p4: depot-paths = "//depot/code/antlr4/main/": change = 9300]
This commit is contained in:
parrt 2011-11-10 14:08:16 -08:00
parent 1cd3bacaf2
commit 61e9eade6a
14 changed files with 172 additions and 135 deletions

View File

@ -204,9 +204,12 @@ public class DefaultANTLRErrorStrategy implements ANTLRErrorStrategy {
throws RecognitionException
{
String ruleName = recognizer.getRuleNames()[recognizer._ctx.getRuleIndex()];
String msg = "rule "+ruleName+" failed predicate: {"+
e.predicateText+"}?";
recognizer.notifyListeners(e.offendingToken, msg, e);
String msg = "rule "+ruleName+" "+e.msg;
recognizer.notifyListeners(curToken(recognizer), msg, e);
}
private Token curToken(BaseRecognizer recognizer) {
return ((TokenStream)recognizer.getInputStream()).LT(1);
}
public void reportUnwantedToken(BaseRecognizer recognizer) {

View File

@ -28,31 +28,29 @@
*/
package org.antlr.v4.runtime;
import com.sun.istack.internal.Nullable;
import org.antlr.v4.runtime.atn.*;
/** A semantic predicate failed during validation. Validation of predicates
* occurs when normally parsing the alternative just like matching a token.
* Disambiguating predicate evaluation occurs when we hoist a predicate into
* a prediction decision.
* Disambiguating predicate evaluation occurs when we test a predicate during
* prediction.
*/
public class FailedPredicateException extends RecognitionException {
public String ruleName;
public String predicateText;
public int ruleIndex;
public int predIndex;
public String msg;
public FailedPredicateException(BaseRecognizer recognizer, String predText) {
public FailedPredicateException(BaseRecognizer recognizer) {
this(recognizer, null);
}
public FailedPredicateException(BaseRecognizer recognizer, @Nullable String msg) {
super(recognizer, recognizer.getInputStream(), recognizer._ctx);
this.predicateText = predText;
}
// public FailedPredicateException(BaseRecognizer recognizer,
// IntStream input,
// String ruleName,
// String predicateText)
// {
// super(recognizer, input, recognizer._ctx);
// this.ruleName = ruleName;
// this.predicateText = predicateText;
// }
public String toString() {
return "FailedPredicateException("+ruleName+",{"+predicateText+"}?)";
ATNState s = recognizer._interp.atn.states.get(recognizer._ctx.s);
PredicateTransition trans = (PredicateTransition)s.transition(0);
ruleIndex = trans.ruleIndex;
predIndex = trans.predIndex;
this.msg = msg;
}
}

View File

@ -447,7 +447,8 @@ ForcedAction(a, chunks) ::= "<chunks>"
ArgAction(a, chunks) ::= "<chunks>"
SemPred(p, chunks) ::= <<
if (!(<chunks>)) throw new FailedPredicateException(this, "");
setState(<p.stateNumber>);
if (!(<chunks>)) throw new FailedPredicateException(this, "<p.msg>");
>>
ActionText(t) ::= "<t.text>"

View File

@ -109,6 +109,8 @@ element returns [List<? extends SrcOp> omos]
| subrule {$omos = $subrule.omos;}
| ACTION {$omos = controller.action($ACTION);}
| SEMPRED {$omos = controller.sempred($SEMPRED);}
| ^(ACTION elementOptions) {$omos = controller.action($ACTION);}
| ^(SEMPRED elementOptions) {$omos = controller.sempred($SEMPRED);}
| treeSpec {$omos = DefaultOutputModelFactory.list($treeSpec.treeMatch);}
;
@ -209,6 +211,7 @@ elementOption
: ID
| ^(ASSIGN ID ID)
| ^(ASSIGN ID STRING_LITERAL)
| ^(ASSIGN ID DOUBLE_QUOTE_STRING_LITERAL)
;
// R E W R I T E S T U F F

View File

@ -30,9 +30,17 @@
package org.antlr.v4.codegen.model;
import org.antlr.v4.codegen.OutputModelFactory;
import org.antlr.v4.tool.ast.GrammarAST;
import org.antlr.v4.tool.ast.*;
/** */
public class SemPred extends Action {
public SemPred(OutputModelFactory factory, GrammarAST ast) { super(factory,ast); }
public String msg; // user-specified in grammar option
public SemPred(OutputModelFactory factory, GrammarAST ast) {
super(factory,ast);
this.msg = ((PredAST)ast).getOption("msg");
if ( msg==null ) {
msg = "failed predicate: "+ast.getText();
}
}
}

View File

@ -276,7 +276,9 @@ optionValue
| // The value is a long string
//
STRING_LITERAL<TerminalAST>
STRING_LITERAL
| DOUBLE_QUOTE_STRING_LITERAL
| // The value was an integer number
//
@ -594,8 +596,7 @@ element
| -> atom
)
| ebnf
| ACTION<ActionAST>
| SEMPRED -> SEMPRED<PredAST>
| actionElement
| treeSpec
( ebnfSuffix -> ^( ebnfSuffix ^(BLOCK<BlockAST>[$treeSpec.start,"BLOCK"] ^(ALT<AltAST> treeSpec ) ) )
| -> treeSpec
@ -628,20 +629,24 @@ element
recover(input,re);
}
actionElement
@after {
GrammarAST options = (GrammarAST)$tree.getFirstChildWithType(ANTLRParser.ELEMENT_OPTIONS);
if ( options!=null ) {
Grammar.setNodeOptions($tree, options);
}
}
: ACTION<ActionAST>
| ACTION elementOptions -> ^(ACTION<ActionAST> elementOptions)
| SEMPRED<PredAST>
| SEMPRED elementOptions -> ^(SEMPRED<PredAST> elementOptions)
;
labeledElement
: id (ass=ASSIGN|ass=PLUS_ASSIGN)
( atom -> ^($ass id atom)
| block (op=ROOT|op=BANG)? -> {$op!=null}? ^($ass id ^($op block))
-> ^($ass id block)
/*
| {buildAST}? blockSet
{
RecognitionException e =
new v4ParserException("can't '"+
input.LT(1).getText()+" "+input.LT(2).getText()+"'", input);
reportError(missingSemi);
}
*/
)
;
@ -710,14 +715,6 @@ ebnfSuffix
;
atom
@after {
if ( $tree.getType()==DOT ) {
GrammarAST options = (GrammarAST)$tree.getFirstChildWithType(ANTLRParser.OPTIONS);
if ( options!=null ) {
Grammar.setNodeOptions($tree, options);
}
}
}
: // Qualified reference delegate.rule. This must be
// lexically contiguous (no spaces either side of the DOT)
// otherwise it is two references with a wildcard in between
@ -736,7 +733,18 @@ atom
| terminal (ROOT^ | BANG^)?
| ruleref
| notSet (ROOT^|BANG^)?
| // Wildcard '.' means any character in a lexer, any
| wildcard
;
catch [RecognitionException re] { throw re; } // pass upwards to element
wildcard
@after {
GrammarAST options = (GrammarAST)$tree.getFirstChildWithType(ANTLRParser.ELEMENT_OPTIONS);
if ( options!=null ) {
Grammar.setNodeOptions($tree, options);
}
}
: // Wildcard '.' means any character in a lexer, any
// token in parser and any node or subtree in a tree parser
// Because the terminal rule is allowed to be the node
// specification for the start of a tree rule, we must
@ -745,7 +753,6 @@ atom
-> {astop!=null}? ^($astop ^(WILDCARD<TerminalAST>[$DOT] elementOptions?))
-> ^(WILDCARD<TerminalAST>[$DOT] elementOptions?)
;
catch [RecognitionException re] { throw re; } // pass upwards to element
// --------------------
// Inverted element set
@ -837,14 +844,14 @@ elementOptions
: LT elementOption (COMMA elementOption)* GT -> ^(ELEMENT_OPTIONS[$LT,"ELEMENT_OPTIONS"] elementOption+)
;
// WHen used with elements we can specify what the tree node type can
// When used with elements we can specify what the tree node type can
// be and also assign settings of various options (which we do not check here)
elementOption
: // This format indicates the default node option
: // This format indicates the default element option
qid
| // This format indicates option assignment
id ASSIGN^ (qid | STRING_LITERAL<TerminalAST>)
id ASSIGN^ (qid | STRING_LITERAL | DOUBLE_QUOTE_STRING_LITERAL)
;
rewrite
@ -897,7 +904,7 @@ rewriteTreeElement
rewriteTreeAtom
@after {
GrammarAST options = (GrammarAST)$tree.getFirstChildWithType(ANTLRParser.OPTIONS);
GrammarAST options = (GrammarAST)$tree.getFirstChildWithType(ANTLRParser.ELEMENT_OPTIONS);
if ( options!=null ) {
Grammar.setNodeOptions($tree, options);
}

View File

@ -95,6 +95,8 @@ element returns [ATNFactory.Handle p]
| subrule {$p = $subrule.p;}
| ACTION {$p = factory.action((ActionAST)$ACTION);}
| SEMPRED {$p = factory.sempred((PredAST)$SEMPRED);}
| ^(ACTION .) {$p = factory.action((ActionAST)$ACTION);}
| ^(SEMPRED .) {$p = factory.sempred((PredAST)$SEMPRED);}
| treeSpec {$p = $treeSpec.p;}
| ^(ROOT a=astOperand) {$p = $a.p;}
| ^(BANG a=astOperand) {$p = $a.p;}

View File

@ -152,10 +152,10 @@ public void discoverSTRewrite(GrammarAST rew) { }
public void discoverTreeRewrite(GrammarAST rew) { }
public void ruleRef(GrammarAST ref, ActionAST arg) { }
public void tokenRef(TerminalAST ref, GrammarAST options) { }
public void terminalOption(TerminalAST t, GrammarAST ID, GrammarAST value) { }
public void stringRef(TerminalAST ref, GrammarAST options) { }
public void wildcardRef(GrammarAST ref, GrammarAST options) { }
public void tokenRef(TerminalAST ref) { }
public void elementOption(GrammarASTWithOptions t, GrammarAST ID, GrammarAST value) { }
public void stringRef(TerminalAST ref) { }
public void wildcardRef(GrammarAST ref) { }
public void actionInAlt(ActionAST action) { }
public void sempredInAlt(PredAST pred) { }
public void label(GrammarAST op, GrammarAST ID, GrammarAST element) { }
@ -165,9 +165,9 @@ public void bangOp(GrammarAST op, GrammarAST opnd) { }
public void discoverRewrites(GrammarAST result) { }
public void finishRewrites(GrammarAST result) { }
public void rewriteTokenRef(TerminalAST ast, GrammarAST options, ActionAST arg) { }
public void rewriteTokenRef(TerminalAST ast, ActionAST arg) { }
public void rewriteTerminalOption(TerminalAST t, GrammarAST ID, GrammarAST value) { }
public void rewriteStringRef(TerminalAST ast, GrammarAST options) { }
public void rewriteStringRef(TerminalAST ast) { }
public void rewriteRuleRef(GrammarAST ast) { }
public void rewriteLabelRef(GrammarAST ast) { }
public void rewriteAction(ActionAST ast) { }
@ -224,6 +224,7 @@ optionValue returns [String v]
@init {$v = $start.token.getText();}
: ID
| STRING_LITERAL
| DOUBLE_QUOTE_STRING_LITERAL
| INT
| STAR
;
@ -354,6 +355,9 @@ element
| subrule
| ACTION {actionInAlt((ActionAST)$ACTION);}
| SEMPRED {sempredInAlt((PredAST)$SEMPRED);}
| ^(ACTION elementOptions) {actionInAlt((ActionAST)$ACTION);}
| ^(SEMPRED elementOptions) {sempredInAlt((PredAST)$SEMPRED);}
| treeSpec
| ^(ROOT astOperand) {rootOp($ROOT, $astOperand.start);}
| ^(BANG astOperand) {bangOp($BANG, $astOperand.start);}
@ -398,8 +402,8 @@ ebnfSuffix
atom: range
| ^(DOT ID terminal)
| ^(DOT ID ruleref)
| ^(WILDCARD elementOptions) {wildcardRef($WILDCARD, $elementOptions.start);}
| WILDCARD {wildcardRef($WILDCARD, null);}
| ^(WILDCARD elementOptions) {wildcardRef($WILDCARD);}
| WILDCARD {wildcardRef($WILDCARD);}
| terminal
| blockSet
| ruleref
@ -410,12 +414,12 @@ blockSet
;
setElement
: STRING_LITERAL {stringRef((TerminalAST)$STRING_LITERAL, null);}
| TOKEN_REF {tokenRef((TerminalAST)$TOKEN_REF, null);}
: STRING_LITERAL {stringRef((TerminalAST)$STRING_LITERAL);}
| TOKEN_REF {tokenRef((TerminalAST)$TOKEN_REF);}
| ^(RANGE a=STRING_LITERAL b=STRING_LITERAL)
{
stringRef((TerminalAST)$a, null);
stringRef((TerminalAST)$b, null);
stringRef((TerminalAST)$a);
stringRef((TerminalAST)$b);
}
;
@ -437,20 +441,21 @@ range
terminal
: ^(STRING_LITERAL elementOptions)
{stringRef((TerminalAST)$STRING_LITERAL, $elementOptions.start);}
| STRING_LITERAL {stringRef((TerminalAST)$STRING_LITERAL, null);}
| ^(TOKEN_REF elementOptions) {tokenRef((TerminalAST)$TOKEN_REF, $elementOptions.start);}
| TOKEN_REF {tokenRef((TerminalAST)$TOKEN_REF, null);}
{stringRef((TerminalAST)$STRING_LITERAL);}
| STRING_LITERAL {stringRef((TerminalAST)$STRING_LITERAL);}
| ^(TOKEN_REF elementOptions) {tokenRef((TerminalAST)$TOKEN_REF);}
| TOKEN_REF {tokenRef((TerminalAST)$TOKEN_REF);}
;
elementOptions
: ^(ELEMENT_OPTIONS elementOption[(TerminalAST)$start.getParent()]+)
: ^(ELEMENT_OPTIONS elementOption[(GrammarASTWithOptions)$start.getParent()]+)
;
elementOption[TerminalAST t]
: ID {terminalOption(t, $ID, null);}
| ^(ASSIGN id=ID v=ID) {terminalOption(t, $id, $v);}
| ^(ASSIGN ID v=STRING_LITERAL) {terminalOption(t, $ID, $v);}
elementOption[GrammarASTWithOptions t]
: ID {elementOption(t, $ID, null);}
| ^(ASSIGN id=ID v=ID) {elementOption(t, $id, $v);}
| ^(ASSIGN ID v=STRING_LITERAL) {elementOption(t, $ID, $v);}
| ^(ASSIGN ID v=DOUBLE_QUOTE_STRING_LITERAL) {elementOption(t, $ID, $v);}
;
rewrite
@ -487,16 +492,16 @@ rewriteTreeElement
rewriteTreeAtom
: ^(TOKEN_REF rewriteElementOptions ARG_ACTION)
{rewriteTokenRef((TerminalAST)$start,$rewriteElementOptions.start,(ActionAST)$ARG_ACTION);}
{rewriteTokenRef((TerminalAST)$start,(ActionAST)$ARG_ACTION);}
| ^(TOKEN_REF rewriteElementOptions)
{rewriteTokenRef((TerminalAST)$start,$rewriteElementOptions.start,null);}
{rewriteTokenRef((TerminalAST)$start,null);}
| ^(TOKEN_REF ARG_ACTION)
{rewriteTokenRef((TerminalAST)$start,null,(ActionAST)$ARG_ACTION);}
| TOKEN_REF {rewriteTokenRef((TerminalAST)$start,null,null);}
{rewriteTokenRef((TerminalAST)$start,(ActionAST)$ARG_ACTION);}
| TOKEN_REF {rewriteTokenRef((TerminalAST)$start,null);}
| RULE_REF {rewriteRuleRef($start);}
| ^(STRING_LITERAL rewriteElementOptions)
{rewriteStringRef((TerminalAST)$start,$rewriteElementOptions.start);}
| STRING_LITERAL {rewriteStringRef((TerminalAST)$start,null);}
{rewriteStringRef((TerminalAST)$start);}
| STRING_LITERAL {rewriteStringRef((TerminalAST)$start);}
| LABEL {rewriteLabelRef($start);}
| ACTION {rewriteAction((ActionAST)$start);}
;

View File

@ -31,11 +31,8 @@ package org.antlr.v4.semantics;
import org.antlr.runtime.Token;
import org.antlr.v4.misc.Utils;
import org.antlr.v4.parse.ANTLRParser;
import org.antlr.v4.parse.GrammarTreeVisitor;
import org.antlr.v4.tool.ErrorManager;
import org.antlr.v4.tool.ErrorType;
import org.antlr.v4.tool.Grammar;
import org.antlr.v4.parse.*;
import org.antlr.v4.tool.*;
import org.antlr.v4.tool.ast.*;
import org.stringtemplate.v4.misc.MultiMap;
@ -130,6 +127,13 @@ public class BasicSemanticChecks extends GrammarTreeVisitor {
}
};
public static final Set<String> legalSemPredOptions =
new HashSet<String>() {
{
add("msg");
}
};
/** Set of valid imports. E.g., can only import a tree parser into
* another tree parser. Maps delegate to set of delegator grammar types.
* validDelegations.get(LEXER) gives list of the kinds of delegators
@ -225,10 +229,10 @@ public class BasicSemanticChecks extends GrammarTreeVisitor {
}
@Override
public void terminalOption(TerminalAST t, GrammarAST ID, GrammarAST value) {
public void elementOption(GrammarASTWithOptions elem, GrammarAST ID, GrammarAST value) {
String v = null;
if ( value!=null ) v = value.getText();
boolean ok = checkTokenOptions(ID, v);
boolean ok = checkElementOptions(elem, ID, v);
// if ( ok ) {
// if ( v!=null ) {
// t.setOption(ID.getText(), v);
@ -268,7 +272,7 @@ public class BasicSemanticChecks extends GrammarTreeVisitor {
}
@Override
public void wildcardRef(GrammarAST ref, GrammarAST options) {
public void wildcardRef(GrammarAST ref) {
checkWildcardRoot(ref);
}
@ -400,8 +404,30 @@ public class BasicSemanticChecks extends GrammarTreeVisitor {
return ok;
}
/** Check option is appropriate for token; parent is ELEMENT_OPTIONS */
boolean checkTokenOptions(GrammarAST ID, String value) {
/** Check option is appropriate for elem; parent of ID is ELEMENT_OPTIONS */
boolean checkElementOptions(GrammarASTWithOptions elem, GrammarAST ID, String value) {
if ( elem instanceof TerminalAST ) {
return checkTokenOptions((TerminalAST)elem, ID, value);
}
if ( elem.getType()==ANTLRParser.ACTION ) {
return false;
}
if ( elem.getType()==ANTLRParser.SEMPRED ) {
Token optionID = ID.token;
String fileName = optionID.getInputStream().getSourceName();
if ( value!=null && !legalSemPredOptions.contains(optionID.getText()) ) {
g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
fileName,
optionID,
optionID.getText());
return false;
}
}
return false;
}
boolean checkTokenOptions(TerminalAST elem, GrammarAST ID, String value) {
Token optionID = ID.token;
String fileName = optionID.getInputStream().getSourceName();
// don't care about ID<ASTNodeName> options

View File

@ -30,12 +30,9 @@
package org.antlr.v4.semantics;
import org.antlr.v4.parse.GrammarTreeVisitor;
import org.antlr.v4.tool.ast.ActionAST;
import org.antlr.v4.tool.ast.GrammarAST;
import org.antlr.v4.tool.ast.TerminalAST;
import org.antlr.v4.tool.ast.*;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
public class RewriteRefs extends GrammarTreeVisitor {
List<GrammarAST> shallow = new ArrayList<GrammarAST>();
@ -52,12 +49,12 @@ public class RewriteRefs extends GrammarTreeVisitor {
}
@Override
public void rewriteTokenRef(TerminalAST ast, GrammarAST options, ActionAST arg) {
public void rewriteTokenRef(TerminalAST ast, ActionAST arg) {
track(ast);
}
@Override
public void rewriteStringRef(TerminalAST ast, GrammarAST options) {
public void rewriteStringRef(TerminalAST ast) {
track(ast);
}

View File

@ -29,18 +29,11 @@
package org.antlr.v4.semantics;
import org.antlr.v4.parse.GrammarTreeVisitor;
import org.antlr.v4.parse.ScopeParser;
import org.antlr.v4.tool.AttributeDict;
import org.antlr.v4.tool.Grammar;
import org.antlr.v4.tool.LabelElementPair;
import org.antlr.v4.tool.Rule;
import org.antlr.v4.parse.*;
import org.antlr.v4.tool.*;
import org.antlr.v4.tool.ast.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/** Collects (create) rules, terminals, strings, actions, scopes etc... from AST
* side-effects: sets resolver field of asts for actions and
@ -178,7 +171,7 @@ public class SymbolCollector extends GrammarTreeVisitor {
}
@Override
public void stringRef(TerminalAST ref, GrammarAST options) {
public void stringRef(TerminalAST ref) {
terminals.add(ref);
strings.add(ref.getText());
if ( currentRule!=null ) {
@ -187,7 +180,7 @@ public class SymbolCollector extends GrammarTreeVisitor {
}
@Override
public void tokenRef(TerminalAST ref, GrammarAST options) {
public void tokenRef(TerminalAST ref) {
terminals.add(ref);
tokenIDRefs.add(ref);
if ( currentRule!=null ) {
@ -211,12 +204,12 @@ public class SymbolCollector extends GrammarTreeVisitor {
public void rewriteRuleRef(GrammarAST ast) { rewriteElements.add(ast); }
@Override
public void rewriteStringRef(TerminalAST ast, GrammarAST options) {
public void rewriteStringRef(TerminalAST ast) {
rewriteElements.add(ast);
}
@Override
public void rewriteTokenRef(TerminalAST ast, GrammarAST options, ActionAST arg) {
public void rewriteTokenRef(TerminalAST ast, ActionAST arg) {
rewriteElements.add(ast);
if ( arg!=null ) arg.resolver = currentRule.alt[currentOuterAltNumber];
}

View File

@ -29,26 +29,17 @@
package org.antlr.v4.tool;
import org.antlr.runtime.tree.TreeVisitor;
import org.antlr.runtime.tree.TreeVisitorAction;
import org.antlr.runtime.tree.TreeWizard;
import org.antlr.runtime.tree.*;
import org.antlr.v4.Tool;
import org.antlr.v4.misc.CharSupport;
import org.antlr.v4.misc.OrderedHashMap;
import org.antlr.v4.parse.ANTLRParser;
import org.antlr.v4.parse.GrammarASTAdaptor;
import org.antlr.v4.parse.GrammarTreeVisitor;
import org.antlr.v4.parse.TokenVocabParser;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.misc.*;
import org.antlr.v4.parse.*;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.misc.IntSet;
import org.antlr.v4.runtime.misc.IntervalSet;
import org.antlr.v4.runtime.misc.*;
import org.antlr.v4.tool.ast.*;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.util.*;
public class Grammar implements AttributeResolver {
@ -756,7 +747,7 @@ public class Grammar implements AttributeResolver {
final Set<String> strings = new HashSet<String>();
GrammarTreeVisitor collector = new GrammarTreeVisitor() {
@Override
public void stringRef(TerminalAST ref, GrammarAST options) {
public void stringRef(TerminalAST ref) {
strings.add(ref.getText());
}
};

View File

@ -35,7 +35,7 @@ import org.antlr.v4.tool.AttributeResolver;
import java.util.List;
public class ActionAST extends GrammarAST implements RuleElementAST {
public class ActionAST extends GrammarASTWithOptions implements RuleElementAST {
// Alt, rule, grammar space
public AttributeResolver resolver;
public List<Token> chunks; // useful for ANTLR IDE developers

View File

@ -30,9 +30,9 @@
package org.antlr.v4.tool.ast;
import org.antlr.runtime.Token;
import org.antlr.v4.misc.CharSupport;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
public abstract class GrammarASTWithOptions extends GrammarAST {
protected Map<String, String> options;
@ -49,6 +49,9 @@ public abstract class GrammarASTWithOptions extends GrammarAST {
public void setOption(String key, String value) {
if ( options==null ) options = new HashMap<String, String>();
if ( value.startsWith("'") || value.startsWith("\"") ) {
value = CharSupport.getStringFromGrammarStringLiteral(value);
}
options.put(key, value);
}