/** STCompiler.m Bytecode compiler. Generates STExecutableCode from source code. Copyright (c) 2002 Free Software Foundation This file is part of the StepTalk project. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #import "STCompiler.h" #import "STMessage.h" #import "STLiterals.h" #import "STBytecodes.h" #import "STCompiledScript.h" #import "STCompiledCode.h" #import "STCompiledMethod.h" #import "STCompilerUtils.h" #import "STSourceReader.h" #import "Externs.h" #import #import #import #import #import #import #import #import #import #import extern int STCparse(void *context); /* FIXME: rewrite parser We need three kinds of grammars: 1. = | 2. = '[|' ']' = | '!' = | 3. = = | Parser 1. are 2. are for scripts. Parser 3. is for script object methods. Because majority of the grammar is reused in all three parsers we use only one grammar definition. There is no problem in haveng 1. and 2. in one file. To be able to have 3. in the same file we do a hack: we add a prefix '!!' for method source. Then we diferentiate it in grammar file by changin the rule: 3. = '!' '!' See STGrammar.y */ @interface STCompiler(STCompilerPrivate) - (void)compile; - (void)initializeContext; - (void)destroyCompilationContext; - (unsigned)tempVariableIndex:(NSString *)varName; - (unsigned)externalVariableIndex:(NSString *)varName; - (void) initializeCompilationContext; - (NSDictionary *)exceptionInfo; - (unsigned)addSelectorLiteral:(NSString*)selector; - (unsigned)addLiteral:literal; - (void)compileMethod:(STCMethod *)method; - (STCompiledCode *) compileStatements:(STCStatements *)statements; - (void)compileStatements:(STCStatements *)statements blockFlag:(BOOL)blockFlag; - (void)compilePrimary:(STCPrimary *)primary; - (void)compileExpression:(STCExpression *)expr; - (void)emitPushReceiverVariable:(unsigned)index; - (void)emitPushSelf; - (void)emitPopAndStoreReceiverVariable:(unsigned)index; - (void)emitReturn; - (void)emitReturnFromBlock; - (void)emitPushTemporary:(unsigned)index; - (void)emitPushLiteral:(unsigned)index; - (void)emitPushVariable:(unsigned)index; - (void)emitPopAndStoreTemporary:(unsigned)index; - (void)emitPopAndStoreVariable:(unsigned)index ; - (void)emitPopStack; - (void)emitSendSelector:(unsigned)index argCount:(unsigned)argCount; - (void)emitBlockCopy; - (void)emitDuplicateStackTop; - (void)emitJump:(short)offset; - (void)emitLongJump:(short)offset; - (void)emitPushNil; - (void)emitPushTrue; - (void)emitPushFalse; - (void)fixupLongJumpAt:(unsigned)index with:(short)offset; - (unsigned)currentBytecode; @end #define MAX(a,b) (((a)>(b))?(a):(b)) @implementation STCompiler + compilerWithEnvironment:(STEnvironment *)env { return AUTORELEASE([[self alloc] initWithEnvironment:env]); } - initWithEnvironment:(STEnvironment *)env { self = [self init]; [self setEnvironment:env]; return self; } - init { [super init]; arrayLiteralClass = [NSMutableArray class]; stringLiteralClass = [NSMutableString class]; /* bytesLiteralClass = [NSMutableData class]; */ intNumberLiteralClass = [NSNumber class]; realNumberLiteralClass = [NSNumber class]; symbolLiteralClass = [STSelector class]; characterLiteralClass = [NSString class]; return self; } - (void)dealloc { [super dealloc]; } - (BOOL)beginScript { if(isSingleMethod) { [NSException raise:@"STCompilerException" format:@"Script source given for single method"]; return NO; } else { return YES; } } /* --------------------------------------------------------------------------- * Compilation * --------------------------------------------------------------------------- */ - (void)setEnvironment:(STEnvironment *)env { ASSIGN(environment,env); } - (STCompiledMethod *)compileMethodFromSource:(NSString *)aString forReceiver:(id)receiver { STCompiledMethod *result; NSString *hackedSource; NSString *exceptionFmt = @"Syntax error at line %i near '%@', " @"reason: %@."; int parsRetval = 0; NSDebugLLog(@"STCompiler", @"Compile method", aString); if(!environment) { [NSException raise:STCompilerGenericException format:@"Compilation environment is not initialized"]; return nil; } hackedSource = [@"!!" stringByAppendingString:aString]; reader = [[STSourceReader alloc] initWithString:hackedSource]; receiverVars = [[NSArray alloc] initWithArray:[receiver instanceVariableNames]]; isSingleMethod = YES; STParserContextInit(&context,self,reader); NS_DURING { // extern int STCdebug; // STCdebug = 1; parsRetval = STCparse(&context); } NS_HANDLER { if ([[localException name] isEqualToString: STCompilerSyntaxException]) { NSString *tokenString; int line; tokenString = [reader tokenString]; line = [reader currentLine]; RELEASE(reader); RELEASE(receiverVars); receiverVars = nil; reader = nil; [NSException raise:STCompilerSyntaxException format:exceptionFmt, line, tokenString, [localException reason]]; } [localException raise]; } NS_ENDHANDLER RELEASE(receiverVars); RELEASE(reader); result = AUTORELEASE(resultMethod); resultMethod = nil; return result; } - (STCompiledScript *)compileString:(NSString *)aString { STCompiledScript *result; NSString *exceptionFmt = @"Syntax error at line %i near '%@', " @"reason: %@."; int parsRetval = 0; NSDebugLLog(@"STCompiler", @"Compile string", aString); isSingleMethod = NO; if(!environment) { [NSException raise:STCompilerGenericException format:@"Compilation environment is not initialized"]; return nil; } reader = [[STSourceReader alloc] initWithString:aString]; receiverVars = [[NSMutableArray alloc] init]; STParserContextInit(&context,self,reader); NS_DURING { // extern int STCdebug; // STCdebug = 1; parsRetval = STCparse(&context); } NS_HANDLER { if ([[localException name] isEqualToString: STCompilerSyntaxException]) { NSString *tokenString; int line; tokenString = [reader tokenString]; line = [reader currentLine]; RELEASE(reader); RELEASE(receiverVars); receiverVars = nil; reader = nil; [NSException raise:STCompilerSyntaxException format:exceptionFmt, line, tokenString, [localException reason]]; } [localException raise]; } NS_ENDHANDLER RELEASE(receiverVars); RELEASE(reader); result = AUTORELEASE(resultScript); resultScript = nil; return result; } - (void)compileMethod:(STCMethod *)method { STCompiledMethod *compiledMethod; STCompiledCode *code; STMessage *messagePattern; /* FIXME: unite STCMessage and STMessage */ messagePattern = (STMessage *)[method messagePattern]; if(!messagePattern) { messagePattern = [STMessage messageWithSelector:@"_unnamed_method" arguments:nil]; } NSDebugLLog(@"STCompiler", @"Compile method %@", [messagePattern selector]); tempVars = [NSMutableArray arrayWithArray:[messagePattern arguments]]; code = [self compileStatements:[method statements]]; compiledMethod = [STCompiledMethod methodWithCode:AUTORELEASE(code) messagePattern:messagePattern]; if(!isSingleMethod) { if(resultMethod) { [NSException raise:@"STCompilerException" format:@"Method is present when compiling a script"]; return; } if(!resultScript) { NSDebugLLog(@"STCompiler", @"Creating script with %i variables",[receiverVars count]); resultScript = [[STCompiledScript alloc] initWithVariableCount: [receiverVars count]]; } [resultScript addMethod:compiledMethod]; } else { if(resultMethod) { [NSException raise:@"STCompilerException" format:@"More than one method compiled for single method request"]; return; } if(resultScript) { [NSException raise:@"STCompilerException" format:@"Compiled script is present when compiling single method"]; return; } resultMethod = RETAIN(compiledMethod); } } - (STCompiledCode *)compileStatements:(STCStatements *)statements { STCompiledCode *compiledCode; #ifdef DEBUG int count; int i; #endif /* FIXME: create another class */ [self initializeCompilationContext]; NSDebugLLog(@"STCompiler", @"compiling statements"); tempsSize = tempsCount = [tempVars count]; [self compileStatements:statements blockFlag:NO]; NSDebugLLog(@"STCompiler", @" temporaries %i stack %i", tempsSize,stackSize); #ifdef DEBUG count = [literals count]; NSDebugLLog(@"STCompiler", @" literals count %i", count); for(i=0;i 0; index--) { [self emitPopAndStoreTemporary:tempsSave + index - 1]; } } array = [statements expressions]; if(array) { enumerator = [array objectEnumerator]; while((expr = [enumerator nextObject])) { [self compileExpression:expr]; } } expr = [statements returnExpression]; if(expr) { [self compileExpression:expr]; } if(blockFlag) { [blockInfo setStackSize:stackSize]; AUTORELEASE(blockInfo); stackSize = stackSizeSave; stackPos = stackPosSave; [self emitReturnFromBlock]; } else { [self emitReturn]; } /* fixup jump (if block) */ if(blockFlag) { [self fixupLongJumpAt:jumpIP with:[self currentBytecode] - jumpIP]; } /* cleanup unneeded temp variables */ [tempVars removeObjectsInRange:NSMakeRange(tempsSave, [tempVars count]-tempsSave)]; tempsCount = tempsSave; } - (void)compilePrimary:(STCPrimary *)primary { id object = [primary object]; int index; NSDebugLLog(@"STCompiler-misc",@" compile primary"); switch([primary type]) { case STCVariablePrimaryType: if([object isEqualToString:@"YES"] || [object isEqualToString:@"true"]) { [self emitPushTrue]; } else if([object isEqualToString:@"NO"] || [object isEqualToString:@"false"]) { [self emitPushFalse]; } else if([object isEqualToString:@"nil"]) { [self emitPushNil]; } else { index = [self tempVariableIndex:object]; if(index != NSNotFound) { [self emitPushTemporary:index]; break; } else if( (index = [self receiverVariableIndex:object])!= NSNotFound) { [self emitPushReceiverVariable:index]; break; } else if( [object isEqual:@"self"] ) { [self emitPushSelf]; break; } index = [self externalVariableIndex:object]; [self emitPushVariable:index]; } break; case STCLiteralPrimaryType: index = [self addLiteral:object]; [self emitPushLiteral:index]; break; case STCBlockPrimaryType: [self compileStatements:object blockFlag:YES]; break; case STCExpressionPrimaryType: [self compileExpression:object]; break; default: [NSException raise:STCompilerInconsistencyException format:@"Unknown primary type %i", [primary type]]; break; } } - (void)compileMessage:(STCMessage *)message { NSEnumerator *enumerator; NSArray *args; id obj; int index; args = [message arguments]; if(args && ([args count]>0)) { enumerator = [args objectEnumerator]; while((obj = [enumerator nextObject])) { if([obj isKindOfClass:[STCPrimary class]]) [self compilePrimary:obj]; else [self compileExpression:obj]; } } index = [self addSelectorLiteral:[message selector]]; [self emitSendSelector:index argCount:[args count]]; } - (void)compileExpression:(STCExpression *)expr { NSEnumerator *enumerator; NSArray *cascade; NSString *varName; NSArray *array; unsigned count; unsigned index,i; id obj; NSDebugLLog(@"STCompiler-misc",@" compile expression"); if([expr isPrimary]) { [self compilePrimary:[expr object]]; } else /* message expression */ { obj = [expr target]; /* target */ if([obj isKindOfClass:[STCPrimary class]]) [self compilePrimary:obj]; else [self compileExpression:obj]; cascade = [expr cascade]; if(cascade) { count = [cascade count]; for(i=0;i0) { for(i = 0; i