<commit_before>/***************************************************************************
 *   Copyright (C) 2006-2009 by <NAME>                           *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                         *
 *   For details about the authors of this software, see the AUTHORS file. *
 ***************************************************************************/

package jolie.lang.parse;

import jolie.lang.parse.context.ParsingContext;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import jolie.lang.Constants;
import jolie.lang.NativeType;
import jolie.lang.parse.ast.AddAssignStatement;
import jolie.lang.parse.ast.expression.AndConditionNode;
import jolie.lang.parse.ast.AssignStatement;
import jolie.lang.parse.ast.CompareConditionNode;
import jolie.lang.parse.ast.CompensateStatement;
import jolie.lang.parse.ast.expression.ConstantIntegerExpression;
import jolie.lang.parse.ast.expression.ConstantDoubleExpression;
import jolie.lang.parse.ast.expression.ConstantStringExpression;
import jolie.lang.parse.ast.CorrelationSetInfo;
import jolie.lang.parse.ast.CorrelationSetInfo.CorrelationAliasInfo;
import jolie.lang.parse.ast.CorrelationSetInfo.CorrelationVariableInfo;
import jolie.lang.parse.ast.courier.CourierChoiceStatement;
import jolie.lang.parse.ast.CurrentHandlerStatement;
import jolie.lang.parse.ast.DeepCopyStatement;
import jolie.lang.parse.ast.DefinitionCallStatement;
import jolie.lang.parse.ast.DefinitionNode;
import jolie.lang.parse.ast.DivideAssignStatement;
import jolie.lang.parse.ast.EmbeddedServiceNode;
import jolie.lang.parse.ast.ExecutionInfo;
import jolie.lang.parse.ast.ExitStatement;
import jolie.lang.parse.ast.ForEachStatement;
import jolie.lang.parse.ast.ForStatement;
import jolie.lang.parse.ast.expression.FreshValueExpressionNode;
import jolie.lang.parse.ast.IfStatement;
import jolie.lang.parse.ast.InstallFixedVariableExpressionNode;
import jolie.lang.parse.ast.InstallFunctionNode;
import jolie.lang.parse.ast.InstallStatement;
import jolie.lang.parse.ast.expression.IsTypeExpressionNode;
import jolie.lang.parse.ast.LinkInStatement;
import jolie.lang.parse.ast.LinkOutStatement;
import jolie.lang.parse.ast.NDChoiceStatement;
import jolie.lang.parse.ast.expression.NotExpressionNode;
import jolie.lang.parse.ast.NotificationOperationStatement;
import jolie.lang.parse.ast.NullProcessStatement;
import jolie.lang.parse.ast.OLSyntaxNode;
import jolie.lang.parse.ast.OneWayOperationDeclaration;
import jolie.lang.parse.ast.OneWayOperationStatement;
import jolie.lang.parse.ast.expression.OrConditionNode;
import jolie.lang.parse.ast.OutputPortInfo;
import jolie.lang.parse.ast.ParallelStatement;
import jolie.lang.parse.ast.PointerStatement;
import jolie.lang.parse.ast.PostDecrementStatement;
import jolie.lang.parse.ast.PostIncrementStatement;
import jolie.lang.parse.ast.PreDecrementStatement;
import jolie.lang.parse.ast.PreIncrementStatement;
import jolie.lang.parse.ast.expression.ProductExpressionNode;
import jolie.lang.parse.ast.Program;
import jolie.lang.parse.ast.RequestResponseOperationDeclaration;
import jolie.lang.parse.ast.RequestResponseOperationStatement;
import jolie.lang.parse.ast.Scope;
import jolie.lang.parse.ast.SequenceStatement;
import jolie.lang.parse.ast.InputPortInfo;
import jolie.lang.parse.ast.InterfaceDefinition;
import jolie.lang.parse.ast.InterfaceExtenderDefinition;
import jolie.lang.parse.ast.SubtractAssignStatement;
import jolie.lang.parse.ast.MultiplyAssignStatement;
import jolie.lang.parse.ast.OperationCollector;
import jolie.lang.parse.ast.PortInfo;
import jolie.lang.parse.ast.SolicitResponseOperationStatement;
import jolie.lang.parse.ast.SpawnStatement;
import jolie.lang.parse.ast.expression.SumExpressionNode;
import jolie.lang.parse.ast.SynchronizedStatement;
import jolie.lang.parse.ast.ThrowStatement;
import jolie.lang.parse.ast.TypeCastExpressionNode;
import jolie.lang.parse.ast.UndefStatement;
import jolie.lang.parse.ast.ValueVectorSizeExpressionNode;
import jolie.lang.parse.ast.expression.VariableExpressionNode;
import jolie.lang.parse.ast.VariablePathNode;
import jolie.lang.parse.ast.VariablePathNode.Type;
import jolie.lang.parse.ast.WhileStatement;
import jolie.lang.parse.ast.courier.CourierDefinitionNode;
import jolie.lang.parse.ast.courier.NotificationForwardStatement;
import jolie.lang.parse.ast.courier.SolicitResponseForwardStatement;
import jolie.lang.parse.ast.expression.*;
import jolie.lang.parse.ast.types.TypeDefinition;
import jolie.lang.parse.ast.types.TypeDefinitionLink;
import jolie.lang.parse.ast.types.TypeDefinitionUndefined;
import jolie.lang.parse.ast.types.TypeInlineDefinition;
import jolie.lang.parse.context.URIParsingContext;
import jolie.util.Pair;
import jolie.util.Range;

