/* * Copyright (C) 2011 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 org.novelang.build.antlr; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.antlr.stringtemplate.StringTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.novelang.build.GrammarBasedJavaGenerator; /** * Generates a Java enum from tokens (in a {@code tokens} clause) declared in and ANTLR grammar. * * @author Laurent Caillette */ public class TokenGenerator extends GrammarBasedJavaGenerator { private static final Logger LOGGER = LoggerFactory.getLogger( TokenGenerator.class ) ; public TokenGenerator( final File grammarFile, final String packageName, final String className, final File targetDirectory ) throws IOException { super( grammarFile, packageName, className, targetDirectory ) ; } @Override protected String generateCode() { final Iterable< Item > enumerationItems = findAntlrTokens( getGrammar() ) ; return generateJavaEnumeration( enumerationItems ) ; } private static final Map< TokenProperty, String > TAG_SCOPE = ImmutableMap.of( TokenProperty.TAG_BEHAVIOR, TagBehavior.SCOPE.name() ) ; private static final Map< TokenProperty, String > TAG_TRAVERSABLE = ImmutableMap.of( TokenProperty.TAG_BEHAVIOR, TagBehavior.TRAVERSABLE.name() ) ; private static final Collection< Item > SYNTHETIC_ITEMS = ImmutableList.of( new Item( "_STYLE" ), new Item( "_LEVEL", TAG_SCOPE ), new Item( "_LIST_WITH_TRIPLE_HYPHEN", TAG_TRAVERSABLE ), new Item( "_LIST_WITH_DOUBLE_HYPHEN_AND_NUMBER_SIGN", TAG_TRAVERSABLE ), /* Crafted out of PARAGRAPH_AS_LIST_ITEM_WITH_TRIPLE_HYPHEN_ by GenericRenderer, no tag scope then. */ new Item( "_PARAGRAPH_AS_LIST_ITEM" ), new Item( "_EMBEDDED_LIST_WITH_HYPHEN" ), new Item( "_EMBEDDED_LIST_WITH_NUMBER_SIGN" ), new Item( "_EMBEDDED_LIST_ITEM" ), new Item( "_META_TIMESTAMP" ), new Item( "_META" ), new Item( "_LOCATION" ), new Item( "_WORD_COUNT" ), new Item( "_TAGS" ), new Item( "_IMAGE_WIDTH" ), new Item( "_IMAGE_HEIGHT" ), new Item( "_URL" ), new Item( "_PLACEHOLDER_" ), new Item( "_ZERO_WIDTH_SPACE" ), new Item( "_PRESERVED_WHITESPACE" ), new Item( "_IMPLICIT_IDENTIFIER" ), new Item( "_EXPLICIT_IDENTIFIER" ), new Item( "_IMPLICIT_TAG" ), new Item( "_PROMOTED_TAG" ), new Item( "_EXPLICIT_TAG" ), new Item( "_COLLIDING_EXPLICIT_IDENTIFIER" ), new Item( "_PAGE" ), new Item( "_PAGE_IDENTIFIER" ), new Item( "_PAGE_PATH" ) ) ; private static final Pattern ALL_TOKENS_PATTERN = Pattern.compile( "tokens(?:\\s*)\\{[^\\}]*\\}" ) ; private static final Pattern ONE_TOKEN_PATTERN ; static { final StringBuilder regexBuilder = new StringBuilder(); //noinspection HardcodedFileSeparator regexBuilder.append( "(?: *([A-Z_]+) *;)(?: *//" ) ; for( final TokenProperty tokenProperty : TokenProperty.values() ) { //noinspection HardcodedFileSeparator regexBuilder .append( "(?:\\s+" ) .append( tokenProperty.getPublicName() ) .append( "=(\\w+))?" ) ; } regexBuilder.append( ")? *" ) ; ONE_TOKEN_PATTERN = Pattern.compile( regexBuilder.toString() ) ; LOGGER.debug( "Crafted regex {}", ONE_TOKEN_PATTERN.pattern() ) ; } protected static Iterable< Item > findAntlrTokens( final String grammar ) { final Matcher allTokensMatcher = ALL_TOKENS_PATTERN.matcher( grammar ) ; if( ! allTokensMatcher.find() ) { LOGGER.warn( "No token found" ) ; return ImmutableList.of() ; } final String allTokens = allTokensMatcher.group( 0 ) ; final Matcher eachTokenMatcher = ONE_TOKEN_PATTERN.matcher( allTokens ) ; final List< Item > tokenList = Lists.newLinkedList() ; while( eachTokenMatcher.find() ) { final Map< TokenProperty, String > properties = Maps.newHashMap() ; for( final TokenProperty tokenProperty : TokenProperty.values() ) { final String propertyValue = eachTokenMatcher.group( tokenProperty.ordinal() + 2 ) ; if( null != propertyValue ) { properties.put( tokenProperty, propertyValue ) ; } } final Item item = new Item( eachTokenMatcher.group( 1 ), properties ) ; tokenList.add( item ) ; } if( tokenList.size() < 1 ) { LOGGER.warn( "No token found" ) ; } tokenList.addAll( SYNTHETIC_ITEMS ) ; return ImmutableList.copyOf( tokenList ) ; } protected String generateJavaEnumeration( final Iterable< Item > enumerationItems ) { final StringTemplate javaEnum = createStringTemplate( "enum" ) ; javaEnum.setAttribute( "items", enumerationItems ) ; return javaEnum.toString() ; } public static final class Item { public final String name ; public final boolean punctuationSign ; public final TagBehavior tagBehavior ; public Item( final String name ) { this( name, ImmutableMap.< TokenProperty, String >of() ) ; } public Item( final String name, final Map< TokenProperty, String > properties ) { this.name = name ; this.punctuationSign = "true".equalsIgnoreCase( properties.get( TokenProperty.PUNCTUATION_SIGN ) ) ; final String tagBehaviorAsString = properties.get( TokenProperty.TAG_BEHAVIOR ) ; if( null == tagBehaviorAsString ) { tagBehavior = TagBehavior.NON_TRAVERSABLE ; } else { tagBehavior = TagBehavior.valueOf( tagBehaviorAsString ) ; } } @Override public boolean equals( final Object o ) { if( this == o ) { return true ; } if( o == null || getClass() != o.getClass() ) { return false; } final Item item = ( Item ) o ; return !( name != null ? !name.equals( item.name ) : item.name != null ); } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0 ; result = 31 * result + ( punctuationSign ? 1 : 0 ) ; return result ; } @Override public String toString() { return "Item[ name: " + name + ( punctuationSign ? " ; punctuationsign" : "" ) + " ]" ; } } private static enum TokenProperty { PUNCTUATION_SIGN, TAG_BEHAVIOR ; private final String publicName ; private TokenProperty() { publicName = name().toLowerCase().replace( "_", "" ) ; } public String getPublicName() { return publicName ; } } /** * This class mirrors the one in {@code novelang.common} which cannot be referenced because * of source directories layout. */ public enum TagBehavior { NON_TRAVERSABLE, TRAVERSABLE, SCOPE, TERMINAL } }