import * as Types from '@pascal-psi/data-types' ; import * as AST from '@pascal-psi/ast' ; import { SymbolScope , BaseSymbolScope , LocalSymbolScope , SymbolScopeType , } from '@pascal-psi/symbol' ; import { IntegerConstantAST } from '@pascal-psi/ast' ; import PSIError from '@pascal-psi/error' ; export class Interpreter extends AST . ASTVisitor < Types . PSIDataType > { public scope : SymbolScope ; constructor ( protected readonly ast , baseScope ) { super ( ) ; this . scope = baseScope ; } private withNewScope < T > ( name , fn ) { const scope = ( this . scope = this . scope . children . get ( name ) ! ) ; const result = fn ( scope ) ; this . scope = this . scope . getParent ( ) ! ; return result ; } public visitAssignment ( node ) { const left = node . left ; const newValue = this . visit ( node . right ) ; if ( left instanceof AST . VariableAST ) { if ( this . scope . type === SymbolScopeType . Function && left . name === this . scope . name ) { this . scope . changeFunctionReturnType ( left . name , newValue ) ; } else this . scope . changeValue ( left . name , newValue ) ; } else if ( left instanceof AST . ArrayAccessAST ) { this . scope . changeArrayValue ( left . array . name , left . accessors . map ( accessor => this . visit ( accessor ) ) , newValue ) ; } return new Types . PSIVoid ( ) ; } public visitPlus ( node ) { return this . visit ( node . left ) . add ( this . visit ( node . right ) ) ; } public visitMinus ( node ) { return this . visit ( node . left ) . subtract ( this . visit ( node . right ) ) ; } public visitIntegerDivision ( node ) { return this . visit ( node . left ) . integerDivide ( this . visit ( node . right ) ) ; } public visitRealDivision ( node ) { return this . visit ( node . left ) . divide ( this . visit ( node . right ) ) ; } public visitMultiplication ( node ) { return this . visit ( node . left ) . multiply ( this . visit ( node . right ) ) ; } public visitMod ( node ) { return this . visit ( node . left ) . mod ( this . visit ( node . right ) ) ; } public visitEquals ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . equals ( this . visit ( node . right ) ) ) ; } public visitNotEquals ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . notEquals ( this . visit ( node . right ) ) ) ; } public visitGreaterThan ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . greaterThan ( this . visit ( node . right ) ) ) ; } public visitLessThan ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . lessThan ( this . visit ( node . right ) ) ) ; } public visitGreaterEquals ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . greaterEqualsThan ( this . visit ( node . right ) ) ) ; } public visitLessEquals ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . lessEqualsThan ( this . visit ( node . right ) ) ) ; } public visitIntegerConstant ( node ) { return node . value ; } public visitRealConstant ( node ) { return node . value ; } public visitCharConstant ( node ) { return node . value ; } public visitTrue ( node ) { return new Types . PSIBoolean ( true ) ; } public visitFalse ( node ) { return new Types . PSIBoolean ( false ) ; } public visitUnaryPlus ( node ) { return this . visit ( node . target ) . unaryPlus ( ) ; } public visitUnaryMinus ( node ) { return this . visit ( node . target ) . unaryMinus ( ) ; } public visitCompound ( node ) { node . children . forEach ( this . visit . bind ( this ) ) ; return new Types . PSIVoid ( ) ; } public visitVariable ( node ) { const variableValue = this . scope . resolveValue ( node . name ) ; if ( ! variableValue ) { throw new PSIError ( node , `Variable '${ node . name }' used without first being initialized` ) ; } return variableValue ; } public visitEmpty ( node ) { return new Types . PSIVoid ( ) ; } public visitProgram ( node ) { return this . withNewScope ( node . name , ( ) => { return this . visit ( node . block ) ; } ) ; } public visitBlock ( node ) { node . declarations . forEach ( this . visit . bind ( this ) ) ; this . visit ( node . compoundStatement ) ; return new Types . PSIVoid ( ) ; } public visitVariableDeclaration ( node ) { return new Types . PSIVoid ( ) ; } public visitReal ( node ) { return new Types . PSIRealType ( ) ; } public visitInteger ( node ) { return new Types . PSIIntegerType ( ) ; } public visitBoolean ( node ) { return new Types . PSIBooleanType ( ) ; } public visitChar ( node ) { return new Types . PSICharType ( ) ; } public visitProcedureDeclaration ( node ) { this . scope . changeValue ( node . name , new Types . PSIProcedure ( args => { this . withNewScope ( node . name , scope => { node . args . map ( arg => arg . variable . name ) . forEach ( ( argName , i ) => { scope . changeValue ( argName , args [ i ] ) ; } ) ; this . visit ( node . block ) ; } ) ; } ) ) ; return new Types . PSIVoid ( ) ; } public visitIf ( node ) { const condition = this . visit ( node . condition ) ; if ( condition . equals ( Types . PSIBoolean . true ) ) { this . visit ( node . statement ) ; } else { if ( node . next ) { this . visit ( node . next ) ; } } return new Types . PSIVoid ( ) ; } public visitAnd ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . equals ( Types . PSIBoolean . true ) && this . visit ( node . right ) . equals ( Types . PSIBoolean . true ) ) ; } public visitOr ( node ) { return new Types . PSIBoolean ( this . visit ( node . left ) . equals ( Types . PSIBoolean . true ) || this . visit ( node . right ) . equals ( Types . PSIBoolean . true ) ) ; } public visitNot ( node ) { const target = this . visit ( node . target ) ; return target . equals ( Types . PSIBoolean . true ) ? Types . PSIBoolean . false : Types . PSIBoolean . true ; } public visitCall ( node ) { const args = node . args . map ( arg => this . visit ( arg ) ) ; const procedureOrFunction = this . scope . resolveValue < Types . PSIProcedure | Types . PSIFunction > ( node . name ) ! ; procedureOrFunction . call ( args ) ; if ( procedureOrFunction instanceof Types . PSIFunction ) { const returnValue = this . scope . resolveValue < Types . PSIFunction > ( node . name ) ! . returnValue ; if ( ! returnValue ) { throw new PSIError ( node , 'Function does not return any value' ) ; } return returnValue ; } else { return new Types . PSIVoid ( ) ; } } public visitFor ( node ) { const variable = node . assignment . left ; this . visitAssignment ( node . assignment ) ; this . visit ( node . statement ) ; if ( node . increment ) { while ( this . visit ( variable ) . lessThan ( this . visit ( node . finalValue ) ) ) { this . visit ( new AST . AssignmentAST ( variable , new AST . PlusAST ( variable , new IntegerConstantAST ( new Types . PSIInteger ( 1 ) ) ) ) ) ; this . visit ( node . statement ) ; } } else { while ( this . visit ( variable ) . greaterThan ( this . visit ( node . finalValue ) ) ) { this . visit ( new AST . AssignmentAST ( variable , new AST . MinusAST ( variable , new IntegerConstantAST ( new Types . PSIInteger ( 1 ) ) ) ) ) ; this . visit ( node . statement ) ; } } return new Types . PSIVoid ( ) ; } public visitWhile ( node ) { while ( this . visit ( node . condition ) . equals ( Types . PSIBoolean . true ) ) { this . visit ( node . statement ) ; } return new Types . PSIVoid ( ) ; } public visitRepeat ( node ) { do { node . statements . forEach ( this . visit . bind ( this ) ) ; } while ( this . visit ( node . condition ) . equals ( Types . PSIBoolean . false ) ) ; return new Types . PSIVoid ( ) ; } public visitSubrange ( node ) { return new ( Types . createPSISubrange ( this . visitConstant ( node . left ) , this . visitConstant ( node . right ) ) ) ( ) ; } public visitArray ( node ) { return new ( Types . createPSIArray ( node . indexTypes . map ( node => ( this . visit ( node ) . constructor as unknown ) as typeof Types . PSIType ) , ( this . visit ( node . componentType ) . constructor as unknown ) as typeof Types . PSIDataType ) ) ( ) ; } public visitArrayAccess ( node ) { const variableValue = this . scope . resolveValue ( node . array . name ) ; if ( ! variableValue ) { throw new PSIError ( node , `Program error: Array accessed without being initialized` ) ; } if ( ! Types . isPSIArray ( variableValue ) ) { throw new PSIError ( node , `Program error: Expected array access variable to be array` ) ; } const value = variableValue . getValue ( node . accessors . map ( node => this . visit ( node ) ) ) ; if ( ! value ) { throw new PSIError ( node , `Array '${ node . array . name }' index used without first being initialized` ) ; } return value ! ; } public visitFunctionDeclaration ( node ) { this . scope . changeValue ( node . name , new Types . PSIFunction ( args => { this . withNewScope ( node . name , scope => { node . args . map ( arg => arg . variable . name ) . forEach ( ( argName , i ) => { scope . changeValue ( argName , args [ i ] ) ; } ) ; this . visit ( node . block ) ; } ) ; } ) ) ; return new Types . PSIFunctionType ( ) ; } }