Changeset 12694


Ignore:
Timestamp:
Apr 30, 2019 4:35:13 PM (7 months ago)
Author:
tgutzmann
Message:

#5764 Use ModelicaScanner in QualifiedName to increase robustness of the implementation.

Location:
trunk/Compiler/ModelicaFrontEnd
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/Compiler/ModelicaFrontEnd/src/java/org/jmodelica/util/QualifiedName.java

    r12667 r12694  
    1616package org.jmodelica.util;
    1717
     18import java.io.IOException;
     19import java.io.StringReader;
    1820import java.util.ArrayList;
    19 import java.util.regex.Matcher;
    20 import java.util.regex.Pattern;
     21import java.util.Iterator;
    2122
     23import org.jmodelica.modelica.parser.ModelicaParser.Terminals;
     24import org.jmodelica.modelica.parser.ModelicaScanner;
    2225import org.jmodelica.util.exceptions.NameFormatException;
     26
     27import beaver.Scanner.Exception;
     28import beaver.Symbol;
    2329
    2430/**
     
    2632 */
    2733public class QualifiedName {
    28     private boolean isGlobal;
    29     private int i = 0;
    30     ArrayList<String> names;
    31     private boolean isUnQualifiedImport;
     34    private final boolean isGlobal;
     35    private final ArrayList<String> names = new ArrayList<>();
     36    private final boolean isUnQualifiedImport;
     37    private final Iterator<String> iterator;
    3238
    3339    public QualifiedName(String name) {
    34         if (name.length() == 0) {
    35             throw new NameFormatException("A name must have atleast one caracter");
    36         }
    3740        isUnQualifiedImport = name.endsWith(".*");
    3841        isGlobal = name.startsWith(".");
    39         names = splitQualifiedClassName(name);
     42        splitQualifiedClassName(name);
     43        iterator = names.iterator();
    4044    }
    4145
    42     // Interpret name as global or not regardless or dot form or not.
     46    // Interpret name as global or not regardless of dot form or not.
    4347    public QualifiedName(String name, boolean isGlobal) {
    44         if (name.length() == 0) {
    45             throw new NameFormatException("A name must have atleast one caracter");
    46         }
    47         names = splitQualifiedClassName(name);
    48         this.isGlobal = isGlobal;
    49     }
    50 
    51     public boolean hasNext() {
    52         return i < names.size();
     48        isUnQualifiedImport = name.endsWith(".*");
     49        splitQualifiedClassName(name);
     50        this.isGlobal = isGlobal; // Note: must be setter after splitting
     51        iterator = names.iterator();
    5352    }
    5453
     
    5655        return names.size();
    5756    }
     57   
     58    public boolean hasNext() {
     59        return iterator.hasNext();
     60    }
     61   
     62    public String next() {
     63        return iterator.next();
     64    }
    5865
    5966    /**
    60      * Only parse if the name is not simple, don't store the actual parts.
    61      * Skips some work.
    62      * @return if the name has only a single part.
     67     * Checks if the name is a valid and simple (unqualified) identifier.
     68     * @param name The name.
     69     * @param allowGlobal If <code>true</code>, then takes 'global' notation with a leading dot into account by
     70     * ignoring such a character.
     71     * @return Whether or not <code>name</code> is a valid identifier.
    6372     */
    64     public static int numberOfParts(String name) {
    65         boolean isGlobal = name.startsWith(".");
    66         if (isGlobal) {
     73    public static boolean isValidSimpleIdentifier(String name, boolean allowGlobal) {
     74        if (allowGlobal && name.startsWith(".")) {
    6775            name = name.substring(1, name.length());
    6876        }
    69         Matcher m = p.matcher(name);
    70         ArrayList<Integer> nameSeparations = new ArrayList<Integer>();
    71         findNameSeparations(m, nameSeparations);
    72         return nameSeparations.size() + 1;
    73     }
    74 
    75     public String next() {
    76         return names.get(i++);
    77     }
    78 
    79     public ArrayList<String> getNames() {
    80         return names;
     77        ModelicaScanner ms = new ModelicaScanner(new StringReader(name));
     78        try {
     79           if (ms.nextToken().getId() != Terminals.ID)
     80               return false;
     81           if (ms.nextToken().getId() != Terminals.EOF)
     82               return false;
     83           return true;
     84        } catch (IOException e) {
     85            // This shouldn't happen when using a StringReader.
     86            throw new RuntimeException("Unhandled internal error", e);
     87        } catch (Exception e) {
     88            // Scanner cannot handle this, so this is not a valid identifier.
     89            return false;
     90        }
    8191    }
    8292
    8393    @Override
    8494    public String toString() {
    85         return names.toString();
     95        return (isGlobal ? "(global) " : "") + (isUnQualifiedImport ? ".* " : "") + names.toString();
    8696    }
    8797
     
    90100    }
    91101
    92     static final Pattern p = Pattern.compile("(?<![\\\\])['.]");
    93 
    94     private static void checkNameLength(int start,int end) {
    95         if (end - start < 2)
    96             throw new NameFormatException("Names must have a length greater than zero");
    97     }
    98 
    99     private static void findNameSeparations(Matcher m, ArrayList<Integer> list) {
    100         boolean inquoted = false;
    101         int prev = -1;
    102         while (m.find()) {
    103             String token = m.group();
    104             if (token.equals("'")) {
    105                 if (inquoted) {
    106                     checkNameLength(prev, m.start());
    107                 } else {
    108                     if (m.start() - prev > 2) {
    109                         throw new NameFormatException("Quotes not allowed inside unqouted name");
    110                     }
    111                 }
    112                 prev = inquoted ? prev : m.start();
    113                 inquoted = !inquoted;
    114             } else if (!inquoted) {
    115                 checkNameLength(prev, m.start());
    116                 prev = m.start();
    117                 list.add(m.start() + 1);
    118             }
     102    /**
     103     * Splits a composite class name into all its partial accesses
     104     */
     105    private final void splitQualifiedClassName(String name) {
     106        if (name.length() == 0) {
     107            throw new NameFormatException("A name must have at least one caracter");
    119108        }
    120         if (inquoted) {
    121             throw new NameFormatException("Qualified Name couldn't be interpreted due to unmatched quotes");
    122         }
    123     }
    124 
    125     /**
    126      * Splits a composite class name into all the partial access
    127      *
    128      * @param name
    129      * @return array with the names of all accessed classes.
    130      */
    131     private final ArrayList<String> splitQualifiedClassName(String name) {
    132109        if (isGlobal || isUnQualifiedImport) {
    133110            int start = isGlobal ? 1 : 0;
     
    135112            name = name.substring(start, end);
    136113        }
    137         Matcher m = p.matcher(name);
    138         ArrayList<Integer> nameSeparations = new ArrayList<Integer>();
    139         findNameSeparations(m, nameSeparations);
    140         nameSeparations.add(name.length() + 1);
    141         ArrayList<String> parts = new ArrayList<String>();
    142         int partEnd = 0;
    143         for (int namePart = 0; namePart < nameSeparations.size() ; namePart++) {
    144             parts.add(name.substring(partEnd, nameSeparations.get(namePart) - 1));
    145             partEnd = nameSeparations.get(namePart);
     114        ModelicaScanner ms = new ModelicaScanner(new StringReader(name));
     115        try {
     116            Symbol sym = ms.nextToken();
     117            if (sym.getId() != Terminals.ID)
     118                throw new NameFormatException("The qualified name is not valid");
     119            names.add((String)sym.value);
     120            while ((sym = ms.nextToken()).getId() == Terminals.DOT) {
     121                sym = ms.nextToken();
     122                if (sym.getId() != Terminals.ID)
     123                    throw new NameFormatException("The qualified name is not valid");
     124                names.add((String)sym.value);
     125            }
     126            if (sym.getId() != Terminals.EOF)
     127                throw new NameFormatException("Invalid name: " + name);
     128        } catch (IOException e) {
     129            // This shouldn't happen when using a StringReader.
     130            throw new RuntimeException("Unhandled internal error", e);
     131        } catch (Exception e) {
     132            // Identifier not valid.
     133            throw new NameFormatException("Invalid name: " + name);
    146134        }
    147         return parts;
    148135    }
    149136}
  • trunk/Compiler/ModelicaFrontEnd/test/junit/org/jmodelica/test/common/QualifiedNameTest.java

    r12642 r12694  
    22
    33import static org.junit.Assert.assertEquals;
     4import static org.junit.Assert.assertTrue;
     5import static org.junit.Assert.assertFalse;
    46
    57import org.jmodelica.util.QualifiedName;
     
    1113    @Test
    1214    public void globalWithQuotedContainingExcapedDot() {
    13         assertEquals("['quotedWith.Dot\\'.', secondPart]",
     15        assertEquals("(global) ['quotedWith.Dot\\'.', secondPart]",
    1416                new QualifiedName(".'quotedWith.Dot\\'.'.secondPart").toString());
    1517    }
     
    1719    @Test
    1820    public void globalDotted()  {
    19         assertEquals("[first, second, third]",
     21        assertEquals("(global) [first, second, third]",
    2022                new QualifiedName(".first.second.third").toString());
    2123    }
     
    2325    @Test
    2426    public void global() {
    25         assertEquals("[global]",
     27        assertEquals("(global) [global]",
    2628                new QualifiedName(".global").toString());
    2729    }
     
    5254    @Test
    5355    public void countNumberOfParts() {
    54         assertEquals(4, new QualifiedName(("A.'B'.C.D")).numberOfParts());
     56        assertEquals(4, new QualifiedName("A.'B'.C.D").numberOfParts());
    5557    }
    5658
    5759    @Test
    5860    public void nonSimpleNameParts() {
    59         assertEquals(4, QualifiedName.numberOfParts(".A.'B'.C.D"));
     61        assertEquals(4, new QualifiedName(".A.'B'.C.D").numberOfParts());
    6062    }
    6163   
    6264    @Test
    6365    public void globalSimpleNameParts() {
    64         assertEquals(1, QualifiedName.numberOfParts(".'A'"));
     66        assertTrue(QualifiedName.isValidSimpleIdentifier(".'A'", true));
    6567    }
    66    
     68
     69    @Test
     70    public void globalSimpleNamePartsNegative() {
     71        assertFalse(QualifiedName.isValidSimpleIdentifier(".'A'", false));
     72    }
     73
    6774    @Test
    6875    public void nameFromUnqualifiedImport() {
    69         assertEquals("[A, B, C]", new QualifiedName("A.B.C.*").toString());
     76        assertEquals(".* [A, B, C]", new QualifiedName("A.B.C.*").toString());
    7077    }
    7178
Note: See TracChangeset for help on using the changeset viewer.