diff options
| author | Cliff Click <cliffc@acm.org> | 2024-10-12 14:02:06 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2024-10-23 17:51:09 -0700 |
| commit | 1ee105dce9c32460feed5c3a20c42abbaa7c1896 (patch) | |
| tree | 1746b458d995622bc11d796e48fe288240709f99 /src/main/java/com/seaofnodes/simple/Parser.java | |
| parent | 67dc6202dee2ce983778ba3143cc3f00196aeb7c (diff) | |
| download | Simple-linear-chapter16.tar.gz Simple-linear-chapter16.zip | |
Chapter 16: Constructors and Final fieldslinear-chapter16
Co-authored-by: Thalia Archibald <thalia@archibald.dev>
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/Parser.java')
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/Parser.java | 446 |
1 files changed, 273 insertions, 173 deletions
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 4bb18d6b..a8064e17 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,12 +2,13 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; +import static com.seaofnodes.simple.Utils.TODO; import java.util.*; /** * The Parser converts a Simple source program to the Sea of Nodes intermediate - * representation directly in one pass. There is no intermediate Abstract + * representation directly in one pass. There is no intermediate Abstract * Syntax Tree structure. * <p> * This is a simple recursive descent parser. All lexical analysis is done here as well. @@ -95,35 +96,26 @@ public class Parser { // Mapping from a type name to a Type. The string name matches // `type.str()` call. No TypeMemPtrs are in here, because Simple does not // have C-style '*ptr' references. - public static HashMap<String, Type> TYPES = new HashMap<>(); + public final HashMap<String, Type> TYPES; + + // Mapping from a type name to the constructor for a Type. + public final HashMap<String, StructNode> INITS; + public Parser(String source, TypeInteger arg) { Node.reset(); IterPeeps.reset(); - TYPES.clear(); - TYPES.put("bool",TypeInteger.U1 ); - TYPES.put("byte",TypeInteger.U8 ); - TYPES.put("f32" ,TypeFloat .B32); - TYPES.put("f64" ,TypeFloat .BOT); - TYPES.put("flt" ,TypeFloat .BOT); - TYPES.put("i16" ,TypeInteger.I16); - TYPES.put("i32" ,TypeInteger.I32); - TYPES.put("i64" ,TypeInteger.BOT); - TYPES.put("i8" ,TypeInteger.I8 ); - TYPES.put("int" ,TypeInteger.BOT); - TYPES.put("u1" ,TypeInteger.U1 ); - TYPES.put("u16" ,TypeInteger.U16); - TYPES.put("u32" ,TypeInteger.U32); - TYPES.put("u8" ,TypeInteger.U8 ); SCHEDULED = false; _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; - START = new StartNode(new Type[]{ Type.CONTROL, arg }); + START = new StartNode(arg); STOP = new StopNode(source); ZERO = con(0).keep(); XCTRL= new XCtrlNode().peephole().keep(); ALIAS = 2; // alias 0 for the control, 1 for memory + TYPES = defaultTypes(); + INITS = new HashMap<>(); } public Parser(String source) { @@ -133,13 +125,29 @@ public class Parser { @Override public String toString() { return _lexer.toString(); } - String src() { return new String( _lexer._input ); } + public static HashMap<String, Type> defaultTypes() { + return new HashMap<>() {{ + put("bool",TypeInteger.U1 ); + put("byte",TypeInteger.U8 ); + put("f32" ,TypeFloat .B32); + put("f64" ,TypeFloat .BOT); + put("flt" ,TypeFloat .BOT); + put("i16" ,TypeInteger.I16); + put("i32" ,TypeInteger.I32); + put("i64" ,TypeInteger.BOT); + put("i8" ,TypeInteger.I8 ); + put("int" ,TypeInteger.BOT); + put("u1" ,TypeInteger.U1 ); + put("u16" ,TypeInteger.U16); + put("u32" ,TypeInteger.U32); + put("u8" ,TypeInteger.U8 ); + }}; + } // Debugging utility to find a Node by index public static Node find(int nid) { return START.find(nid); } private Node ctrl() { return _scope.ctrl(); } - private Node ctrl(Node n) { return _scope.ctrl(n); } public StopNode parse() { return parse(false); } @@ -147,8 +155,12 @@ public class Parser { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 1, ScopeNode.ARG0).peephole()); + ScopeMinNode mem = new ScopeMinNode(); + mem.addDef(null); + _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole()); + mem.addDef( new ProjNode(START, 1, ScopeNode.MEM0).peephole()); + _scope.define(ScopeNode.MEM0, TypeMem.TOP , false, mem .peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); // Parse whole program parseBlock(); @@ -157,6 +169,9 @@ public class Parser { STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole()); _scope.pop(); _xScopes.pop(); + for( StructNode init : INITS.values() ) + init.unkeep().kill(); + INITS.clear(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); if( show ) showGraph(); @@ -205,49 +220,6 @@ public class Parser { // to ambiguity else return parseExpressionStatement(); } - - /** - * Parse a struct field. - * <pre> - * type IDENTIFIER ; - * </pre> - */ - private Field parseField() { - Type t = type(); - if( t==null ) - throw errorSyntax("Requires a field type, found '"+_lexer.getAnyNextToken()+"'"); - return require(Field.make(requireId().intern(),ALIAS++,t),";"); - } - - /** - * Parse a struct declaration, and return the following statement. - * Only allowed in top level scope. - * Structs cannot be redefined. - * - * @return The statement following the struct - */ - private Node parseStruct() { - if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); - String typeName = requireId(); - Type t = TYPES.get(typeName); - if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) ) - throw errorSyntax("struct '" + typeName + "' cannot be redefined"); - // Parse a collection of fields - ArrayList<Field> fields = new ArrayList<>(); - require("{"); - while (!peek('}') && !_lexer.isEOF()) { - Field field = parseField(); - if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); - fields.add(field); - } - require("}"); - // Build and install the TypeStruct - TypeStruct ts = TypeStruct.make(typeName, fields.toArray(new Field[fields.size()])); - TYPES.put(typeName, TypeMemPtr.make(ts)); - START.addMemProj(ts, _scope); // Insert memory edges - return parseStatement(); - } - /** * Parses a while statement * @@ -328,6 +300,8 @@ public class Parser { // At exit the false control is the current control, and // the scope is the exit scope after the exit test. + _xScopes.pop(); + _xScopes.push(exit); return _scope = exit; } @@ -336,15 +310,15 @@ public class Parser { ctrl(XCTRL); // Kill current scope // Prune nested lexical scopes that have depth > than the loop head // We use _breakScope as a proxy for the loop head scope to obtain the depth - while( cur._idxs.size() > _breakScope._idxs.size() ) + while( cur._lexSize.size() > _breakScope._lexSize.size() ) cur.pop(); // If this is a continue then first time the target is null // So we just use the pruned current scope as the base for the // "continue" - if (toScope == null) + if( toScope == null ) return cur; // toScope is either the break scope, or a scope that was created here - assert toScope._idxs.size() <= _breakScope._idxs.size(); + assert toScope._lexSize.size() <= _breakScope._lexSize.size(); toScope.ctrl(toScope.mergeScopes(cur)); return toScope; } @@ -429,43 +403,44 @@ public class Parser { return null; } - /** - * Parses an expression statement or a declaration statement where type is a struct - * - * <pre> - * name; // Error - * type name; // Define name with default initial value - * type name = expr; // Define name with given initial value - * name = expr; // Reassign existing - * expr // Something else - * </pre> - * @return an expression {@link Node}, never {@code null} + /** Parse: name [=expr] */ - private Node parseExpressionStatement() { + private Node parseAsgn(Type t, boolean xfinal) { + boolean isDecl = t!=null; // Having a type is a declaration, missing one is updating a prior name int old = _lexer._position; - Type t = type(); String name = requireId(); Node expr; - if( match(";") ) { // Assign a default value - // No type and no expr is an error + if( peek(';') || peek(',') ) { + // Bare "name" is not allowed if( t==null ) throw errorSyntax("expression"); - expr = new ConstantNode(t.makeInit()).peephole(); - } else if( match("=") ) { // Assign "= expr;" - expr = require(parseExpression(), ";"); - } else { // Neither, so just a normal expression parse + // Does this declaration need an initializer and not getting one? + Type init = xfinal || t instanceof TypeMemPtr tmp && !tmp._nil + // Must be initialized in the "new" constructor + ? Type.TOP + // Else takes the default + : t.makeInit(); + expr = con(init); + + } else if( !match("=") ) { // Something else _lexer._position = old; - return require(parseExpression(),";"); - } + return null; + } else + expr = parseExpression(); // Defining a new variable vs updating an old one - if( t != null ) { - if( _scope.define(name,t,expr) == null ) - throw error("Redefining name '" + name + "'"); - } else { - if( _scope.lookup(name)==null ) + if( !isDecl ) { // Assigning over an existing name + // Lookup + ScopeMinNode.Var def = _scope.lookup(name); + if( def==null ) throw error("Undefined name '" + name + "'"); - t = _scope.lookupDeclaredType(name); + // TOP fields are for late-initialized fields; these have never + // been written to, and this must be the final write. Other writes + // need to check the final bit. + if( _scope.in(def._idx)._type!=Type.TOP && def._final ) + throw error("Cannot reassign final '"+name+"'"); + t = def._type; // Declared field type } + // Auto-widen int to float if( expr._type instanceof TypeInteger && t instanceof TypeFloat ) expr = new ToFloatNode(expr).peephole(); @@ -478,9 +453,93 @@ public class Parser { // Type is sane if( !e.isa(t) ) throw error("Type " + e.str() + " is not of declared type " + t.str()); - return _scope.update(name,expr); + + if( isDecl ) { + if( !_scope.define(name,t,xfinal,expr) ) + throw error("Redefining name '" + name + "'"); + } else + _scope.update(name,expr); + return expr; + } + + /** Parse final: [!]asgn + */ + private Node parseFinal(Type t) { + boolean xfinal = t!=null && match("!"); + return parseAsgn(t,xfinal); + } + + /** + * Parses an expression statement or a declaration statement where type is a struct + * + * <pre> + * type decl [, decl]*; // Define many names with the same type + * asgn; // Assign or load a variable + * expr; // Something else + * name; // Bare name is an error + + * </pre> + * @return an expression {@link Node}, never {@code null} + */ + private Node parseExpressionStatement() { + Node n; + Type t = type(); + if( t != null ) { + // now parse final [, final]* + n = parseFinal(t); + while( match(",") ) + n = parseFinal(t); + + // Parse "asgn;" which is just "name = expr;" + } else if( ( n = parseAsgn(null,false)) == null ) { + // Something else + n = parseExpression(); + } + return require(n,";"); } + + /** + * Parse a struct declaration, and return the following statement. + * Only allowed in top level scope. + * Structs cannot be redefined. + * + * @return The statement following the struct + */ + private Node parseStruct() { + if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); + String typeName = requireId(); + Type t = TYPES.get(typeName); + if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) ) + throw errorSyntax("struct '" + typeName + "' cannot be redefined"); + + // A Block scope parse, and inspect the scope afterward for fields. + _scope.push(); + require("{"); + while (!peek('}') && !_lexer.isEOF()) + parseStatement(); + + // Grab the declarations and build fields and a Struct + int lexlen = _scope._lexSize.last(); + int varlen = _scope._vars._len; + StructNode s = new StructNode(); + Field[] fs = new Field[varlen-lexlen]; + for( int i=lexlen; i<varlen; i++ ) { + s.addDef(_scope.in(i)); + ScopeMinNode.Var v = _scope._vars.at(i); + fs[i-lexlen] = Field.make(v._name,v._type,ALIAS++,v._final); + } + TypeStruct ts = s._ts = TypeStruct.make(typeName, fs); + TYPES.put(typeName, TypeMemPtr.make(ts)); + INITS.put(typeName,s.peephole().keep()); + // Done with struct/block scope + require("}"); + require(";"); + _scope.pop(); + return null; + } + + // Parse and return a type or null. Valid types always are followed by an // 'id' which the caller must parse. This lets us distinguish forward ref // types (which ARE valid here) from local vars in an (optional) forward @@ -529,14 +588,14 @@ public class Parser { private TypeMemPtr typeAry( Type t ) { if( t instanceof TypeMemPtr tmp && !tmp._nil ) throw error("Arrays of reference types must always be nullable"); - String tname = t.str()+"[]"; + String tname = "["+t.str()+"]"; Type ta = TYPES.get(tname); if( ta != null ) return (TypeMemPtr)ta; // Need make an array type. TypeStruct ts = TypeStruct.makeAry(TypeInteger.BOT,ALIAS++,t,ALIAS++); + assert ts.str().equals(tname); TypeMemPtr tary = TypeMemPtr.make(ts); TYPES.put(tname,tary); - START.addMemProj(ts, _scope); // Insert memory alias edges return tary; } @@ -694,67 +753,107 @@ public class Parser { if( match("(") ) return require(parseExpression(), ")"); if( matchx("true" ) ) return con(1); if( matchx("false") ) return ZERO; - if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); - if( matchx("new") ) { - Type t = type(); - if( t==null ) throw error("Expected a type"); - if( match("[") ) { - Node len = parseExpression().keep(); - if( !(len._type instanceof TypeInteger) ) - throw error("Cannot allocate an array with length "+len._type); - require("]"); - TypeMemPtr tmp = typeAry(t); - return newArray(tmp._obj,len); - } - if( t instanceof TypeMemPtr tmp ) { - TypeStruct obj = tmp._obj; - if( obj._fields==null ) - throw error("Unknown struct type '" + obj._name + "'"); - if( obj.isAry() ) - throw Utils.TODO(); // Missing array length - return newStruct(obj,con(obj.offset(obj._fields.length))); - } - throw error("Cannot allocate a "+t.str()); - } + if( matchx("null" ) ) return con(TypeMemPtr.NULLPTR); + if( matchx("new" ) ) return alloc(); + // Expect an identifier now String name = _lexer.matchId(); if( name == null) throw errorSyntax("an identifier or expression"); - Node n = _scope.lookup(name); - if( n!=null ) return n; + ScopeMinNode.Var n = _scope.lookup(name); + if( n!=null ) return _scope.in(n); throw error("Undefined name '" + name + "'"); } /** - * Return a NewNode with pre-zeroed memory + Parse an allocation + */ + private Node alloc() { + Type t = type(); + if( t==null ) throw error("Expected a type"); + // Parse ary[ length_expr ] + if( match("[") ) { + Node len = parseExpression().keep(); + if( !(len._type instanceof TypeInteger) ) + throw error("Cannot allocate an array with length "+len._type); + require("]"); + TypeMemPtr tmp = typeAry(t); + return newArray(tmp._obj,len); + } + + if( !(t instanceof TypeMemPtr tmp) ) + throw error("Cannot allocate a "+t.str()); + + // Parse new struct { default_initialization } + StructNode s = INITS.get(tmp._obj._name); + if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'"); + + Field[] fs = s._ts._fields; + // if the object is fully initialized, we can skip a block here. + // Check for constructor block: + boolean hasConstructor = match("{"); + Ary<Node> init=s._inputs; int idx=0; + if( hasConstructor ) { + idx = _scope.nIns(); + // Push a scope, and pre-assign all struct fields. + _scope.push(); + for( int i=0; i<fs.length; i++ ) + _scope.define(fs[i]._fname, fs[i]._type, fs[i]._final, s.in(i)); + // Parse the constructor body + parseBlock(); + require("}"); + init = _scope._inputs; + } + // Check that all fields are initialized + for( int i=idx; i<init.size(); i++ ) + if( init.get(i)._type == Type.TOP ) + throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor"); + Node ptr = newStruct(tmp._obj, con(tmp._obj.offset(fs.length)), idx, init ); + if( hasConstructor ) + _scope.pop(); + return ptr; + } + + + /** + * Return a NewNode initialized memory. + * @param obj is the declared type, with GLB fields + * @param init is a collection of initialized fields */ - private Node newStruct(TypeStruct obj, Node size) { + private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) { Field[] fs = obj._fields; - Node[] ns = new Node[2+fs.length]; - ns[0] = ctrl(); + if( fs==null ) + throw error("Unknown struct type '" + obj._name + "'"); + int len = fs.length; + Node[] ns = new Node[2+len+len]; + ns[0] = ctrl(); // Control in slot 0 + // Total allocated length in bytes ns[1] = size; - for( int i = 0; i < fs.length; i++ ) - ns[i+2] = memAlias(fs[i]._alias); + // Memory aliases for every field + for( int i = 0; i < len; i++ ) + ns[2 +i] = memAlias(fs[i]._alias); + // Initial values for every field + for( int i = 0; i < len; i++ ) + ns[2+len+i] = init.get(i+idx); Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole(); - for( int i = 0; i < fs.length; i++ ) + for( int i = 0; i < len; i++ ) memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); return new ProjNode(nnn,1,obj._name).peephole(); } + private static final Ary<Node> ALTMP = new Ary<>(Node.class); private Node newArray(TypeStruct ary, Node len) { int base = ary.aryBase (); int scale= ary.aryScale(); Node size = new AddNode(con(base),new ShlNode(len,con(scale)).peephole()).peephole(); - Node ptr = newStruct(ary,size); - int alias = ary._fields[0]._alias; // Length alias - memAlias(alias,new StoreNode("#",alias,TypeInteger.BOT,memAlias(alias),ptr,con( ary.offset(0) ), len.unkeep(), true ).peephole()); - return ptr; + ALTMP.clear(); ALTMP.add(len); ALTMP.add(con(ary._fields[1]._type.makeInit())); + return newStruct(ary,size,0,ALTMP); } // We set up memory aliases by inserting special vars in the scope these // variables are prefixed by $ so they cannot be referenced in Simple code. // Using vars has the benefit that all the existing machinery of scoping // and phis work as expected - private Node memAlias(int alias ) { return _scope.lookup(memName(alias) ); } - private Node memAlias(int alias, Node st) { return _scope.update(memName(alias), st); } + private Node memAlias(int alias ) { return _scope.mem(alias ); } + private void memAlias(int alias, Node st) { _scope.mem(alias, st); } public static String memName(int alias) { return ("$"+alias).intern(); } /** @@ -777,52 +876,49 @@ public class Parser { // Sanity check expr for being a reference if( !(expr._type instanceof TypeMemPtr ptr) ) throw error("Expected reference but got " + expr._type.str()); - if( ptr == TypeMemPtr.TOP ) throw error("Accessing field '" + name + "' from null"); - if( ptr._obj == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + + // Happens when parsing known dead code, which often has other typing + // issues. Since the code is dead, possibly due to inlining, lets not + // spoil the user experience with error messages. + if( ctrl()._type==Type.XCONTROL ) + // Exit out via parsing the trailing expression + return matchOpx('=','=') ? parseExpression() : parsePostfix(con(Type.TOP)); + // Sanity check field name for existing TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name); if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); TypeStruct base = tmp._obj; int fidx = base.find(name); if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - expr.keep(); // Get field type and layout offset from base type and field index fidx Field f = base._fields[fidx]; // Field from field index - Node off; - if( name.equals("[]") ) { // If field is an array body - // Array index math - Node idx = require(parseExpression(),"]"); - Node shl = new ShlNode(idx,con(base.aryScale())).peephole(); - off = new AddNode(con(base.aryBase()),shl).peephole(); - } else { // Else normal struct field - // Hardwired field offset - off = con(base.offset(fidx)); - } - if( match("=") ) { - // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( peek('=') ) _lexer._position--; - else { - off.keep(); - Node val = parseExpression(); - // Auto-truncate when storing to narrow fields - val = zsMask(val,f._type).keep(); - Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr.unkeep(), off.unkeep(), val, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); - return val.unkeep(); // "obj.a = expr" returns the expression while updating memory - } + Node off = name.equals("[]") // If field is an array body + // Array index math + ? new AddNode(con(base.aryBase()),new ShlNode(require(parseExpression(),"]"),con(base.aryScale())).peephole()).peephole() + // Struct field offsets are hardwired + : con(base.offset(fidx)); + + // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment + if( matchOpx('=','=') ) { + Node val = parseExpression(); + // Auto-truncate when storing to narrow fields + val = zsMask(val,f._type); + Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr, off, val, false); + // Arrays include control, as a proxy for a safety range check. + // Structs don't need this; they only need a NPE check which is + // done via the type system. + if( base.isAry() ) st.setDef(0,ctrl()); + memAlias(f._alias, st.peephole()); + return val; // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr.unkeep(), off); + Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. - if( base.isAry() ) load.setDef(0,ctrl()); + //if( base.isAry() ) load.setDef(0,ctrl()); return parsePostfix(load.peephole()); } @@ -855,13 +951,9 @@ public class Parser { * floatLiteral: [digits].[digits]?[e [digits]]? * </pre> */ - private ConstantNode parseLiteral() { - return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); - } - - public static Node con( long con ) { - return new ConstantNode(TypeInteger.constant(con)).peephole(); - } + private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } + public static Node con( long con ) { return con(TypeInteger.constant(con)); } + public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); } ////////////////////////////////// // Utilities for lexical analysis @@ -870,6 +962,7 @@ public class Parser { private boolean match (String syntax) { return _lexer.match (syntax); } // Match must be "exact", not be followed by more id letters private boolean matchx(String syntax) { return _lexer.matchx(syntax); } + private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); } // Return true and do NOT skip if 'ch' is next private boolean peek(char ch) { return _lexer.peek(ch); } private boolean peekIsId() { return _lexer.peekIsId(); } @@ -989,6 +1082,13 @@ public class Parser { _position -= syntax.length(); return false; } + boolean matchOpx(char c0, char c1) { + skipWhiteSpace(); + if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 ) + return false; + _position++; + return true; + } private boolean peek(char ch) { skipWhiteSpace(); |
