/******************************************************************************* * Copyright (c) 2011, 2012 Wind River Systems, Inc. and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.tcf.core.scripting.parser; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.Assert; import org.eclipse.tcf.protocol.JSON; /** * Script parser implementation. */ public class Parser { // Reference to the script to parse private final String script; // Define some patterns to match the lines against private static final Pattern EMPTY_LINE = Pattern.compile("\\s*"); //$NON-NLS-1$ private static final Pattern COMMENT_LINE = Pattern.compile("\\s*#.*"); //$NON-NLS-1$ private static final Pattern CONNECT_LINE = Pattern.compile("\\s*connect\\s+.*"); //$NON-NLS-1$ private static final Pattern COMMAND_LINE = Pattern.compile("\\s*tcf\\s+(\\w+)\\s+(\\w+)(.*)"); //$NON-NLS-1$ /** * Constructor. */ public Parser(String script) { Assert.isNotNull(script); this.script = script; } /** * Parse the given script and returns the extracted command tokens. * * @return The list of command tokens found in the script, or an empty list. * @throws IOException - if the script parsing fails. */ public Token[] parse() throws IOException { List<Token> tokens = new ArrayList<Token>(); BufferedReader reader = new BufferedReader(new StringReader(script)); String line; while ((line = reader.readLine()) != null) { // All the following lines are ignored if matched if (EMPTY_LINE.matcher(line).matches()) continue; if (COMMENT_LINE.matcher(line).matches()) continue; if (CONNECT_LINE.matcher(line).matches()) continue; // If it is a command line, get the groups from it Matcher matcher = COMMAND_LINE.matcher(line); if (matcher.matches()) { String serviceName = matcher.group(1).trim(); String commandName = matcher.group(2).trim(); String arguments = matcher.group(3); // Create a new token Token token = new Token(); token.setServiceName(serviceName); token.setCommandName(commandName); // Parse the arguments parseArguments(token, arguments); // Add the token to the list tokens.add(token); } } reader.close(); return tokens.toArray(new Token[tokens.size()]); } /** * Parse the arguments string and add the extracted arguments * to the given token. * * @param token The token. Must not be <code>null</code>. * @param arguments The arguments string or <code>null</code>. */ protected void parseArguments(Token token, String arguments) { Assert.isNotNull(token); if (arguments == null || "".equals(arguments.trim())) { //$NON-NLS-1$ return; } // Tokenize by space, but do special handling for maps and lists StringTokenizer tokenizer = new StringTokenizer(arguments, " "); //$NON-NLS-1$ while (tokenizer.hasMoreTokens()) { String tok = tokenizer.nextToken(); if ("".equals(tok.trim())) continue; //$NON-NLS-1$ if (tok.equals("null")) { //$NON-NLS-1$ token.addArgument(null); continue; } if (tok.startsWith("\"")) { //$NON-NLS-1$ // String type StringBuilder fullTok = new StringBuilder(tok); boolean complete = isComplete(fullTok.toString(), '"', '"'); while (!complete && tokenizer.hasMoreTokens()) { fullTok.append(" "); //$NON-NLS-1$ fullTok.append(tokenizer.nextToken()); complete = isComplete(fullTok.toString(), '"', '"'); } if (complete) { String fullTokStr = fullTok.toString().trim(); if (fullTokStr.startsWith("\"")) fullTokStr = fullTokStr.substring(1); //$NON-NLS-1$ if (fullTokStr.endsWith("\"")) fullTokStr = fullTokStr.substring(0, fullTok.length() - 2); //$NON-NLS-1$ token.addArgument(fullTokStr); continue; } } if ("true".equalsIgnoreCase(tok) || "false".equalsIgnoreCase(tok)) { //$NON-NLS-1$ //$NON-NLS-2$ token.addArgument(Boolean.valueOf(tok)); continue; } try { Integer i = Integer.decode(tok); token.addArgument(i); continue; } catch (NumberFormatException e) { /* ignored on purpose */ } try { Long l = Long.decode(tok); token.addArgument(l); continue; } catch (NumberFormatException e) { /* ignored on purpose */ } try { Float f = Float.valueOf(tok); token.addArgument(f); continue; } catch (NumberFormatException e) { /* ignored on purpose */ } try { Double d = Double.valueOf(tok); token.addArgument(d); continue; } catch (NumberFormatException e) { /* ignored on purpose */ } // If it starts with '{' or '[', it's a map or list type if (tok.startsWith("{")) { //$NON-NLS-1$ // Map type StringBuilder fullTok = new StringBuilder(tok); boolean complete = isComplete(fullTok.toString(), '{', '}'); while (!complete && tokenizer.hasMoreTokens()) { fullTok.append(" "); //$NON-NLS-1$ fullTok.append(tokenizer.nextToken()); complete = isComplete(fullTok.toString(), '{', '}'); } if (complete) { String fullTokStr = fullTok.toString() + "\0"; //$NON-NLS-1$ try { Object[] args = JSON.parseSequence(fullTokStr.getBytes()); if (args != null) { for (Object arg : args) { if (arg != null) token.addArgument(arg); } continue; } } catch (IOException e) { /* ignored on purpose */ e.printStackTrace(); } } } if (tok.startsWith("[")) { //$NON-NLS-1$ // List type StringBuilder fullTok = new StringBuilder(tok); boolean complete = isComplete(fullTok.toString(), '[', ']'); while (!complete && tokenizer.hasMoreTokens()) { fullTok.append(" "); //$NON-NLS-1$ fullTok.append(tokenizer.nextToken()); complete = isComplete(fullTok.toString(), '[', ']'); } if (complete) { String fullTokStr = fullTok.toString() + "\0"; //$NON-NLS-1$ try { Object[] args = JSON.parseSequence(fullTokStr.getBytes()); if (args != null) { for (Object arg : args) { if (arg != null) token.addArgument(arg); } continue; } } catch (IOException e) { /* ignored on purpose */ } } } // Add the argument token as is token.addArgument(tok); } } /** * Counts the number of opening and closing characters inside the given * string and returns <code>true</code> if the number matches. * * @param tok The arguments token. Must not be <code>null</code>. * @param opening The opening character. * @param closing The closing character. * * @return <code>True</code> if the number of opening characters matches the number of closing characters, <code>false</code> otherwise. */ protected boolean isComplete(String tok, char opening, char closing) { Assert.isNotNull(tok); int countOpening = 0; int countClosing = 0; boolean same = opening == closing; for (int i = 0; i < tok.length(); i++) { char c = tok.charAt(i); if (c == opening && same) { if (countOpening > countClosing) countClosing++; else countOpening++; } else { if (c == opening) { countOpening++; continue; } if (c == closing) { countClosing++; continue; } } } return countOpening > 0 && countOpening == countClosing; } }