All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.singingbush.sdl.Parser Maven / Gradle / Ivy

/*
 * Simple Declarative Language (SDL) for Java
 * Copyright 2005 Ikayzo, inc.
 *
 * This program is free software. You can distribute or modify it under the
 * terms of the GNU Lesser General Public License version 2.1 as published by
 * the Free Software Foundation.
 *
 * This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
 * INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, contact the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package com.singingbush.sdl;

import org.jetbrains.annotations.Nullable;

import java.io.*;
import java.math.BigDecimal;
import java.time.*;
import java.time.format.DateTimeFormatter;

import java.util.*;

/**
 * The SDL parser.
 *
 * @author Daniel Leuck
 */
class Parser {

    private static final String DATE_REGEX = "(\\d+\\/\\d+\\/\\d+)";
    private static final String TIME_REGEX = "(\\d+:\\d+(:\\d+)?(.\\d+)?)(-\\w+)?";
    public static final String DATETIME_REGEX = DATE_REGEX + " " + TIME_REGEX;
    public static final String TIMESPAN_REGEX = "-?(\\d+d:)?(\\d+:\\d+:\\d+)(.\\d+)?";

    private final BufferedReader reader;
	private String line;
	private List toks;
	private StringBuilder sb;
	private boolean startEscapedQuoteLine;
	private int lineNumber=-1, lineStart = 0, pos=0, lineLength=0, tokenStart=0;
    private boolean semicolonTerminated = false;

	/**
	 * Create an SDL parser
	 */
	Parser(Reader reader) {
		this.reader = (reader instanceof BufferedReader)
			? ((BufferedReader)reader)
			: new BufferedReader(reader);
	}

	/**
	 * Convenience for users wanting to parse SDL syntax that is held in a Java String
	 * @param sdlText a string of SDLang
	 * @since 1.4.0
	 */
	Parser(final String sdlText) {
		this(new StringReader(sdlText));
		//this(new InputStreamReader(new ByteArrayInputStream(sdlText.getBytes())));
	}

	/**
	 * Convenience for users wanting to parse SDL from a java.io.File
	 * @param file A UTF-8 encoded .sdl file
	 * @since 1.4.0
	 */
	Parser(final File file) throws FileNotFoundException, UnsupportedEncodingException {
		this(new InputStreamReader(new FileInputStream(file), "UTF-8"));
	}

	/**
	 * @return A list of tags described by the input
	 * @throws IOException If a problem is encountered with the reader
	 * @throws SDLParseException If the document is malformed
	 */
	List parse() throws IOException,SDLParseException {
		final List tags = new ArrayList<>();
		List toks;

		while((toks=getLineTokens()) != null) {
			int size = toks.size();

			if(toks.get(size-1).getType()==SdlType.START_BLOCK) {
				Tag t = constructTag(toks.subList(0, size-1));
				addChildren(t);
				tags.add(t);
			} else if(toks.get(0).getType()==SdlType.END_BLOCK){
				parseException("No opening block ({) for close block (}).", toks.get(0).getLine(), toks.get(0).getPosition());
			} else {
				List tokens = new ArrayList<>(size);
				for (final Token t : toks) {
					tokens.add(t);
					if(SdlType.SEMICOLON.equals(t.getType())) {
						tags.add(constructTag(tokens));
						tokens = new ArrayList<>(size);
					}
				}
				if(!tokens.isEmpty()) {
					tags.add(constructTag(tokens));
				}
			}
		}

		reader.close();

		return tags;
	}

	private void addChildren(Tag parent) throws SDLParseException, IOException {
		List toks;
		while((toks=getLineTokens())!=null) {
			int size = toks.size();

			if(toks.get(0).getType()==SdlType.END_BLOCK) {
				return;
			} else if(toks.get(size-1).getType()==SdlType.START_BLOCK) {
				Tag tag = constructTag(toks.subList(0, size-1));
				addChildren(tag);
				parent.addChild(tag);
			} else {
				parent.addChild(constructTag(toks));
			}
		}

		// we have to use -2 for position rather than -1 for unknown because
		// the parseException method adds 1 to line and position
		parseException("No close block (}).", lineNumber, -2);
	}

