/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.binio.util; import com.bc.ceres.binio.*; import static com.bc.ceres.binio.TypeBuilder.*; import com.bc.ceres.binio.internal.CompoundTypeImpl; import com.bc.ceres.binio.internal.VarElementCountSequenceTypeImpl; import java.io.IOException; import java.io.Reader; import java.io.StreamTokenizer; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; /** * A simple parser which can be used to read type definition <i>units</i> using the following syntax: * <blockquote> * <i>unit</i> := <i>compounds</i><br/> * <p/> * <i>compounds</i> := { <i>compound</i> }<br/> * <p/> * <i>compound</i> := <i>compound-name</i> <u>{</u> <i>members</i> <u>}</u> [<u>;</u>] * <p/> * <i>members</i> := { <i>member</i> }<br/> * <p/> * <i>member</i> := <i>type</i> <i>member-name</i> <u>;</u> <br/> * <p/> * <i>type</i> := <i>scalar-type</i> | <i>array-type</i> <br/> * <p/> * <i>array-type</i> := <i>element-type</i> <u>[</u><i>element-count</i><u>]</u> { <u>[</u><i>element-count</i><u>]</u> } <br/> * <p/> * <i>element-type</i> := <i>scalar-type</i> <br/> * <p/> * <i>element-count</i> := <i>integer</i> | <i>member-reference</i> <br/> * <p/> * <i>scalar-type</i> := <i>simple-type</i> | <i>compound-name</i> <br/> * <p/> * <i>simple-type</i> := <u>byte</u> | <u>ubyte</u> | <u>short</u> | <u>ushort</u> | <u>int</u> | <u>uint</u> | <u>long</u> | <u>float</u> | <u>double</u> <br/> * <p/> * <i>member-reference</i> := <u>$</u><i>member-name</i> (member must be an integer type) <br/> * <p/> * <i>compound-name</i> := <i>name</i> <br/> * <p/> * <i>member-name</i> := <i>name</i> <br/> * <p/> * <i>name</i> := <i>java-identifier</i> | any character sequence within two enclosing <u>"</u> (double quote)<br/> * </blockquote> * <p/> * <p/> * For example: * <pre> * <p/> * Dataset { * int lineCount; * Scanline[$lineCount] scanlines; * }; * <p/> * Scanline { * int flags; * double[512] data; * }; * </pre> */ public class TypeParser { private final HashMap<String, SimpleType> simpleTypeMap; private final HashMap<String, CompoundType> compoundTypeMap; private final StreamTokenizer st; private static final String UNRESOLVED = "Unresolved@"; public final static SimpleType[] SIMPLE_TYPES = { SimpleType.BYTE, SimpleType.UBYTE, SimpleType.SHORT, SimpleType.USHORT, SimpleType.INT, SimpleType.UINT, SimpleType.LONG, SimpleType.ULONG, SimpleType.FLOAT, SimpleType.DOUBLE }; private TypeParser(StreamTokenizer st) { this.st = st; this.compoundTypeMap = new HashMap<String, CompoundType>(11); this.simpleTypeMap = new HashMap<String, SimpleType>(11); for (SimpleType type : SIMPLE_TYPES) { registerSimpleType(type); } } private void registerSimpleType(SimpleType type) { simpleTypeMap.put(type.getName(), type); } public static CompoundType[] parseUnit(Reader reader) throws IOException, ParseException { StreamTokenizer st = new StreamTokenizer(reader); st.resetSyntax(); st.eolIsSignificant(false); st.slashSlashComments(true); st.slashStarComments(true); st.parseNumbers(); st.wordChars('a', 'z'); st.wordChars('A', 'Z'); st.wordChars('_', '_'); st.wordChars('0', '9'); st.whitespaceChars(0, ' '); st.quoteChar('"'); TypeParser typeParser = new TypeParser(st); CompoundType[] compoundTypes = typeParser.parseCompoundTypes(); typeParser.resolve(compoundTypes); return compoundTypes; } private void resolve(CompoundType[] compoundTypes) throws ParseException { for (CompoundType compoundType : compoundTypes) { resolve(compoundType); } } private Type resolveType(Type type) throws ParseException { Type resolvedType; if (type instanceof CompoundType) { resolvedType = resolve((CompoundType) type); } else if (type instanceof SequenceType) { resolvedType = resolve((SequenceType) type); } else { resolvedType = type; } return resolvedType; } private CompoundType resolve(CompoundType compoundType) throws ParseException { if (compoundType.getName().startsWith(UNRESOLVED)) { String name = compoundType.getName().substring(UNRESOLVED.length()); CompoundType resolvedType = compoundTypeMap.get(name); if (resolvedType == null) { throw new ParseException("Unresolved compound type: " + name, -1); } return resolvedType; } for (int i = 0; i < compoundType.getMemberCount(); i++) { CompoundMember member = compoundType.getMember(i); Type memberType = member.getType(); Type resolvedMemberType = resolveType(memberType); ((CompoundTypeImpl) compoundType).setMember(i, MEMBER(member.getName(), resolvedMemberType)); } return compoundType; } private Type resolve(SequenceType sequenceType) throws ParseException { if (sequenceType instanceof VarElementCountSequenceTypeImpl) { VarElementCountSequenceTypeImpl varSequenceType = (VarElementCountSequenceTypeImpl) sequenceType; return VAR_SEQUENCE(resolveType(sequenceType.getElementType()), varSequenceType.getMemberName()); } else { return SEQUENCE(resolveType(sequenceType.getElementType()), sequenceType.getElementCount()); } } public CompoundType[] parseCompoundTypes() throws IOException, ParseException { ArrayList<CompoundType> list = new ArrayList<CompoundType>(32); while (true) { CompoundType compoundType = parseCompoundType(); if (compoundType == null) { break; } list.add(compoundType); compoundTypeMap.put(compoundType.getName(), compoundType); } return list.toArray(new CompoundType[list.size()]); } private CompoundType parseCompoundType() throws IOException, ParseException { final String name = parseName(); if (name == null) { return null; } int token; token = st.nextToken(); if (token != '{') { error(st, "'{' expected."); } CompoundMember[] members = parseMembers(name); token = st.nextToken(); if (token != '}') { st.pushBack(); error(st, "'}' expected."); } token = st.nextToken(); if (token != ';') { st.pushBack(); } return COMPOUND(name, members); } private String parseName() throws IOException { int token = st.nextToken(); final String name; if (token == StreamTokenizer.TT_WORD) { name = st.sval; } else if (token == '"') { name = st.sval; } else { st.pushBack(); name = null; } return name; } private CompoundMember[] parseMembers(String parentCompoundName) throws IOException, ParseException { ArrayList<CompoundMember> list = new ArrayList<CompoundMember>(32); while (true) { final CompoundMember member = parseMember(parentCompoundName); if (member == null) { break; } list.add(member); } return list.toArray(new CompoundMember[list.size()]); } private CompoundMember parseMember(String parentCompoundName) throws IOException, ParseException { Type type = parseType(parentCompoundName); if (type == null) { return null; } String name = parseName(); if (name == null) { error(st, "Member name expected."); } int token = st.nextToken(); if (token != ';') { st.pushBack(); error(st, "';' expected."); } return MEMBER(name, type); } private Type parseType(String parentCompoundName) throws IOException, ParseException { String name = parseName(); if (name == null) { return null; } Type type = simpleTypeMap.get(name); if (type == null) { type = compoundTypeMap.get(name); if (type == null) { CompoundType unresolvedType = COMPOUND(UNRESOLVED + name); compoundTypeMap.put(name, unresolvedType); type = unresolvedType; } } while (true) { int token = st.nextToken(); if (token == '[') { token = st.nextToken(); if (token == StreamTokenizer.TT_NUMBER) { int elementCount = (int) st.nval; if (elementCount != st.nval) { error(st, "Integer element count expected."); } token = st.nextToken(); if (token != ']') { error(st, "']' expected."); } type = SEQUENCE(type, elementCount); } else if (token == StreamTokenizer.TT_WORD) { String lengthRefName = st.sval; if (lengthRefName.indexOf('.') == -1) { lengthRefName = parentCompoundName + "." + lengthRefName; } type = VAR_SEQUENCE(type, lengthRefName); token = st.nextToken(); if (token != ']') { error(st, "']' expected."); } } else if (token == ']') { type = SEQUENCE(type, -1); } else { st.pushBack(); error(st, "Array length specifier expected after '['."); } } else { st.pushBack(); break; } } return type; } private static void error(StreamTokenizer st, String s) throws ParseException { throw new ParseException(s, st.lineno()); } }