/** Parser for a .ol file. 
 * @author <NAME>
 *
 */
public class OLParser extends AbstractParser
{
	private final Program program;
	private final Map< String, Scanner.Token > constantsMap =
		new HashMap< String, Scanner.Token >();
	private boolean insideInstallFunction = false;
	private String[] includePaths;
	private final Map< String, InterfaceDefinition > interfaces =
		new HashMap< String, InterfaceDefinition >();
	private final Map< String, InterfaceExtenderDefinition > interfaceExtenders =
		new HashMap< String, InterfaceExtenderDefinition >();

	private final Map< String, TypeDefinition > definedTypes;
	private final ClassLoader classLoader;
	
	private InterfaceExtenderDefinition currInterfaceExtender = null;
	
	public OLParser( Scanner scanner, String[] includePaths, ClassLoader classLoader )
	{
		super( scanner );
		ParsingContext context = new URIParsingContext( scanner.source(), 0 );
		this.program = new Program( context );
		this.includePaths = includePaths;
		this.classLoader = classLoader;
		this.definedTypes = createTypeDeclarationMap( context );
	}

	public void putConstants( Map< String, Scanner.Token > constantsToPut )
	{
		constantsMap.putAll( constantsToPut );
	}

	public static Map< String, TypeDefinition > createTypeDeclarationMap( ParsingContext context )
	{
		Map< String, TypeDefinition > definedTypes = new HashMap< String, TypeDefinition >();

		// Fill in defineTypes with all the supported native types (string, int, double, ...)
		for( NativeType type : NativeType.values() ) {
			definedTypes.put( type.id(), new TypeInlineDefinition( context, type.id(), type, Constants.RANGE_ONE_TO_ONE ) );
		}
		definedTypes.put( TypeDefinitionUndefined.UNDEFINED_KEYWORD, TypeDefinitionUndefined.getInstance() );
		
		return definedTypes;
	}

	public Program parse()
		throws IOException, ParserException
	{
		_parse();

		if ( initSequence != null ) {
			program.addChild( new DefinitionNode( getContext(), "init", initSequence ) );
		}

		if ( main != null ) {
			program.addChild( main );
		}

		return program;
	}

	private void _parse()
		throws IOException, ParserException
	{
		getToken();
		Scanner.Token t;
		do {
			t = token;
			parseInclude();
			parseConstants();
			parseInclude();
			parseExecution();
			parseInclude();
			parseCorrelationSets();
			parseInclude();
			parseTypes();
			parseInclude();
			parseInterfaceOrPort();
			parseInclude();
			parseEmbedded();
			parseInclude();
			parseCode();
		} while( t != token );

		if ( t.isNot( Scanner.TokenType.EOF ) ) {
			throwException( "Invalid token encountered" );
		}
	}

	private void parseTypes()
		throws IOException, ParserException
	{
		String typeName;
		TypeDefinition currentType;

		program.addChild( TypeDefinitionUndefined.getInstance() );

		while( token.isKeyword( "type" ) ) {
			getToken();
			typeName = token.content();
			eat( Scanner.TokenType.ID, "expected type name" );
			eat( Scanner.TokenType.COLON, "expected COLON (cardinality not allowed in root type declaration, it is fixed to [1,1])" );
			NativeType nativeType = readNativeType();
			if ( nativeType == null ) { // It's a user-defined type
				currentType = new TypeDefinitionLink( getContext(), typeName, Constants.RANGE_ONE_TO_ONE, token.content() );
				getToken();
			} else {
				currentType = new TypeInlineDefinition( getContext(), typeName, nativeType, Constants.RANGE_ONE_TO_ONE );
				getToken();
				if ( token.is( Scanner.TokenType.LCURLY ) ) { // We have sub-types to parse
					parseSubTypes( (TypeInlineDefinition)currentType );
				}
			}

			// Keep track of the root types to support them in successive type declarations
			definedTypes.put( typeName, currentType );
			program.addChild( currentType );
		}
	}

	private NativeType readNativeType()
	{
		if ( token.is( Scanner.TokenType.CAST_INT ) ) {
			return NativeType.INT;
		} else if ( token.is( Scanner.TokenType.CAST_DOUBLE ) ) {
			return NativeType.DOUBLE;
		} else if ( token.is( Scanner.TokenType.CAST_STRING ) ) {
			return NativeType.STRING;
		} else if ( token.is( Scanner.TokenType.CAST_LONG ) ) {
			return NativeType.LONG;
		} else if ( token.is( Scanner.TokenType.CAST_BOOL ) ) {
			return NativeType.BOOL;
		} else {
			return NativeType.fromString( token.content() );
		}
	}

	private void parseSubTypes( TypeInlineDefinition type )
		throws IOException, ParserException
	{
		eat( Scanner.TokenType.LCURLY, "expected {" );
		
		if ( token.is( Scanner.TokenType.QUESTION_MARK ) ) {
			type.setUntypedSubTypes( true );
			getToken();
		} else {
			TypeDefinition currentSubType;
			while( !token.is( Scanner.TokenType.RCURLY ) ) {
				currentSubType = parseSubType();
				if ( type.hasSubType( currentSubType.id() ) ) {
					throwException( "sub-type " + currentSubType.id() + " conflicts with another sub-type with the same name" );
				}
				type.putSubType( currentSubType );
			}
		}

		eat( Scanner.TokenType.RCURLY, "RCURLY expected" );
	}