	/**
	 * Construct a tag (but not its children) from a string of tokens
	 *
	 * @throws SDLParseException
	 */
	Tag constructTag(List toks) throws SDLParseException {
	    if(lineNumber ==141) {
	        int sdf=23;
        }
		if(toks.isEmpty())
			// we have to use -2 for position rather than -1 for unknown because
			// the parseException method adds 1 to line and position
			parseException("Internal Error: Empty token list", lineNumber, -2);

		Token t0 = toks.get(0);


		if(t0.isLiteral()) {
			toks.add(0, t0 = new Token("content", -1, -1));
		} else if(!SdlType.IDENTIFIER.equals(t0.getType())) {
			expectingButGot("IDENTIFIER", "" + t0.getType() + " (" + t0.getText() + ")",
					t0.getLine(), t0.getPosition());
		}

		int size = toks.size();

		Tag tag = null;

		if(size == 1) {
			tag = new Tag(t0.getText());
		} else {
			int valuesStartIndex = 1;

			final Token t1 = toks.get(1);

			if(SdlType.COLON.equals(t1.getType())) {
				if(size==2 || !SdlType.IDENTIFIER.equals(toks.get(2).getType())) {
                    parseException("Colon (:) encountered in unexpected location.", t1.getLine(), t1.getPosition());
                }

				Token t2 = toks.get(2);
				tag = new Tag(t0.getText(), t2.getText());

				valuesStartIndex = 3;
			} else {
				tag = new Tag(t0.getText());
			}

			// read values
			int i = addTagValues(tag, toks, valuesStartIndex);

			// read attributes
			if(i toks, int tpos) throws SDLParseException {

		int size=toks.size(), i=tpos;

		for(; i < size; i++) {
			Token t = toks.get(i);
			if(t.isLiteral()) {

//			    if(SdlType.DATETIME.equals(t.getType())) { // don't think I need this
//                    tag.addValue( new SdlValue<>(ZonedDateTime.class.cast(t.getObjectForLiteral().getValue()), SdlType.DATETIME) );
////                    tag.addValue(LocalDateTime.class.cast(t.getObjectForLiteral()));
//                }


                tag.addValue(t.getObjectForLiteral());

			} else if(SdlType.IDENTIFIER.equals(t.getType())) {
                break;

			    // todo: CHECK THIS CODE - SOMETHING HERE IS WRONG/BROKEN
//			    if( (i+2) < size && SdlType.EQUALS.equals(toks.get(i+1).getType()) ) {
//                    tag.setAttribute(t.getText(), toks.get(i+2).getObjectForLiteral());
//                    i++;//i +=2;
//                } else {
//                    break;
//                }
                //i++;//i +=2;

			} else {
				expectingButGot("LITERAL or IDENTIFIER", t.getType(), t.getLine(), t.getPosition());
			}
		}

		return i;
	}

	/**
	 * Add attributes to the given tag
	 */
	private void addTagAttributes(final Tag tag, final List toks, final int tpos) throws SDLParseException {

		int i = tpos;
        final int size = toks.size();

        while(i getLineTokens() throws SDLParseException, IOException {
//		line = readLine();
        if(semicolonTerminated) {
            semicolonTerminated = false;
        } else {
            line = readLine();
        }

		if(line==null) {
            return null;
        }
		toks = new ArrayList<>();
		lineLength = line.length();
		sb = null;
		tokenStart=0;

		for(;pos0 && tokString.charAt(0)=='"' && tokString.charAt(tokString.length()-1)!='"') {
				parseException("String literal \"" + tokString + "\" not terminated by end quote.", lineNumber, line.length());
			} else if(tokString.length()==1 && tokString.charAt(0)=='"') {
				parseException("Orphan quote (unterminated string)", lineNumber, line.length());
			}
		}
	}

	private void handleEscapedDoubleQuotedString() throws SDLParseException, IOException {
		if(pos==lineLength-1) {
			line = readLine();
			if(line==null) {
				parseException("Escape at end of file.", lineNumber, pos);
			}

			lineLength = line.length();
			pos=-1;
			startEscapedQuoteLine=true;
		} else {
			// consume whitespace
			int j=pos+1;
			while(j.  Strings must start and end with \"");
        }

		return literal.substring(1, literal.length()-1);
	}

    static String parseMultilineString(final String literal) {
        if(literal.charAt(0) != '`' && literal.charAt(literal.length()-1) != '`') {
            throw new IllegalArgumentException("Malformed string <" + literal + ">.  String Literals must start and end with `");
        }

        return new String(literal.substring(1, literal.length() - 1).getBytes());
        //text.replaceAll("\\", "\\\\");
    }

	static Character parseCharacter(String literal) {
		if(literal.charAt(0)!='\'' || literal.charAt(literal.length()-1)!='\'') {
            throw new IllegalArgumentException("Malformed character <" +
                literal + ">. Character literals must start and end with single quotes.");
        }

		return Character.valueOf(literal.charAt(1));
	}

	static Number parseNumber(String literal) {
		int textLength = literal.length();
		boolean hasDot=false;
		int tailStart=0;

		for(int i=0; i < textLength; i++) {
			char c=literal.charAt(i);
			if("-0123456789".indexOf(c) == -1) {
				if(c=='.') {
					if(hasDot) {
						throw new NumberFormatException("Encountered second decimal point.");
					} else if(i == textLength-1) {
						throw new NumberFormatException("Encountered decimal point at the end of the number.");
					} else {
						hasDot=true;
					}
				} else {
					tailStart=i;
					break;
				}
			} else {
				tailStart=i+1;
			}
		}

		final String number = literal.substring(0, tailStart);
		final String tail = literal.substring(tailStart);


		if(tail.length() == 0) {
			if(hasDot) {
				return new Double(number);
			} else {
				return new Integer(number);
			}
		}

		if(tail.equalsIgnoreCase("BD")) {
			return new BigDecimal(number);
		} else if(tail.equalsIgnoreCase("L")) {
			if(hasDot) {
				throw new NumberFormatException("Long literal with decimal point");
			}
			return new Long(number);
		} else if(tail.equalsIgnoreCase("F")) {
			return new Float(number);
		} else if(tail.equalsIgnoreCase("D")) {
			return new Double(number);
		}

		throw new NumberFormatException("Could not parse number <" + literal + ">");
	}

	static LocalDate parseDate(final String literal) {
	    return LocalDate.parse(literal, DateTimeFormatter.ofPattern("y/M/d"));
    }

    static LocalTime parseTime(final String literal) {
        final String[] split = literal.split(":|\\.|-");

        //final StringBuilder pattern = new StringBuilder("H:m");

        //LocalTime.of()

        switch (split.length) {
            case 2: // H:m
                return LocalTime.parse(literal, DateTimeFormatter.ofPattern("H:m"));
                //return LocalTime.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
            case 3: // H:m:s
                return LocalTime.parse(literal, DateTimeFormatter.ofPattern("H:m:s"));
                //return LocalTime.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
            case 4: // H:m:s.S
                return LocalTime.parse(literal, DateTimeFormatter.ISO_LOCAL_TIME); //DateTimeFormatter.ofPattern("H:m:s.S")
            case 5:
                // can't handle time with a TimeZone but no Date
                return null;
//                return LocalTime.of(Integer.parseInt(split[0]),
//                    Integer.parseInt(split[1]),
//                    Integer.parseInt(split[2]),
//                    Integer.parseInt(split[3]));

//            case 1:
//                return LocalTime.parse(literal, DateTimeFormatter.ofPattern("H:m:s"));
//            case 2:
//                if(split[1].contains(".")) {
//                    return LocalTime.parse(literal, DateTimeFormatter.ofPattern("H:m:s.SSS"));
//                } // else we are trying to parse "HH:mm:ss-Z" which is not possible
//                break;
//            case 3:
//                // you cannot have ZonedTime without a date
//                //return LocalTime.parse(literal, DateTimeFormatter.ofPattern("HH:mm:ss.SSS-Z"));
//                break;
        }
        return null; // should there be an exception here???
    }

    static LocalDateTime parseLocalDateTime(final String literal) {
	    int[] intVals = {0,0,0,0,0,0,0};
        final String[] strVals = literal.split("/| |:|\\.");
        for (int i = 0; i < strVals.length; i++) {
            intVals[i] = Integer.parseInt(strVals[i]);
        }
        return LocalDateTime.of(intVals[0], intVals[1], intVals[2], intVals[3], intVals[4], intVals[5], intVals[6]*1_000_000);
    }

	static ZonedDateTime parseZonedDateTime(final String literal) {
        final TimeZone timeZone = literal.contains("-") ? TimeZone.getTimeZone(literal.substring(literal.indexOf("-") +1)) : TimeZone.getDefault();

        int[] intVals = {0,0,0,0,0,0,0};

        final String[] strVals = literal.contains("-") ? literal
            .substring(0, literal.lastIndexOf("-"))
            .split("/| |:|\\.") :
            literal.split("/| |:|\\.");

        for (int i = 0; i < strVals.length; i++) {
            intVals[i] = Integer.parseInt(strVals[i]);
        }

        return ZonedDateTime.of(intVals[0], intVals[1], intVals[2], intVals[3], intVals[4], intVals[5], intVals[6]*1_000_000, timeZone.toZoneId());
    }

//	static Calendar parseDateTime(String literal) {
//		int spaceIndex = literal.indexOf(' ');
//		if(spaceIndex==-1) {
//			return parseDate(literal);
//		} else {
//			Calendar dc = parseDate(literal.substring(0,spaceIndex));
//			String timeString = literal.substring(spaceIndex+1);
//
//			int dashIndex = timeString.indexOf('-');
//			String tzString = null;
//			if(dashIndex!=-1) {
//				tzString=timeString.substring(dashIndex+1);
//				timeString=timeString.substring(0, dashIndex);
//			}
//
//			String[] timeComps = timeString.split(":");
//			if(timeComps.length<2 || timeComps.length>3)
//				throw new IllegalArgumentException("Malformed time " +
//						"component in date/time literal.  Must use " +
//						"hh:mm(:ss)(.xxx)");
//
//			int hour = 0;
//			int minute = 0;
//			int second = 0;
//			int millisecond = 0;
//
//			// TODO - parse the time string, concatenate and return date/time
//			try {
//				hour=Integer.parseInt(timeComps[0]);
//				minute=Integer.parseInt(timeComps[1]);
//
//				if(timeComps.length==3) {
//					String last = timeComps[2];
//
//					int dotIndex = last.indexOf('.');
//					if(dotIndex==-1) {
//						second=Integer.parseInt(last);
//					} else {
//						second=Integer.parseInt(last.substring(0,dotIndex));
//
//						String millis = last.substring(dotIndex+1);
//						if(millis.length()==1)
//							millis=millis+"00";
//						else if(millis.length()==2)
//							millis=millis+"0";
//						millisecond=Integer.parseInt(millis);
//					}
//				}
//			} catch(NumberFormatException nfe) {
//				throw new IllegalArgumentException("Number format exception " +
//						"in time portion of date/time literal \"" +
//							nfe.getMessage() +"\"");
//			}
//
//			TimeZone tz = (tzString==null) ? TimeZone.getDefault() :
//				TimeZone.getTimeZone(tzString);
//
//			GregorianCalendar gc = new GregorianCalendar(tz);
//			gc.set(dc.get(Calendar.YEAR), dc.get(Calendar.MONTH),
//					dc.get(Calendar.DAY_OF_MONTH), hour, minute, second);
//			gc.set(Calendar.MILLISECOND, millisecond);
//			gc.getTime();
//
//			return gc;
//		}
//	}

//	static Calendar parseDate(String literal) {
//		String[] comps = literal.split("/");
//		if(comps.length!=3)
//			throw new IllegalArgumentException("Malformed Date <" + literal + ">");
//
//		try {
//			return new GregorianCalendar(
//					Integer.parseInt(comps[0]),
//					Integer.parseInt(comps[1])-1,
//					Integer.parseInt(comps[2])
//			);
//		} catch(final NumberFormatException e) {
//			throw new IllegalArgumentException(String.format("Number format exception: \"%s\" for date literal <%s>", e.getMessage(), literal));
//
//		}
//	}

	static byte[] parseBinary(String literal) {
		final String stripped = literal.substring(1, literal.length()-1);
		final StringBuilder sb = new StringBuilder();
		final int btLength = stripped.length();
		for(int i=0; i.  Time spans must use the format " +
                "(d:)hh:mm:ss(.xxx) Note: if the day component is " +
                "included it must be suffixed with lower case \"d\"");
        }

        final boolean negate = literal.contains("-");
        final List groups = Arrays.asList(literal.replace("-", "").split("d:|\\.")); // we should get 1 - 3 groups

        int days=0; // optional
		int hours=0; // mandatory
		int minutes=0; // mandatory
		int seconds=0; // mandatory
		long milliseconds=0; // optional

        for(final String group : groups) {
            if(group.matches("\\d+:\\d+:\\d+")) {
                final String[] segments = group.split(":");
                hours = Integer.parseInt(segments[0]);
                minutes = Integer.parseInt(segments[1]);
                seconds = Integer.parseInt(segments[2]);
            } else {
                if(groups.indexOf(group) == 0) {
                    days = Integer.parseInt(group);
                } else {
                    milliseconds = Long.parseLong(group);
                }
            }
        }

        final Duration duration = Duration.ofDays(days)
            .plusHours(hours)
            .plusMinutes(minutes)
            .plusSeconds(seconds)
            .plusMillis(milliseconds);

        return negate ? duration.negated() : duration;
    }

//	static SDLTimeSpan parseTimeSpan(String literal) {
//		int days=0; // optional
//		int hours=0; // mandatory
//		int minutes=0; // mandatory
//		int seconds=0; // mandatory
//		int milliseconds=0; // optional
//
//		String[] segments = literal.split(":");
//
//		if(segments.length<3 || segments.length>4)
//			throw new IllegalArgumentException("Malformed time span <" +
//					literal + ">.  Time spans must use the format " +
//					"(d:)hh:mm:ss(.xxx) Note: if the day component is " +
//					"included it must be suffixed with lower case \"d\"");
//
//		try {
//			if(segments.length==4) {
//				String dayString = segments[0];
//				if(!dayString.endsWith("d")) {
//					throw new IllegalArgumentException("The day component of a time span must end with a lower case d");
//				}
//
//				days = Integer.parseInt(dayString.substring(0, dayString.length()-1));
//
//				hours = Integer.parseInt(segments[1]);
//				minutes = Integer.parseInt(segments[2]);
//
//				if(segments.length==4) {
//					String last = segments[3];
//					int dotIndex = last.indexOf(".");
//
//					if(dotIndex==-1) {
//						seconds = Integer.parseInt(last);
//					} else {
//						seconds =
//							Integer.parseInt(
//									last.substring(0, dotIndex));
//
//						String millis = last.substring(dotIndex+1);
//						if(millis.length()==1)
//							millis=millis+"00";
//						else if(millis.length()==2)
//							millis=millis+"0";
//
//						milliseconds =
//							Integer.parseInt(millis);
//					}
//				}
//
//				if(days<0) {
//					hours=reverseIfPositive(hours);
//					minutes=reverseIfPositive(minutes);
//					seconds=reverseIfPositive(seconds);
//					milliseconds=reverseIfPositive(milliseconds);
//				}
//			} else {
//				hours=Integer.parseInt(segments[0]);
//				minutes=Integer.parseInt(segments[1]);
//
//				String last = segments[2];
//				int dotIndex = last.indexOf(".");
//
//				if(dotIndex==-1) {
//					seconds = Integer.parseInt(last);
//				} else {
//					seconds = Integer.parseInt(last.substring(0, dotIndex));
//
//					String millis = last.substring(dotIndex+1);
//					if(millis.length()==1)
//						millis=millis+"00";
//					else if(millis.length()==2)
//						millis=millis+"0";
//					milliseconds = Integer.parseInt(millis);
//				}
//
//				if(hours<0) {
//					minutes=reverseIfPositive(minutes);
//					seconds=reverseIfPositive(seconds);
//					milliseconds=reverseIfPositive(milliseconds);
//				}
//			}
//		} catch(final NumberFormatException e) {
//			throw new IllegalArgumentException(String.format("Number format in time span exception: \"%s\" for literal <%s>", e.getMessage(), literal));
//		}
//
//		return new SDLTimeSpan(days, hours, minutes, seconds, milliseconds);
//	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy