/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain Eclipse Public Licensed code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.editor.json.parsing;
import java.text.ParseException;
import org.eclipse.jface.preference.IPreferenceStore;
import com.aptana.ide.editor.json.lexing.JSONTokenTypes;
import com.aptana.ide.editor.json.parsing.nodes.JSONParseNode;
import com.aptana.ide.editor.json.parsing.nodes.JSONParseNodeTypes;
import com.aptana.ide.editors.UnifiedEditorsPlugin;
import com.aptana.ide.editors.unified.folding.FoldingExtensionPointLoader;
import com.aptana.ide.editors.unified.parsing.UnifiedParser;
import com.aptana.ide.lexer.ILexer;
import com.aptana.ide.lexer.Lexeme;
import com.aptana.ide.lexer.LexerException;
import com.aptana.ide.lexer.TokenCategories;
import com.aptana.ide.parsing.IParseState;
import com.aptana.ide.parsing.ParserInitializationException;
import com.aptana.ide.parsing.nodes.IParseNode;
import com.aptana.ide.parsing.nodes.IParseNodeFactory;
/**
* @author Kevin Lindsey
*/
public class JSONParser extends UnifiedParser
{
/**
* JSONParser
*
* @throws ParserInitializationException
*/
public JSONParser() throws ParserInitializationException
{
this(JSONMimeType.MimeType);
}
/**
* JSONParser
*
* @param mimeType
* @throws ParserInitializationException
*/
public JSONParser(String mimeType) throws ParserInitializationException
{
super(mimeType);
IPreferenceStore unifiedStore = UnifiedEditorsPlugin.getDefault().getPreferenceStore();
unifiedStore.setDefault(FoldingExtensionPointLoader.createEnablePreferenceId(JSONMimeType.MimeType), true);
}
/**
* @see com.aptana.ide.parsing.AbstractParser#advance()
*/
protected void advance() throws LexerException
{
super.advance();
while (this.isEOS() == false && this.currentLexeme.getCategoryIndex() == TokenCategories.WHITESPACE)
{
super.advance();
}
}
/**
* createNode
*
* @param type
* @param startingLexeme
* @return JSParseNode
*/
private JSONParseNode createNode(int type, Lexeme startingLexeme)
{
IParseNodeFactory factory = this.getParseNodeFactory();
JSONParseNode result = null;
if (factory != null)
{
result = (JSONParseNode) factory.createParseNode(type, startingLexeme);
}
else
{
// we need to return something to prevent NPE's
result = new JSONParseNode(type, startingLexeme);
}
return result;
}
/**
* @see com.aptana.ide.parsing.AbstractParser#createParseState(com.aptana.ide.parsing.IParseState)
*/
public IParseState createParseState(IParseState parent)
{
IParseState result;
if (parent == null)
{
result = new JSONParseState();
}
else
{
result = new JSONParseState(parent);
}
return result;
}
/**
* @see com.aptana.ide.parsing.AbstractParser#initializeLexer()
*/
public void initializeLexer() throws LexerException
{
ILexer lexer = this.getLexer();
String language = this.getLanguage();
lexer.setIgnoreSet(language, new int[] { JSONTokenTypes.WHITESPACE });
lexer.setLanguageAndGroup(language, "default"); //$NON-NLS-1$
}
/**
* @see com.aptana.ide.parsing.AbstractParser#parseAll(com.aptana.ide.parsing.nodes.IParseNode)
*/
public void parseAll(IParseNode parentNode) throws ParseException, LexerException
{
// set node to be used as the root node for the results of this parse
IParseNode rootNode;
if (parentNode == null)
{
IParseNodeFactory nodeFactory = this.getParseNodeFactory();
if (nodeFactory != null)
{
rootNode = nodeFactory.createRootNode();
if (parentNode != null)
{
parentNode.appendChild(rootNode);
}
}
else
{
rootNode = null;
}
}
else
{
rootNode = parentNode;
}
ILexer lexer = this.getLexer();
lexer.setLanguageAndGroup(this.getLanguage(), "default"); //$NON-NLS-1$
this.advance();
while (this.isEOS() == false)
{
IParseNode result = this.parseStatement();
if (rootNode != null && result != null)
{
rootNode.appendChild(result);
}
}
}
/**
* parseStatement
*
* @return IParseNode
* @throws LexerException
*/
private IParseNode parseStatement() throws LexerException
{
IParseNode result = null;
switch (this.currentLexeme.typeIndex)
{
case JSONTokenTypes.LBRACKET:
result = this.parseArray();
break;
case JSONTokenTypes.LCURLY:
result = this.parseObject();
break;
case JSONTokenTypes.FALSE:
case JSONTokenTypes.TRUE:
case JSONTokenTypes.NUMBER:
case JSONTokenTypes.STRING:
case JSONTokenTypes.NULL:
case JSONTokenTypes.REFERENCE:
result = this.createNode(JSONParseNodeTypes.SCALAR, this.currentLexeme);
this.advance();
break;
default:
advance();
}
return result;
}
/**
* parseArray
*
* @return parse node
* @throws LexerException
*/
private IParseNode parseArray() throws LexerException
{
IParseNode result = this.createNode(JSONParseNodeTypes.ARRAY, this.currentLexeme);
// assuming '[' and advancing over (not asserting to make things a bit easier)
this.advance();
while (this.isEOS() == false && this.currentLexeme.typeIndex != JSONTokenTypes.RBRACKET)
{
IParseNode child = this.parseStatement();
if (child != null)
{
result.appendChild(child);
}
else
{
this.recover(JSONTokenTypes.RBRACKET);
}
if (this.currentLexeme.typeIndex == JSONTokenTypes.COMMA)
{
this.advance();
}
}
if (this.currentLexeme.typeIndex == JSONTokenTypes.RBRACKET)
{
result.setEndingLexeme(this.currentLexeme);
this.advance();
}
return result;
}
/**
* parseObject
*
* @return parse node
* @throws LexerException
*/
private IParseNode parseObject() throws LexerException
{
IParseNode result = this.createNode(JSONParseNodeTypes.OBJECT, this.currentLexeme);
// assuming '{' and advancing over (not asserting to make things a bit easier)
this.advance();
while (this.isEOS() == false && this.currentLexeme.typeIndex != JSONTokenTypes.RCURLY)
{
IParseNode child = null;
try
{
child = this.parseNameValuePair();
}
catch (ParseException pe)
{
}
if (child != null)
{
result.appendChild(child);
}
else
{
this.recover(JSONTokenTypes.RCURLY);
}
if (this.currentLexeme.typeIndex == JSONTokenTypes.COMMA)
{
this.advance();
}
}
if (this.currentLexeme.typeIndex == JSONTokenTypes.RCURLY)
{
result.setEndingLexeme(this.currentLexeme);
this.advance();
}
return result;
}
/**
* parseNameValuePair
*
* @return parse node
* @throws LexerException
* @throws ParseException
*/
private IParseNode parseNameValuePair() throws LexerException, ParseException
{
IParseNode result = this.createNode(JSONParseNodeTypes.NAME_VALUE_PAIR, this.currentLexeme);
switch (this.currentLexeme.typeIndex)
{
case JSONTokenTypes.STRING:
case JSONTokenTypes.PROPERTY:
IParseNode name = this.createNode(JSONParseNodeTypes.SCALAR, this.currentLexeme);
result.appendChild(name);
this.advance();
this.assertAndAdvance(JSONTokenTypes.COLON, Messages.getString("JSONParser.Missing_Colon")); //$NON-NLS-1$
IParseNode value = this.parseStatement();
if (value != null)
{
result.appendChild(value);
}
else
{
// report failure
result = null;
}
break;
default:
// report failure
result = null;
}
return result;
}
/**
* recover
*
* @param closeType
*/
private void recover(int closeType) throws LexerException
{
Lexeme startLexeme = this.currentLexeme;
while (this.isEOS() == false)
{
int typeIndex = this.currentLexeme.typeIndex;
if (typeIndex == closeType)
{
break;
}
else if (typeIndex == JSONTokenTypes.LBRACKET || typeIndex == JSONTokenTypes.LCURLY || typeIndex == JSONTokenTypes.COMMA)
{
if (this.currentLexeme != startLexeme)
{
break;
}
else
{
this.advance();
}
}
else
{
this.advance();
}
}
}
}