	private TypeDefinition parseSubType()
		throws IOException, ParserException
	{
		eat( Scanner.TokenType.DOT, "sub-type syntax error (dot not found)" );

		// SubType id
		String id = token.content();
		eatIdentifier( "expected type name" );

		Range cardinality = parseCardinality();
		eat( Scanner.TokenType.COLON, "expected COLON" );

		NativeType nativeType = readNativeType();

		if ( nativeType == null ) { // It's a user-defined type
			TypeDefinitionLink linkedSubType = new TypeDefinitionLink( getContext(), id, cardinality, token.content() );
			getToken();
			return linkedSubType;
		} else {
			getToken();
			TypeInlineDefinition inlineSubType = new TypeInlineDefinition( getContext(), id, nativeType, cardinality );
			if ( token.is( Scanner.TokenType.LCURLY ) ) { // Has ulterior sub-types
				parseSubTypes( inlineSubType );
			}
			return inlineSubType;
		}
	}

	private Range parseCardinality()
		throws IOException, ParserException
	{
		int min = -1;
		int max = -1;

		if ( token.is( Scanner.TokenType.COLON ) ) { // Default (no cardinality specified)
			min = 1;
			max = 1;
		} else if ( token.is( Scanner.TokenType.QUESTION_MARK ) ) {
			min = 0;
			max = 1;
			getToken();
		} else if ( token.is( Scanner.TokenType.ASTERISK ) ) {
			min = 0;
			max = Integer.MAX_VALUE;
			getToken();
		} else if ( token.is( Scanner.TokenType.LSQUARE ) ) {
			getToken(); // eat [

			// Minimum
			assertToken( Scanner.TokenType.INT, "expected int value" );
			min = Integer.parseInt( token.content() );
			if ( min < 0 ) {
				throwException( "Minimum number of occurences of a sub-type must be positive or zero" );
			}

			getToken();
			eat( Scanner.TokenType.COMMA, "expected comma separator" );

			// Maximum
			if ( token.is( Scanner.TokenType.INT ) ) {
				max = Integer.parseInt( token.content() );
				if ( max < 1 ) {
					throwException( "Maximum number of occurences of a sub-type must be positive" );
				}
			} else if ( token.is( Scanner.TokenType.ASTERISK ) ) {
				max = Integer.MAX_VALUE;
			} else {
				throwException( "Maximum number of sub-type occurences not valid: " + token.content() );
			}

			getToken();
			eat( Scanner.TokenType.RSQUARE, "expected ]" );
		} else {
			throwException( "Sub-type cardinality syntax error" );
		}

		return new Range( min, max );
	}


	private void parseEmbedded()
		throws IOException, ParserException
	{
		if ( token.isKeyword( "embedded" ) ) {
			String servicePath, portId;
			getToken();
			eat( Scanner.TokenType.LCURLY, "expected {" );
			boolean keepRun = true;
			Constants.EmbeddedServiceType type;
			while ( keepRun ) {
				type = null;
				if ( token.isKeyword( "Java" ) ) {
					type = Constants.EmbeddedServiceType.JAVA;
				} else if ( token.isKeyword( "Jolie" ) ) {
					type = Constants.EmbeddedServiceType.JOLIE;
				} else if ( token.isKeyword( "JavaScript" ) ) {
					type = Constants.EmbeddedServiceType.JAVASCRIPT;
				}
				if ( type == null ) {
					keepRun = false;
				} else {
					getToken();
					eat( Scanner.TokenType.COLON, "expected : after embedded service type" );
					checkConstant();
					while ( token.is( Scanner.TokenType.STRING ) ) {
						servicePath = token.content();
						getToken();
						if ( token.isKeyword( "in" ) ) {
							eatKeyword( "in", "expected in" );
							assertToken( Scanner.TokenType.ID, "expected output port name" );
							portId = token.content();
							getToken();
						} else {
							portId = null;
						}
						program.addChild(
							new EmbeddedServiceNode(
							getContext(),
							type,
							servicePath,
							portId ) );
						if ( token.is( Scanner.TokenType.COMMA ) ) {
							getToken();
						} else {
							break;
						}
					}
				}
			}
			eat( Scanner.TokenType.RCURLY, "expected }" );
		}
	}

	private void parseCorrelationSets()
		throws IOException, ParserException
	{
		while( token.isKeyword( "cset" ) ) {
			getToken();
			/*assertToken( Scanner.TokenType.ID, "expected correlation set name" );
			String csetName = token.content();
			getToken();*/
			eat( Scanner.TokenType.LCURLY, "expected {" );
			List< CorrelationVariableInfo > variables = new ArrayList< CorrelationVariableInfo >();
			List< CorrelationAliasInfo > aliases;
			VariablePathNode correlationVariablePath;
			String typeName;
			while ( token.is( Scanner.TokenType.ID ) ) {
				aliases = new LinkedList< CorrelationAliasInfo >();
				correlationVariablePath = parseVariablePath();
				eat( Scanner.TokenType.COLON, "expected correlation variable alias list" );
				assertToken( Scanner.TokenType.ID, "expected correlation variable alias" );
				while ( token.is( Scanner.TokenType.ID ) ) {
					typeName = token.content();
					getToken();
					eat( Scanner.TokenType.DOT, "expected . after message type name in correlation alias" );
					aliases.add( new CorrelationAliasInfo( typeName, parseVariablePath() ) );
				}
				variables.add( new CorrelationVariableInfo( correlationVariablePath, aliases ) );
				if ( token.is( Scanner.TokenType.COMMA ) ) {
					getToken();
				} else {
					break;
				}
			}

			program.addChild( new CorrelationSetInfo( getContext(), variables ) );
			eat( Scanner.TokenType.RCURLY, "expected }" );
		}
	}

