/* * Copyright 2013 Guidewire Software, Inc. */ package gw.lang.shell; import gw.config.CommonServices; import gw.lang.GosuShop; import gw.lang.parser.ExternalSymbolMapSymbolTableWrapper; import gw.lang.parser.GosuParserFactory; import gw.lang.parser.IDynamicFunctionSymbol; import gw.lang.parser.IExpression; import gw.lang.parser.IGosuParser; import gw.lang.parser.IGosuProgramParser; import gw.lang.parser.IParseResult; import gw.lang.parser.IParseTree; import gw.lang.parser.IParsedElement; import gw.lang.parser.ISourceCodeTokenizer; import gw.lang.parser.ISymbol; import gw.lang.parser.ISymbolTable; import gw.lang.parser.ITypeUsesMap; import gw.lang.parser.ParserOptions; import gw.lang.parser.StandardSymbolTable; import gw.lang.parser.exceptions.ParseResultsException; import gw.lang.parser.expressions.IIdentifierExpression; import gw.lang.parser.expressions.IMemberAccessExpression; import gw.lang.parser.expressions.INotAWordExpression; import gw.lang.parser.expressions.IVarStatement; import gw.lang.parser.statements.IStatementList; import gw.lang.parser.statements.IUsesStatement; import gw.lang.reflect.IMethodInfo; import gw.lang.reflect.INamespaceType; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IType; import gw.lang.reflect.ITypeInfo; import gw.lang.reflect.gs.IGosuProgram; import gw.lang.reflect.gs.IProgramInstance; import gw.lang.reflect.java.JavaTypes; import gw.util.GosuExceptionUtil; import gw.util.GosuStringUtil; import jline.CandidateListCompletionHandler; import jline.Completor; import jline.ConsoleReader; import jline.Terminal; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; class InteractiveShell implements Runnable { private static final String GOSU_PROMPT = "gs> "; private static final String MORE_PROMPT = "... "; private static final String OUTPUT_PREFIX = " = "; private ISymbolTable _interactiveSymbolTable; private ConsoleReader _cr; private CompletionHandler _completionHandler; private ITypeUsesMap _interactiveTypeUsesMap; private boolean _logOutput; public InteractiveShell() { this( false ); } public InteractiveShell( boolean logOutput ) { _interactiveTypeUsesMap = CommonServices.getGosuIndustrialPark().createTypeUsesMap( Collections.<String>emptyList() ); _interactiveSymbolTable = new StandardSymbolTable( true ); _completionHandler = new CompletionHandler( _interactiveSymbolTable, _interactiveTypeUsesMap ); _logOutput = logOutput; } public void run() { try { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { System.out.println(); System.out.flush(); } }); Terminal.setupTerminal(); _cr = new ConsoleReader(); _cr.setDefaultPrompt( GOSU_PROMPT ); _cr.setCompletionHandler( new CandidateListCompletionHandler() ); _cr.addCompletor( _completionHandler ); _cr.printString( "Type \"help\" to see available commands" ); _cr.printNewline(); //noinspection InfiniteLoopStatement while( true ) { String expr = readExpr( _cr ); if (expr == null) { return; } Boolean result = tryAsCommand(expr); if ( result == null ) { System.exit( 0 ); } if( result ) { continue; } try { if( !GosuStringUtil.isEmpty( expr ) ) { interactivelyEvaluate( expr ); } } catch( ParseResultsException e ) { _cr.printString( e.getMessage() ); } catch( Throwable e ) { _cr.printString( e.getMessage() == null ? "" : e.getMessage() ); _cr.printNewline(); StringWriter str = new StringWriter(); e.printStackTrace( new PrintWriter( str ) ); _cr.printString( str.toString() ); } } } catch( Exception e ) { throw GosuExceptionUtil.forceThrow( e ); } } protected Object interactivelyEvaluate( String expr ) throws ParseResultsException, IOException { IGosuProgram gosuProgram = parseProgram( expr ); IProgramInstance instance = gosuProgram.getProgramInstance(); Object val = instance.evaluate( new ExternalSymbolMapSymbolTableWrapper( _interactiveSymbolTable, true ) ); processProgram( gosuProgram, instance ); IExpression expression = gosuProgram.getExpression(); boolean noReturnValue = expression == null || JavaTypes.pVOID().equals(expression.getType()) || expression instanceof INotAWordExpression; if (!noReturnValue && _logOutput ) { _cr.printString( OUTPUT_PREFIX + GosuShop.toString( val ) ); _cr.printNewline(); } return val; } private static class CompletionHandler implements Completor { private ISymbolTable _symbolTable; private ITypeUsesMap _typeUsesMap; public CompletionHandler( ISymbolTable symbolTable, ITypeUsesMap typeUsesMap ) { _symbolTable = symbolTable; _typeUsesMap = typeUsesMap; } public int complete( String buffer, int cursor, List candidates ) { IGosuParser parser = GosuParserFactory.createParser( buffer, _symbolTable ); parser.setTypeUsesMap( _typeUsesMap ); IParsedElement pe; try { pe = parser.parseExpOrProgram( null ); } catch( ParseResultsException e ) { pe = e.getParsedElement(); } IParseTree parseTree = pe.getLocation().getDeepestLocation( cursor, true ); if( parseTree == null ) { return 0; } IParsedElement element = parseTree.getParsedElement(); if( element instanceof IIdentifierExpression) { return handleIdentifier( candidates, element ); } if( element instanceof IMemberAccessExpression) { IMemberAccessExpression ma = (IMemberAccessExpression)element; String memberName = ma.getMemberName(); IType type = ma.getRootType(); if( type instanceof INamespaceType ) { //need to implement } else { ITypeInfo typeInfo = type.getTypeInfo(); ArrayList<String> featureNames = new ArrayList<String>(); List<? extends IPropertyInfo> propertyInfos = typeInfo.getProperties(); for( IPropertyInfo propertyInfo : propertyInfos ) { if( propertyInfo.getName().startsWith( memberName ) ) { featureNames.add( propertyInfo.getName() ); } } List<? extends IMethodInfo> methodInfos = typeInfo.getMethods(); for( IMethodInfo mi : methodInfos ) { if( mi.getName().startsWith( memberName ) ) { featureNames.add( mi.getName() ); } } Collections.sort( featureNames ); //noinspection unchecked candidates.addAll( featureNames ); int ext = ma.getRootExpression().getLocation().getExtent(); return buffer.indexOf( '.', ext ) + 1; } } return 0; } private int handleIdentifier( List candidates, IParsedElement element ) { IIdentifierExpression identifier = (IIdentifierExpression)element; String prefix = identifier.getSymbol().getName(); ArrayList<String> symNames = new ArrayList<String>(); for( Object symbolName : _symbolTable.getSymbols().keySet() ) { symNames.add( symbolName.toString() ); } Collections.sort( symNames ); for( String name : symNames ) { if( name.startsWith( prefix ) ) { //noinspection unchecked candidates.add( name ); } } return identifier.getLocation().getColumn() - 1; } } private IGosuProgram parseProgram( String expr ) throws ParseResultsException { IGosuProgramParser programParser = GosuParserFactory.createProgramParser(); ParserOptions parserOptions = new ParserOptions(); parserOptions.withTypeUsesMap( _interactiveTypeUsesMap ); IParseResult result = programParser.parseExpressionOrProgram( expr, _interactiveSymbolTable, parserOptions ); return result.getProgram(); } private Boolean tryAsCommand( String expr ) throws IOException { expr = expr.trim(); if( "help".equals( expr ) || "\"help\"".equals( expr ) ) { printCommandHelp(); return true; } if( "ls".equals( expr ) ) { StandardSymbolTable defaultSymbols = new StandardSymbolTable(true); @SuppressWarnings({"unchecked"}) ArrayList<ISymbol> symbols = new ArrayList<ISymbol>( _interactiveSymbolTable.getSymbols().values() ); Collections.sort( symbols, new Comparator<ISymbol>() { public int compare( ISymbol o1, ISymbol o2 ) { return o1.getName().compareTo( o2.getName() ); } } ); _cr.printString( "Symbols : \n\n" ); for( ISymbol symbol : symbols ) { if ( defaultSymbols.getSymbol( symbol.getName() ) != null ) { _cr.printString( " " + symbol.getName() + " : (builtin)\n" ); } else { _cr.printString( " " + symbol.getName() + " : " + symbol.getType() + " = " + GosuShop.toString( symbol.getValue() ) + "\n" ); } } _cr.printNewline(); return true; } if( "exit".equals( expr ) || "quit".equals( expr ) ) { return null; } if( "clear".equals( expr ) ) { _interactiveSymbolTable = new StandardSymbolTable( true ); return true; } if( expr.startsWith( "rm " ) ) { String sym = expr.substring( "rm ".length() ); if ( _interactiveSymbolTable.getSymbol( sym ) == null ) { _cr.printString( "Symbol '" + sym + "' does not exist\n" ); } else { try { _interactiveSymbolTable.removeSymbol( sym ); } catch ( Exception ex ) { _cr.printString( "Cannot remove built-in symbol '" + sym + "'\n" ); } } return true; } return false; } private void processProgram( IGosuProgram program, IProgramInstance instance ) throws IOException { maybeHandleVar( program, instance ); maybeHandleFunction( program ); maybeHandleUses( program ); } private void maybeHandleUses( IGosuProgram program ) { if( program.getTypeUsesMap() != null && program.getTypeUsesMap().getUsesStatements() != null ) { for( IUsesStatement usesStmt : program.getTypeUsesMap().getUsesStatements() ) { _interactiveTypeUsesMap.addToTypeUses( usesStmt ); } } } private void maybeHandleFunction( IGosuProgram program ) { if( program.getMemberFunctions() != null ) { for( IDynamicFunctionSymbol function : program.getMemberFunctions() ) { if( function.getArgTypes().length != 1 || !JavaTypes.IEXTERNAL_SYMBOL_MAP().equals( function.getArgs().get( 0 ) ) || (!function.getDisplayName().equals( "evaluate" ) && !function.getDisplayName().equals( "evaluateRootExpr" )) ) { _interactiveSymbolTable.putSymbol( function ); } } } } private void maybeHandleVar( IGosuProgram program, IProgramInstance instance ) { if( program.getStatement() instanceof IStatementList ) { IStatementList statementList = (IStatementList)program.getStatement(); if( statementList.getStatements() != null && statementList.getStatements().length == 2 && statementList.getStatements()[0] instanceof IVarStatement ) { IVarStatement var = (IVarStatement)statementList.getStatements()[0]; _interactiveSymbolTable.putSymbol( CommonServices.getGosuIndustrialPark().createSymbol( var.getIdentifierName(), var.getType(), getValue( instance, var ) ) ); } } } private Object getValue( IProgramInstance instance, IVarStatement var ) { Object value = null; try { Field field = instance.getClass().getDeclaredField( var.getIdentifierName().toString() ); field.setAccessible( true ); value = field.get( instance ); } catch( Exception e ) { //ignore } return value; } private String readExpr( ConsoleReader cr ) throws IOException { String s = cr.readLine(); if ( s == null ) { return null; } while( eatMore( s ) ) { cr.setDefaultPrompt( MORE_PROMPT ); String additionalInput = cr.readLine(); if ( additionalInput.trim().length() == 0 ) { break; } s = s + additionalInput + "\n"; } cr.setDefaultPrompt( GOSU_PROMPT ); return s; } private boolean eatMore( String s ) { ISourceCodeTokenizer tokenizer = CommonServices.getGosuIndustrialPark().createSourceCodeTokenizer(s); int openParens = tokenizer.countMatches("("); int closedParens = tokenizer.countMatches(")"); if (openParens != closedParens) { return true; } int openBraces = tokenizer.countMatches("{"); int closeBraces = tokenizer.countMatches("}"); return openBraces != closeBraces; } private void printCommandHelp() throws IOException { _cr.printString( "The following commands are available:\n" + "\n" + " help - show this message\n" + " exit - exit this interpreter\n" + " quit - exit this interpreter\n" + " ls - lists all the local variables\n" + " clear - clears all local variables and functions\n" + " rm [var_name] - clears the given variable\n" ); _cr.printNewline(); } public int complete( String s, List completions ) { return complete( s, s.length() - 1, completions ); } public int complete( String s, int pos, List completions ) { return _completionHandler.complete( s, pos, completions ); } }