	private void parseExecution()
		throws IOException, ParserException
	{
		if ( token.is( Scanner.TokenType.EXECUTION ) ) {
			Constants.ExecutionMode mode = Constants.ExecutionMode.SEQUENTIAL;
			getToken();
			eat( Scanner.TokenType.LCURLY, "{ expected" );
			assertToken( Scanner.TokenType.ID, "expected execution modality" );
			if ( "sequential".equals( token.content() ) ) {
				mode = Constants.ExecutionMode.SEQUENTIAL;
			} else if ( "concurrent".equals( token.content() ) ) {
				mode = Constants.ExecutionMode.CONCURRENT;
			} else if ( "single".equals( token.content() ) ) {
				mode = Constants.ExecutionMode.SINGLE;
			} else {
				throwException( "Expected execution mode, found " + token.content() );
			}
			program.addChild( new ExecutionInfo( getContext(), mode ) );
			getToken();
			eat( Scanner.TokenType.RCURLY, "} expected" );
		}
	}

	private void parseConstants()
		throws IOException, ParserException
	{
		if ( token.is( Scanner.TokenType.CONSTANTS ) ) {
			getToken();
			eat( Scanner.TokenType.LCURLY, "expected {" );
			boolean keepRun = true;
			while ( token.is( Scanner.TokenType.ID ) && keepRun ) {
				String cId = token.content();
				getToken();
				eat( Scanner.TokenType.ASSIGN, "expected =" );
				if ( token.isValidConstant() == false ) {
					throwException( "expected string, integer, double or identifier constant" );
				}
				if ( constantsMap.containsKey( cId ) == false ) {
					constantsMap.put( cId, token );
				}
				getToken();
				if ( token.isNot( Scanner.TokenType.COMMA ) ) {
					keepRun = false;
				} else {
					getToken();
				}
			}
			eat( Scanner.TokenType.RCURLY, "expected }" );
		}
	}

	private static class IncludeFile {
		private final InputStream inputStream;
		private final String parentPath;
		private IncludeFile( InputStream inputStream, String parentPath )
		{
			this.inputStream = inputStream;
			this.parentPath = parentPath;
		}

		private InputStream getInputStream()
		{
			return inputStream;
		}

		private String getParentPath()
		{
			return parentPath;
		}
	}

	private static IncludeFile retrieveIncludeFile( final String path, final String filename )
	{
		IncludeFile ret = null;

		File f = new File(
				new StringBuilder()
					.append( path )
					.append( Constants.fileSeparator )
					.append( filename )
					.toString()
				);
		try {
			ret = new IncludeFile(
					new BufferedInputStream( new FileInputStream( f ) ),
					f.getParent()
				);
		} catch( FileNotFoundException e ) {
			try {
				String urlStr =
					new StringBuilder()
						.append( path )
						.append( Constants.fileSeparator )
						.append( filename )
						.toString();
				URL url = null;
				if ( urlStr.startsWith( "jap:" ) || urlStr.startsWith( "jar:" ) ) {
					/*
					 * We need the embedded URL path, otherwise URI.normalize
					 * is going to do nothing.
					 */
					url = new URL(
						urlStr.substring( 0,4 ) + new URI( urlStr.substring( 4 ) ).normalize().toString()
					);
				} else {
					url = new URL( new URI( urlStr ).normalize().toString() );
				}
				ret = new IncludeFile(
					url.openStream(),
					new File( url.toString() ).getParent()
					//path
				);
			} catch( MalformedURLException mue ) {
			} catch( IOException ioe ) {
			} catch( URISyntaxException use ) {}
		}
		return ret;
	}

	private void parseInclude()
		throws IOException, ParserException
	{
		String[] origIncludePaths;
		IncludeFile includeFile;
		while ( token.is( Scanner.TokenType.INCLUDE ) ) {
			getToken();
			Scanner oldScanner = scanner();
			assertToken( Scanner.TokenType.STRING, "expected filename to include" );
			String includeStr = token.content();
			includeFile = null;

			// Try the same directory of the program file first.
			if ( includePaths.length > 1 ) {
				includeFile = retrieveIncludeFile( includePaths[0], includeStr );
			}

			if ( includeFile == null ) {
				URL includeURL = classLoader.getResource( includeStr );
				if ( includeURL != null ) {
					includeFile = new IncludeFile( includeURL.openStream(), null );
				}
			}

			for ( int i = 1; i < includePaths.length && includeFile == null; i++ ) {
				includeFile = retrieveIncludeFile( includePaths[i], includeStr );
			}

			if ( includeFile == null ) {
				throwException( "File not found: " + includeStr );
			}

			origIncludePaths = includePaths;
			try {
				setScanner( new Scanner( includeFile.getInputStream(), new URI( includeFile.parentPath.toString() + includeStr ) ) );<commit_msg>Fix for including files through the classloader.<commit_after>/***************************************************************************
 *   Copyright (C) 2006-2009 by <NAME>                           *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                         *
 *   For details about the authors of this software, see the AUTHORS file. *
 ***************************************************************************/

package jolie.lang.parse;

import jolie.lang.parse.context.ParsingContext;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import jolie.lang.Constants;
import jolie.lang.NativeType;
import jolie.lang.parse.ast.AddAssignStatement;
import jolie.lang.parse.ast.expression.AndConditionNode;
import jolie.lang.parse.ast.AssignStatement;
import jolie.lang.parse.ast.CompareConditionNode;
import jolie.lang.parse.ast.CompensateStatement;
import jolie.lang.parse.ast.expression.ConstantIntegerExpression;
import jolie.lang.parse.ast.expression.ConstantDoubleExpression;
import jolie.lang.parse.ast.expression.ConstantStringExpression;
import jolie.lang.parse.ast.CorrelationSetInfo;
import jolie.lang.parse.ast.CorrelationSetInfo.CorrelationAliasInfo;
import jolie.lang.parse.ast.CorrelationSetInfo.CorrelationVariableInfo;
import jolie.lang.parse.ast.courier.CourierChoiceStatement;
import jolie.lang.parse.ast.CurrentHandlerStatement;
import jolie.lang.parse.ast.DeepCopyStatement;
import jolie.lang.parse.ast.DefinitionCallStatement;
import jolie.lang.parse.ast.DefinitionNode;
import jolie.lang.parse.ast.DivideAssignStatement;
import jolie.lang.parse.ast.EmbeddedServiceNode;
import jolie.lang.parse.ast.ExecutionInfo;
import jolie.lang.parse.ast.ExitStatement;
import jolie.lang.parse.ast.ForEachStatement;
import jolie.lang.parse.ast.ForStatement;
import jolie.lang.parse.ast.expression.FreshValueExpressionNode;
import jolie.lang.parse.ast.IfStatement;
import jolie.lang.parse.ast.InstallFixedVariableExpressionNode;
import jolie.lang.parse.ast.InstallFunctionNode;
import jolie.lang.parse.ast.InstallStatement;
import jolie.lang.parse.ast.expression.IsTypeExpressionNode;
import jolie.lang.parse.ast.LinkInStatement;
import jolie.lang.parse.ast.LinkOutStatement;
import jolie.lang.parse.ast.NDChoiceStatement;
import jolie.lang.parse.ast.expression.NotExpressionNode;
import jolie.lang.parse.ast.NotificationOperationStatement;
import jolie.lang.parse.ast.NullProcessStatement;
import jolie.lang.parse.ast.OLSyntaxNode;
import jolie.lang.parse.ast.OneWayOperationDeclaration;
import jolie.lang.parse.ast.OneWayOperationStatement;
import jolie.lang.parse.ast.expression.OrConditionNode;
import jolie.lang.parse.ast.OutputPortInfo;
import jolie.lang.parse.ast.ParallelStatement;
import jolie.lang.parse.ast.PointerStatement;
import jolie.lang.parse.ast.PostDecrementStatement;
import jolie.lang.parse.ast.PostIncrementStatement;
import jolie.lang.parse.ast.PreDecrementStatement;
import jolie.lang.parse.ast.PreIncrementStatement;
import jolie.lang.parse.ast.expression.ProductExpressionNode;
import jolie.lang.parse.ast.Program;
import jolie.lang.parse.ast.RequestResponseOperationDeclaration;
import jolie.lang.parse.ast.RequestResponseOperationStatement;
import jolie.lang.parse.ast.Scope;
import jolie.lang.parse.ast.SequenceStatement;
import jolie.lang.parse.ast.InputPortInfo;
import jolie.lang.parse.ast.InterfaceDefinition;
import jolie.lang.parse.ast.InterfaceExtenderDefinition;
import jolie.lang.parse.ast.SubtractAssignStatement;
import jolie.lang.parse.ast.MultiplyAssignStatement;
import jolie.lang.parse.ast.OperationCollector;
import jolie.lang.parse.ast.PortInfo;
import jolie.lang.parse.ast.SolicitResponseOperationStatement;
import jolie.lang.parse.ast.SpawnStatement;
import jolie.lang.parse.ast.expression.SumExpressionNode;
import jolie.lang.parse.ast.SynchronizedStatement;
import jolie.lang.parse.ast.ThrowStatement;
import jolie.lang.parse.ast.TypeCastExpressionNode;
import jolie.lang.parse.ast.UndefStatement;
import jolie.lang.parse.ast.ValueVectorSizeExpressionNode;
import jolie.lang.parse.ast.expression.VariableExpressionNode;
import jolie.lang.parse.ast.VariablePathNode;
import jolie.lang.parse.ast.VariablePathNode.Type;
import jolie.lang.parse.ast.WhileStatement;
import jolie.lang.parse.ast.courier.CourierDefinitionNode;
import jolie.lang.parse.ast.courier.NotificationForwardStatement;
import jolie.lang.parse.ast.courier.SolicitResponseForwardStatement;
import jolie.lang.parse.ast.expression.*;
import jolie.lang.parse.ast.types.TypeDefinition;
import jolie.lang.parse.ast.types.TypeDefinitionLink;
import jolie.lang.parse.ast.types.TypeDefinitionUndefined;
import jolie.lang.parse.ast.types.TypeInlineDefinition;
import jolie.lang.parse.context.URIParsingContext;
import jolie.util.Pair;
import jolie.util.Range;

/** Parser for a .ol file. 
 * @author <NAME>
 *
 */
public class OLParser extends AbstractParser
{
	private final Program program;
	private final Map< String, Scanner.Token > constantsMap =
		new HashMap< String, Scanner.Token >();
	private boolean insideInstallFunction = false;
	private String[] includePaths;
	private final Map< String, InterfaceDefinition > interfaces =
		new HashMap< String, InterfaceDefinition >();
	private final Map< String, InterfaceExtenderDefinition > interfaceExtenders =
		new HashMap< String, InterfaceExtenderDefinition >();

	private final Map< String, TypeDefinition > definedTypes;
	private final ClassLoader classLoader;
	
	private InterfaceExtenderDefinition currInterfaceExtender = null;
	
	public OLParser( Scanner scanner, String[] includePaths, ClassLoader classLoader )
	{
		super( scanner );
		ParsingContext context = new URIParsingContext( scanner.source(), 0 );
		this.program = new Program( context );
		this.includePaths = includePaths;
		this.classLoader = classLoader;
		this.definedTypes = createTypeDeclarationMap( context );
	}

	public void putConstants( Map< String, Scanner.Token > constantsToPut )
	{
		constantsMap.putAll( constantsToPut );
	}

	public static Map< String, TypeDefinition > createTypeDeclarationMap( ParsingContext context )
	{
		Map< String, TypeDefinition > definedTypes = new HashMap< String, TypeDefinition >();

		// Fill in defineTypes with all the supported native types (string, int, double, ...)
		for( NativeType type : NativeType.values() ) {
			definedTypes.put( type.id(), new TypeInlineDefinition( context, type.id(), type, Constants.RANGE_ONE_TO_ONE ) );
		}
		definedTypes.put( TypeDefinitionUndefined.UNDEFINED_KEYWORD, TypeDefinitionUndefined.getInstance() );
		
		return definedTypes;
	}

	public Program parse()
		throws IOException, ParserException
	{
		_parse();

		if ( initSequence != null ) {
			program.addChild( new DefinitionNode( getContext(), "init", initSequence ) );
		}

		if ( main != null ) {
			program.addChild( main );
		}

		return program;
	}

	private void _parse()
		throws IOException, ParserException
	{
		getToken();
		Scanner.Token t;
		do {
			t = token;
			parseInclude();
			parseConstants();
			parseInclude();
			parseExecution();
			parseInclude();
			parseCorrelationSets();
			parseInclude();
			parseTypes();
			parseInclude();
			parseInterfaceOrPort();
			parseInclude();
			parseEmbedded();
			parseInclude();
			parseCode();
		} while( t != token );

		if ( t.isNot( Scanner.TokenType.EOF ) ) {
			throwException( "Invalid token encountered" );
		}
	}

	private void parseTypes()
		throws IOException, ParserException
	{
		String typeName;
		TypeDefinition currentType;

		program.addChild( TypeDefinitionUndefined.getInstance() );

		while( token.isKeyword( "type" ) ) {
			getToken();
			typeName = token.content();
			eat( Scanner.TokenType.ID, "expected type name" );
			eat( Scanner.TokenType.COLON, "expected COLON (cardinality not allowed in root type declaration, it is fixed to [1,1])" );
			NativeType nativeType = readNativeType();
			if ( nativeType == null ) { // It's a user-defined type
				currentType = new TypeDefinitionLink( getContext(), typeName, Constants.RANGE_ONE_TO_ONE, token.content() );
				getToken();
			} else {
				currentType = new TypeInlineDefinition( getContext(), typeName, nativeType, Constants.RANGE_ONE_TO_ONE );
				getToken();
				if ( token.is( Scanner.TokenType.LCURLY ) ) { // We have sub-types to parse
					parseSubTypes( (TypeInlineDefinition)currentType );
				}
			}

			// Keep track of the root types to support them in successive type declarations
			definedTypes.put( typeName, currentType );
			program.addChild( currentType );
		}
	}

	private NativeType readNativeType()
	{
		if ( token.is( Scanner.TokenType.CAST_INT ) ) {
			return NativeType.INT;
		} else if ( token.is( Scanner.TokenType.CAST_DOUBLE ) ) {
			return NativeType.DOUBLE;
		} else if ( token.is( Scanner.TokenType.CAST_STRING ) ) {
			return NativeType.STRING;
		} else if ( token.is( Scanner.TokenType.CAST_LONG ) ) {
			return NativeType.LONG;
		} else if ( token.is( Scanner.TokenType.CAST_BOOL ) ) {
			return NativeType.BOOL;
		} else {
			return NativeType.fromString( token.content() );
		}
	}

	private void parseSubTypes( TypeInlineDefinition type )
		throws IOException, ParserException
	{
		eat( Scanner.TokenType.LCURLY, "expected {" );
		
		if ( token.is( Scanner.TokenType.QUESTION_MARK ) ) {
			type.setUntypedSubTypes( true );
			getToken();
		} else {
			TypeDefinition currentSubType;
			while( !token.is( Scanner.TokenType.RCURLY ) ) {
				currentSubType = parseSubType();
				if ( type.hasSubType( currentSubType.id() ) ) {
					throwException( "sub-type " + currentSubType.id() + " conflicts with another sub-type with the same name" );
				}
				type.putSubType( currentSubType );
			}
		}

		eat( Scanner.TokenType.RCURLY, "RCURLY expected" );
	}

	private TypeDefinition parseSubType()
		throws IOException, ParserException
	{
		eat( Scanner.TokenType.DOT, "sub-type syntax error (dot not found)" );

		// SubType id
		String id = token.content();
		eatIdentifier( "expected type name" );

		Range cardinality = parseCardinality();
		eat( Scanner.TokenType.COLON, "expected COLON" );

		NativeType nativeType = readNativeType();

		if ( nativeType == null ) { // It's a user-defined type
			TypeDefinitionLink linkedSubType = new TypeDefinitionLink( getContext(), id, cardinality, token.content() );
			getToken();
			return linkedSubType;
		} else {
			getToken();
			TypeInlineDefinition inlineSubType = new TypeInlineDefinition( getContext(), id, nativeType, cardinality );
			if ( token.is( Scanner.TokenType.LCURLY ) ) { // Has ulterior sub-types
				parseSubTypes( inlineSubType );
			}
			return inlineSubType;
		}
	}

	private Range parseCardinality()
		throws IOException, ParserException
	{
		int min = -1;
		int max = -1;

		if ( token.is( Scanner.TokenType.COLON ) ) { // Default (no cardinality specified)
			min = 1;
			max = 1;
		} else if ( token.is( Scanner.TokenType.QUESTION_MARK ) ) {
			min = 0;
			max = 1;
			getToken();
		} else if ( token.is( Scanner.TokenType.ASTERISK ) ) {
			min = 0;
			max = Integer.MAX_VALUE;
			getToken();
		} else if ( token.is( Scanner.TokenType.LSQUARE ) ) {
			getToken(); // eat [

			// Minimum
			assertToken( Scanner.TokenType.INT, "expected int value" );
			min = Integer.parseInt( token.content() );
			if ( min < 0 ) {
				throwException( "Minimum number of occurences of a sub-type must be positive or zero" );
			}

			getToken();
			eat( Scanner.TokenType.COMMA, "expected comma separator" );

			// Maximum
			if ( token.is( Scanner.TokenType.INT ) ) {
				max = Integer.parseInt( token.content() );
				if ( max < 1 ) {
					throwException( "Maximum number of occurences of a sub-type must be positive" );
				}
			} else if ( token.is( Scanner.TokenType.ASTERISK ) ) {
				max = Integer.MAX_VALUE;
			} else {
				throwException( "Maximum number of sub-type occurences not valid: " + token.content() );
			}

			getToken();
			eat( Scanner.TokenType.RSQUARE, "expected ]" );
		} else {
			throwException( "Sub-type cardinality syntax error" );
		}

		return new Range( min, max );
	}


	private void parseEmbedded()
		throws IOException, ParserException
	{
		if ( token.isKeyword( "embedded" ) ) {
			String servicePath, portId;
			getToken();
			eat( Scanner.TokenType.LCURLY, "expected {" );
			boolean keepRun = true;
			Constants.EmbeddedServiceType type;
			while ( keepRun ) {
				type = null;
				if ( token.isKeyword( "Java" ) ) {
					type = Constants.EmbeddedServiceType.JAVA;
				} else if ( token.isKeyword( "Jolie" ) ) {
					type = Constants.EmbeddedServiceType.JOLIE;
				} else if ( token.isKeyword( "JavaScript" ) ) {
					type = Constants.EmbeddedServiceType.JAVASCRIPT;
				}
				if ( type == null ) {
					keepRun = false;
				} else {
					getToken();
					eat( Scanner.TokenType.COLON, "expected : after embedded service type" );
					checkConstant();
					while ( token.is( Scanner.TokenType.STRING ) ) {
						servicePath = token.content();
						getToken();
						if ( token.isKeyword( "in" ) ) {
							eatKeyword( "in", "expected in" );
							assertToken( Scanner.TokenType.ID, "expected output port name" );
							portId = token.content();
							getToken();
						} else {
							portId = null;
						}
						program.addChild(
							new EmbeddedServiceNode(
							getContext(),
							type,
							servicePath,
							portId ) );
						if ( token.is( Scanner.TokenType.COMMA ) ) {
							getToken();
						} else {
							break;
						}
					}
				}
			}
			eat( Scanner.TokenType.RCURLY, "expected }" );
		}
	}

	private void parseCorrelationSets()
		throws IOException, ParserException
	{
		while( token.isKeyword( "cset" ) ) {
			getToken();
			/*assertToken( Scanner.TokenType.ID, "expected correlation set name" );
			String csetName = token.content();
			getToken();*/
			eat( Scanner.TokenType.LCURLY, "expected {" );
			List< CorrelationVariableInfo > variables = new ArrayList< CorrelationVariableInfo >();
			List< CorrelationAliasInfo > aliases;
			VariablePathNode correlationVariablePath;
			String typeName;
			while ( token.is( Scanner.TokenType.ID ) ) {
				aliases = new LinkedList< CorrelationAliasInfo >();
				correlationVariablePath = parseVariablePath();
				eat( Scanner.TokenType.COLON, "expected correlation variable alias list" );
				assertToken( Scanner.TokenType.ID, "expected correlation variable alias" );
				while ( token.is( Scanner.TokenType.ID ) ) {
					typeName = token.content();
					getToken();
					eat( Scanner.TokenType.DOT, "expected . after message type name in correlation alias" );
					aliases.add( new CorrelationAliasInfo( typeName, parseVariablePath() ) );
				}
				variables.add( new CorrelationVariableInfo( correlationVariablePath, aliases ) );
				if ( token.is( Scanner.TokenType.COMMA ) ) {
					getToken();
				} else {
					break;
				}
			}

			program.addChild( new CorrelationSetInfo( getContext(), variables ) );
			eat( Scanner.TokenType.RCURLY, "expected }" );
		}
	}

	private void parseExecution()
		throws IOException, ParserException
	{
		if ( token.is( Scanner.TokenType.EXECUTION ) ) {
			Constants.ExecutionMode mode = Constants.ExecutionMode.SEQUENTIAL;
			getToken();
			eat( Scanner.TokenType.LCURLY, "{ expected" );
			assertToken( Scanner.TokenType.ID, "expected execution modality" );
			if ( "sequential".equals( token.content() ) ) {
				mode = Constants.ExecutionMode.SEQUENTIAL;
			} else if ( "concurrent".equals( token.content() ) ) {
				mode = Constants.ExecutionMode.CONCURRENT;
			} else if ( "single".equals( token.content() ) ) {
				mode = Constants.ExecutionMode.SINGLE;
			} else {
				throwException( "Expected execution mode, found " + token.content() );
			}
			program.addChild( new ExecutionInfo( getContext(), mode ) );
			getToken();
			eat( Scanner.TokenType.RCURLY, "} expected" );
		}
	}

	private void parseConstants()
		throws IOException, ParserException
	{
		if ( token.is( Scanner.TokenType.CONSTANTS ) ) {
			getToken();
			eat( Scanner.TokenType.LCURLY, "expected {" );
			boolean keepRun = true;
			while ( token.is( Scanner.TokenType.ID ) && keepRun ) {
				String cId = token.content();
				getToken();
				eat( Scanner.TokenType.ASSIGN, "expected =" );
				if ( token.isValidConstant() == false ) {
					throwException( "expected string, integer, double or identifier constant" );
				}
				if ( constantsMap.containsKey( cId ) == false ) {
					constantsMap.put( cId, token );
				}
				getToken();
				if ( token.isNot( Scanner.TokenType.COMMA ) ) {
					keepRun = false;
				} else {
					getToken();
				}
			}
			eat( Scanner.TokenType.RCURLY, "expected }" );
		}
	}

	private static class IncludeFile {
		private final InputStream inputStream;
		private final String parentPath;
		private IncludeFile( InputStream inputStream, String parentPath )
		{
			this.inputStream = inputStream;
			this.parentPath = parentPath;
		}

		private InputStream getInputStream()
		{
			return inputStream;
		}

		private String getParentPath()
		{
			return parentPath;
		}
	}

	private static IncludeFile retrieveIncludeFile( final String path, final String filename )
	{
		IncludeFile ret = null;

		File f = new File(
				new StringBuilder()
					.append( path )
					.append( Constants.fileSeparator )
					.append( filename )
					.toString()
				);
		try {
			ret = new IncludeFile(
					new BufferedInputStream( new FileInputStream( f ) ),
					f.getParent()
				);
		} catch( FileNotFoundException e ) {
			try {
				String urlStr =
					new StringBuilder()
						.append( path )
						.append( Constants.fileSeparator )
						.append( filename )
						.toString();
				URL url = null;
				if ( urlStr.startsWith( "jap:" ) || urlStr.startsWith( "jar:" ) ) {
					/*
					 * We need the embedded URL path, otherwise URI.normalize
					 * is going to do nothing.
					 */
					url = new URL(
						urlStr.substring( 0,4 ) + new URI( urlStr.substring( 4 ) ).normalize().toString()
					);
				} else {
					url = new URL( new URI( urlStr ).normalize().toString() );
				}
				ret = new IncludeFile(
					url.openStream(),
					new File( url.toString() ).getParent()
					//path
				);
			} catch( MalformedURLException mue ) {
			} catch( IOException ioe ) {
			} catch( URISyntaxException use ) {}
		}
		return ret;
	}

	private void parseInclude()
		throws IOException, ParserException
	{
		String[] origIncludePaths;
		IncludeFile includeFile;
		while ( token.is( Scanner.TokenType.INCLUDE ) ) {
			getToken();
			Scanner oldScanner = scanner();
			assertToken( Scanner.TokenType.STRING, "expected filename to include" );
			String includeStr = token.content();
			includeFile = null;

			// Try the same directory of the program file first.
			if ( includePaths.length > 1 ) {
				includeFile = retrieveIncludeFile( includePaths[0], includeStr );
			}

			if ( includeFile == null ) {
				URL includeURL = classLoader.getResource( includeStr );
				if ( includeURL != null ) {
					includeFile = new IncludeFile( includeURL.openStream(), new File( includeURL.toString() ).getParent() );
				}
			}

			for ( int i = 1; i < includePaths.length && includeFile == null; i++ ) {
				includeFile = retrieveIncludeFile( includePaths[i], includeStr );
			}

			if ( includeFile == null ) {
				throwException( "File not found: " + includeStr );
			}

			origIncludePaths = includePaths;
			try {
				setScanner( new Scanner( includeFile.getInputStream(), new URI( includeFile.parentPath.toString() + includeStr ) ) );
{'fraction_non_alphanumeric': 0.11811795067864653, 'fraction_numerical': 0.0019594723762186963, 'mean_word_length': 3.4648458337778725, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '663694', 'n_tokens_mistral': 14565, 'n_tokens_neox': 12484, 'n_words': 2923}