From bed5a47d5536504b35985e2c080e791fff4544fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Anh=20Khoa?= Date: Tue, 4 Dec 2018 02:05:06 +0700 Subject: [PATCH] init --- .gitignore | 611 +++++++++++++++++++++++++ README.md | 36 ++ astgen/ASTGeneration.py | 402 +++++++++++++++++ astgen/__init__.py | 0 checker/StaticCheck.py | 778 ++++++++++++++++++++++++++++++++ checker/StaticError.py | 125 ++++++ checker/__init__.py | 0 codegen/CodeGenError.py | 16 + codegen/CodeGenerator.py | 940 +++++++++++++++++++++++++++++++++++++++ codegen/Emitter.py | 784 ++++++++++++++++++++++++++++++++ codegen/Frame.py | 206 +++++++++ codegen/MachineCode.py | 776 ++++++++++++++++++++++++++++++++ codegen/__init__.py | 0 genANTLR4.py | 37 ++ libs/io.class | Bin 0 -> 2731 bytes libs/io.java | 156 +++++++ mpc.py | 112 +++++ parser/MP.g4 | 588 ++++++++++++++++++++++++ parser/__init__.py | 0 parser/lexererr.py | 14 + utils/AST.py | 534 ++++++++++++++++++++++ utils/Utils.py | 7 + utils/Visitor.py | 199 +++++++++ utils/__init__.py | 0 24 files changed, 6321 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 astgen/ASTGeneration.py create mode 100644 astgen/__init__.py create mode 100644 checker/StaticCheck.py create mode 100644 checker/StaticError.py create mode 100644 checker/__init__.py create mode 100644 codegen/CodeGenError.py create mode 100644 codegen/CodeGenerator.py create mode 100644 codegen/Emitter.py create mode 100644 codegen/Frame.py create mode 100644 codegen/MachineCode.py create mode 100644 codegen/__init__.py create mode 100644 genANTLR4.py create mode 100644 libs/io.class create mode 100644 libs/io.java create mode 100644 mpc.py create mode 100644 parser/MP.g4 create mode 100644 parser/__init__.py create mode 100644 parser/lexererr.py create mode 100644 utils/AST.py create mode 100644 utils/Utils.py create mode 100644 utils/Visitor.py create mode 100644 utils/__init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0df1938 --- /dev/null +++ b/.gitignore @@ -0,0 +1,611 @@ + +# Created by https://www.gitignore.io/api/vim,python,eclipse,netbeans,sublimetext,visualstudio,visualstudiocode +# Edit at https://www.gitignore.io/?templates=vim,python,eclipse,netbeans,sublimetext,visualstudio,visualstudiocode + +### Eclipse ### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### NetBeans ### +**/nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### Python Patch ### +.venv/ + +### SublimeText ### +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp_proj +*_wpftmp.csproj +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ +# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true +**/wwwroot/lib/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# End of https://www.gitignore.io/api/vim,python,eclipse,netbeans,sublimetext,visualstudio,visualstudiocode + + +**/.DS_Store +tests/ +**/*.jar +**/*.mp +parser/.antlr4 +parser/*.tokens +parser/*.interp +parser/MPLexer.py +parser/MPParser.py +parser/MPVisitor.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0f79ce --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# MP Compiler + +From my Principle of Programming Languages assignment, I have created a compiler for the MP language. The assignment phase is divided to 4 phases, from doing Lexer, Parser, AST generation to Static Checker and Jasmin Code generation. + +The assignment code structure is quite ugle, so I re-organized the code, adding some more steps to make the code look nicer and compile a \*.mp file to a jar file. + +Given the mp file as follows: + +```mp +// hello.mp +procedure main(); +begin + putString("Hello World"); +end +``` + +Compile and run the file by issuing these commands: + +```shell +python mpc.py hello.mp +java -jar hello.jar +``` + +More documentation is being built. + + +## Notes + +Because I was having serious deadlines at the end of the semester, I drop on working on ArrayCell, which will be added later. + +Because the lexer and parser are given by the famous `ANTLR4` engine, there should exists a path to antlr4.jar on the environment variable `ANTLR_LIB` or else, the program will use the antlr4 file in the external folder. + +Before running `mpc.py`, you must be sure that you have generate neccessary files from ANTLR4. +```shell +python genANTRL4.py +``` diff --git a/astgen/ASTGeneration.py b/astgen/ASTGeneration.py new file mode 100644 index 0000000..a117679 --- /dev/null +++ b/astgen/ASTGeneration.py @@ -0,0 +1,402 @@ +from parser.MPVisitor import MPVisitor +from parser.MPParser import MPParser +from functools import reduce + +# * is not a good use case +from utils.AST import ( + IntType, + FloatType, + BoolType, + StringType, + ArrayType, + # VoidType, + Program, + # Decl, + VarDecl, + FuncDecl, + # Stmt, + Assign, + If, + While, + For, + Break, + Continue, + Return, + With, + CallStmt, + # Expr, + BinaryOp, + UnaryOp, + CallExpr, + # LHS, + Id, + ArrayCell, + # Literal, + IntLiteral, + FloatLiteral, + StringLiteral, + BooleanLiteral +) + + +def flatten(listOflist): + return reduce(lambda x, item: x + item, listOflist, []) + + +class ASTGeneration(MPVisitor): + def visitProgram(self, ctx: MPParser.ProgramContext): + """ + return Program(list of Decl) + where Decl: + + VarDecl ==> var_decl + + FuncDecl ==> func_decl + + ProcDecl ==> proc_decl + """ + return Program(self.visit(ctx.manydecl())) + + def visitManydecl(self, ctx: MPParser.ManydeclContext): + """ + return list of decl expanded + """ + decl = self.visit(ctx.decl()) + if ctx.manydecl(): + return decl + self.visit(ctx.manydecl()) + else: + return decl + + def visitDecl(self, ctx: MPParser.DeclContext): + """ + return either + + var_decl + + func_decl + + proc_decl + """ + decl = self.visit(ctx.getChild(0)) + if ctx.var_decl(): + return decl + return [decl] + + def visitVar_decl(self, ctx: MPParser.Var_declContext): + """ + return varlist + """ + return self.visit(ctx.varlist()) + + def visitVarlist(self, ctx: MPParser.VarlistContext): + """ + return list of VarDecl(iden, mptype) + """ + var = self.visit(ctx.var()) + if ctx.varlist(): + return var + self.visit(ctx.varlist()) + else: + return var + + def visitVar(self, ctx: MPParser.VarContext): + """ + return list of VarDecl(iden, mptype) + """ + mptype = self.visit(ctx.mptype()) + idenlist = self.visit(ctx.idenlist()) + + # apply VarDecl(x, mptype) to idenlist where x is item in idenlist + def compose(f, arg): + def h(x): + return f(x, arg) + return h + hoo = compose(lambda x, y: VarDecl(x, y), mptype) + return list(map(hoo, idenlist)) + + def visitIdenlist(self, ctx: MPParser.IdenlistContext): + """ + return list of iden + """ + ident = Id(ctx.IDENT().getText()) + if ctx.idenlist(): + return [ident] + self.visit(ctx.idenlist()) + else: + return [ident] + + def visitMptype(self, ctx: MPParser.MptypeContext): + return self.visit(ctx.getChild(0)) + + def visitPrimitive_type(self, ctx: MPParser.Primitive_typeContext): + if ctx.INTEGER(): + return IntType() + elif ctx.BOOLEAN(): + return BoolType() + elif ctx.REAL(): + return FloatType() + elif ctx.STRING(): + return StringType() + + def visitCompound_type(self, ctx: MPParser.Compound_typeContext): + """ + return ArrayType(low, high, type) + """ + low, high = self.visit(ctx.array_value()) + pri_type = self.visit(ctx.primitive_type()) + return ArrayType(low, high, pri_type) + + def visitArray_value(self, ctx: MPParser.Array_valueContext): + """ + return low, high + """ + low = int(ctx.NUM_INT(0).getText()) + high = int(ctx.NUM_INT(1).getText()) + sub = len(ctx.MINUS()) + if sub == 0: + pass + elif sub == 2: + low = -low + high = -high + elif ctx.getChild(1).getText() == '-': + low = -low + else: + high = -high + return low, high + + def visitFunc_decl(self, ctx: MPParser.Func_declContext): + ident = Id(ctx.IDENT().getText()) + param_list = self.visit(ctx.param_list()) if ctx.param_list() else [] + mptype = self.visit(ctx.mptype()) + var_decl = flatten(list(map(self.visit, ctx.var_decl()))) + compound_statement = self.visit(ctx.compound_statement()) + return FuncDecl( + ident, + param_list, + var_decl, + compound_statement, + mptype + ) + + def visitParam_list(self, ctx: MPParser.Param_listContext): + """ + return list of VarDecl(iden, mptype) + """ + var = self.visit(ctx.var()) + # var is a list of VarDecl + if ctx.param_list(): + # concat + return var + self.visit(ctx.param_list()) + else: + # plain list return + return var + return + + def visitProc_decl(self, ctx: MPParser.Proc_declContext): + ident = Id(ctx.IDENT().getText()) + param_list = self.visit(ctx.param_list()) if ctx.param_list() else [] + var_decl = flatten(list(map(self.visit, ctx.var_decl()))) + compound_statement = self.visit(ctx.compound_statement()) + return FuncDecl( + ident, + param_list, + var_decl, + compound_statement + ) + + def visitExpression(self, ctx: MPParser.ExpressionContext): + if ctx.getChildCount() == 1: + return self.visit(ctx.expression_lv1()) + if ctx.AND(): + op = "andthen" + else: + op = "orelse" + return BinaryOp( + op, + self.visit(ctx.expression()), + self.visit(ctx.expression_lv1()) + ) + + def visitExpression_lv1(self, ctx: MPParser.Expression_lv1Context): + if ctx.getChildCount() == 1: + return self.visit(ctx.expression_lv2(0)) + return BinaryOp( + ctx.getChild(1).getText(), + self.visit(ctx.expression_lv2(0)), + self.visit(ctx.expression_lv2(1)) + ) + + def visitExpression_lv2(self, ctx: MPParser.Expression_lv2Context): + if ctx.getChildCount() == 1: + return self.visit(ctx.expression_lv3()) + return BinaryOp( + ctx.getChild(1).getText(), + self.visit(ctx.expression_lv2()), + self.visit(ctx.expression_lv3()) + ) + + def visitExpression_lv3(self, ctx: MPParser.Expression_lv3Context): + if ctx.getChildCount() == 1: + return self.visit(ctx.expression_lv4()) + return BinaryOp( + ctx.getChild(1).getText(), + self.visit(ctx.expression_lv3()), + self.visit(ctx.expression_lv4()) + ) + + def visitExpression_lv4(self, ctx: MPParser.Expression_lv4Context): + if ctx.getChildCount() == 1: + return self.visit(ctx.index_expression()) + return UnaryOp( + ctx.getChild(0).getText(), + self.visit(ctx.expression_lv4()) + ) + + def visitIndex_expression(self, ctx: MPParser.Index_expressionContext): + if ctx.getChildCount() == 1: + return self.visit(ctx.factor()) + return ArrayCell( + self.visit(ctx.index_expression()), + self.visit(ctx.expression()) + ) + + def visitInvocation_expression( + self, ctx: MPParser.Invocation_expressionContext): + if ctx.call_param(): + return self.visit(ctx.call_param()) + return [] + + def visitFactor(self, ctx: MPParser.FactorContext): + if ctx.expression(): + return self.visit(ctx.expression()) + elif ctx.invocation_expression(): + return CallExpr(Id(ctx.IDENT().getText()), + self.visit(ctx.invocation_expression())) + elif ctx.literal(): + return self.visit(ctx.literal()) + elif ctx.IDENT(): + return Id(ctx.IDENT().getText()) + elif ctx.STRING_LITERAL(): + return StringLiteral(ctx.STRING_LITERAL().getText()) + return + + def visitStatement(self, ctx: MPParser.StatementContext): + return self.visit(ctx.getChild(0)) + + def visitStructured_statement( + self, ctx: MPParser.Structured_statementContext): + if ctx.compound_statement(): + return self.visit(ctx.getChild(0)) + else: + return [self.visit(ctx.getChild(0))] + + def visitNormal_statement(self, ctx: MPParser.Normal_statementContext): + if ctx.assignment_statement(): + return self.visit(ctx.getChild(0)) + else: + return [self.visit(ctx.getChild(0))] + + def visitAssignment_statement( + self, ctx: MPParser.Assignment_statementContext): + """ + return list of Assign(lhs, exp) + """ + expression = self.visit(ctx.expression()) + assignment_lhs_list = self.visit(ctx.assignment_lhs_list()) + + rhs_list = assignment_lhs_list[1:] + [expression] + + # def compose(arg): + # def h(x): + # return Assign(x, arg) + # return h + # hoo = list(map(lambda x: compose(x), rhs_list)) + return [Assign(lhs, rhs) + for lhs, rhs in zip(assignment_lhs_list, rhs_list)][::-1] + + def visitAssignment_lhs_list( + self, ctx: MPParser.Assignment_lhs_listContext): + """ + return list of lhs + """ + lhs = self.visit(ctx.lhs()) + if ctx.assignment_lhs_list(): + return [lhs] + self.visit(ctx.assignment_lhs_list()) + else: + return [lhs] + + def visitLhs(self, ctx: MPParser.LhsContext): + """ + return IDENT or index_pression + """ + if ctx.IDENT(): + return Id(ctx.IDENT().getText()) + else: + return self.visit(ctx.index_expression()) + + def visitIf_statement(self, ctx: MPParser.If_statementContext): + expression = self.visit(ctx.expression()) + if ctx.ELSE(): + then_statement = self.visit(ctx.statement(0)) + else_statement = self.visit(ctx.statement(1)) + return If(expression, then_statement, else_statement) + else: + then_statement = self.visit(ctx.statement(0)) + return If(expression, then_statement) + + def visitWhile_statement(self, ctx: MPParser.While_statementContext): + return While(self.visit(ctx.expression()), self.visit(ctx.statement())) + + def visitFor_statement(self, ctx: MPParser.For_statementContext): + up = True if ctx.TO() else False + return For( + Id(ctx.IDENT().getText()), + self.visit(ctx.expression(0)), + self.visit(ctx.expression(1)), + up, + self.visit(ctx.statement())) + + def visitBreak_statement(self, ctx: MPParser.Break_statementContext): + return Break() + + def visitContinue_statement(self, ctx: MPParser.Continue_statementContext): + return Continue() + + def visitReturn_statement(self, ctx: MPParser.Return_statementContext): + if ctx.expression(): + return Return(self.visit(ctx.expression())) + else: + return Return() + + def visitCompound_statement(self, ctx: MPParser.Compound_statementContext): + if ctx.statement(): + return flatten(list(map(self.visit, ctx.statement()))) + else: + return [] + + def visitWith_statement(self, ctx: MPParser.With_statementContext): + return With(self.visit(ctx.varlist()), self.visit(ctx.statement())) + + def visitCall_statement(self, ctx: MPParser.Call_statementContext): + param = self.visit(ctx.call_param()) if ctx.call_param() else [] + return CallStmt(Id(ctx.IDENT().getText()), param) + + def visitCall_param(self, ctx: MPParser.Call_paramContext): + expression = self.visit(ctx.expression()) + if ctx.call_param(): + return [expression] + self.visit(ctx.call_param()) + else: + return [expression] + + def visitEmpty(self, ctx: MPParser.EmptyContext): + return + + def visitLiteral(self, ctx: MPParser.LiteralContext): + if ctx.number(): + return self.visit(ctx.number()) + elif ctx.BOOL_LIT(): + if ctx.BOOL_LIT().getText().lower() == 'true': + return BooleanLiteral(True) + else: + return BooleanLiteral(False) + elif ctx.STRING_LITERAL(): + return StringLiteral(ctx.STRING_LITERAL().getText()) + return + + def visitNumber(self, ctx: MPParser.NumberContext): + if ctx.NUM_INT(): + return IntLiteral(int(ctx.NUM_INT().getText())) + elif ctx.NUM_REAL(): + return FloatLiteral(float(ctx.NUM_REAL().getText())) diff --git a/astgen/__init__.py b/astgen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/checker/StaticCheck.py b/checker/StaticCheck.py new file mode 100644 index 0000000..95e08dd --- /dev/null +++ b/checker/StaticCheck.py @@ -0,0 +1,778 @@ +from utils.Visitor import BaseVisitor +from utils.Utils import Utils +from checker.StaticError import ( + # Kind, + Function, + Procedure, + Variable, + Parameter, + Identifier, + # StaticError, + Undeclared, + Redeclared, + TypeMismatchInExpression, + TypeMismatchInStatement, + FunctionNotReturn, + BreakNotInLoop, + ContinueNotInLoop, + NoEntryPoint, + UnreachableStatement, + Unreachable +) +from utils.AST import ( + IntType, + FloatType, + BoolType, + StringType, + ArrayType, + VoidType, + # Program, + # Decl, + # VarDecl, + FuncDecl, + # Stmt, + # Assign, + # If, + # While, + # For, + # Break, + # Continue, + # Return, + # With, + # CallStmt, + # Expr, + # BinaryOp, + # UnaryOp, + CallExpr, + # LHS, + # Id, + # ArrayCell, + # Literal, + # IntLiteral, + # FloatLiteral, + # StringLiteral, + # BooleanLiteral +) +from functools import reduce + + +class MType: + def __init__(self, partype, rettype): + self.partype = partype + self.rettype = rettype + + def __str__(self): + return 'MType([{}],{})'.format( + ','.join([str(x) for x in self.partype]), + str(self.rettype) + ) + + +class Symbol: + def __init__(self, name, mtype, value=0): + self.name = name + self.mtype = mtype + self.value = value + + def __str__(self): + return 'Symbol({},{})'.format( + self.name, + str(self.mtype) + ) + + +# DEBUG = True +DEBUG = False + + +def printEnv(env, stop=False): + if not DEBUG: + return + if stop: + try: + input(','.join([str(e) for e in env])) + except EOFError: + print(','.join([str(e) for e in env])) + else: + print(','.join([str(e) for e in env])) + + +def printDebug(desc, **kwargs): + if not DEBUG: + return + + print(desc) + + if 'env' in kwargs: + if 'stop' in kwargs: + printEnv(kwargs['env'], kwargs['stop']) + else: + printEnv(kwargs['env']) + + +class StaticChecker(BaseVisitor, Utils): + + global_envi = [ + # from specification, section 7: Built-in Functions/Procedures + Symbol("getInt", MType([], IntType())), + Symbol("putInt", MType([IntType()], VoidType())), + Symbol("putIntLn", MType([IntType()], VoidType())), + Symbol("getFloat", MType([], FloatType())), + Symbol("putFloat", MType([FloatType()], VoidType())), + Symbol("putFloatLn", MType([FloatType()], VoidType())), + Symbol("putBool", MType([BoolType()], VoidType())), + Symbol("putBoolLn", MType([BoolType()], VoidType())), + Symbol("putString", MType([StringType()], VoidType())), + Symbol("putStringLn", MType([StringType()], VoidType())), + Symbol("putLn", MType([], VoidType())) + ] + + def __init__(self, ast): + self.ast = ast + + def check(self): + return self.visit(self.ast, StaticChecker.global_envi) + + def checkRedeclared(self, symbol, kind, env): + res = self.lookup(symbol.name.lower(), env, lambda e: e.name.lower()) + if res is not None: + raise Redeclared(kind, symbol.name) + + def mergeGlobal2Local(self, local_scope, global_scope): + for s in global_scope: + res = self.lookup(s.name, local_scope, lambda e: e.name.lower()) + if res is None: + local_scope.append(s) + + def checkTypeCompatibility(self, lhs, rhs, error): + # array check + if isinstance(lhs, ArrayType): + if not isinstance(rhs, ArrayType): + raise error + if lhs.lower != rhs.lower or \ + lhs.upper != rhs.upper: + raise error + # self.checkTypeCompatibility(lhs.eleType, rhs.eleType, error) + if not isinstance(lhs.eleType, type(rhs.eleType)): + raise error + + # float/int coersion + elif isinstance(lhs, FloatType): + if not isinstance(rhs, (IntType, FloatType)): + raise error + + # else + elif not isinstance(lhs, type(rhs)): + raise error + + def callBody(self, ast, env): + ''' + ast: CallStmt | CallExpr + env: List[Symbol] + raise TypeMismatchInStatement | TypeMismatchInExpression + => Type: MType + ; Used by CallStmt and CallExpr + ; Both have exact structure difference only on + ; Raising Error and Kind Expectation + ''' + callParam = [self.visit(x, env) for x in ast.param] + mtype = self.visit( # visits Id + ast.method, + { + 'env': env, + 'kind': Function() if isinstance(ast, CallExpr) \ + else Procedure(), + } + ) + rightParam = mtype.partype + + # lazy init of Error + error = TypeMismatchInExpression if isinstance(ast, CallExpr) \ + else TypeMismatchInStatement + + if len(rightParam) != len(callParam): + raise error(ast) + + # LHS’s are formal parameters and RHS’s are arguments + for pair in zip(rightParam, callParam): + lhs = pair[0] + rhs = pair[1] + + self.checkTypeCompatibility(lhs, rhs, error(ast)) + + return mtype.rettype + + def loopBody(self, stmts, param): + ''' + stmt: List[Statement] + param: { + 'env': List[Symbol], + 'inloop': Bool, + 'rettype': Type + } + ''' + env = param['env'] + rettype = param['rettype'] + + outFlag = False + for stmt in stmts: + if outFlag: + raise UnreachableStatement(stmt) + if self.visit( + stmt, { + 'env': env, 'inloop': True, 'rettype': rettype + } + ): + outFlag = True + + return False + + def processStatement(self, stmts, param): + returnFlag = False + for stmt in stmts: + if returnFlag: + raise UnreachableStatement(stmt) + if self.visit(stmt, param): + returnFlag = True + return returnFlag + + def visitProgram(self, ast, env): + printDebug("======SCAN PROGRAM======") + global_scope = reduce( + lambda returnList, decl: + [self.visit( + decl, + {'env': returnList, 'scan': False} + )] + returnList, + ast.decl, + env[:] + ) + printDebug("======GLOBAL======", env=global_scope) + + if not any(map( + lambda symbol: isinstance( + symbol.mtype, + MType) and symbol.name.lower() == 'main' and isinstance( + symbol.mtype.rettype, + VoidType) and len(symbol.mtype.partype) == 0, + global_scope)): + raise NoEntryPoint() + + funcs = filter(lambda x: isinstance(x, FuncDecl), ast.decl) + for func in funcs: + self.visit(func, {'env': global_scope, 'scan': True}) + + for symbol in global_scope: + if not isinstance(symbol.mtype, MType): + continue + if symbol.name.lower() == 'main' and \ + isinstance(symbol.mtype.rettype, VoidType): + continue + if symbol.value == 0: + if symbol in env: + continue + raise Unreachable( + Procedure() if isinstance(symbol.mtype.rettype, VoidType) + else Function(), + symbol.name) + + return global_scope + + def visitFuncDecl(self, ast, param): + ''' + ast: FuncDecl + param: { + env: List[Symbol], # Global Reference Environment + scan: Bool + } + raise Redeclared(Parameter) + raise Redeclared(Variable) + raise UnreachableStatement + raise FunctionNotReturn + => Symbol if not scan else None + ''' + env = param['env'] + scan = param['scan'] + if not scan: + printDebug("FUNCDECL", env=env, stop=False) + s = Symbol( + ast.name.name, + MType( + [x.varType for x in ast.param], + ast.returnType + )) + + kind = Procedure() if isinstance(ast.returnType, VoidType) \ + else Function() + self.checkRedeclared(s, kind, env) + return s + else: + printDebug("========SCAN FUNC========") + printDebug(str(ast)) + + try: + # visits VarDecl -- throws Redeclared(Variable) + parameter = reduce( + lambda scope, vardecl: + [self.visit(vardecl, {'env': scope})] + scope, + ast.param, + # env[:] # copy + [] + ) + except Redeclared as e: + raise Redeclared(Parameter(), e.n) + printDebug("PARAM", env=parameter) + + # visits VarDecl -- throws Redeclared(Variable) + local_scope = reduce( + lambda scope, vardecl: + [self.visit(vardecl, {'env': scope})] + scope, + ast.local, + parameter # for safety reason, copy + ) + printDebug("LOCAL_VAR", env=local_scope) + # self.mergeGlobal2Local(local_scope, env) + local_scope += env + printDebug("LOCAL_ENV", env=local_scope, stop=False) + + # check in body + if not self.processStatement( + ast.body, + { + 'env': local_scope, + 'inloop': False, + 'rettype': ast.returnType + }) and not isinstance(ast.returnType, VoidType): + raise FunctionNotReturn(ast.name.name) + + def visitVarDecl(self, ast, param): + ''' + ast: VarDecl + param: { + env: List[Symbol] + ~~scan: Bool~~ # ignore + } + => Symbol + ''' + # print(param, file=sys.stderr) + env = param['env'] + printDebug("VARDECL", env=env, stop=False) + + s = Symbol( + ast.variable.name, + ast.varType + ) + + self.checkRedeclared(s, Variable(), env) + + return s + + def visitIntType(self, asttree, param): + return None + + def visitFloatType(self, asttree, param): + return None + + def visitBoolType(self, asttree, param): + return None + + def visitStringType(self, asttree, param): + return None + + def visitVoidType(self, asttree, param): + return None + + def visitArrayType(self, asttree, param): + return None + + def visitBinaryOp(self, ast, param): + ''' + ast: BinaryOp + param: list[Symbol] + raise TypeMismatchInExpression + (/) --> Float/Int:Float/Int => Float + (+,-,*) --> Float:Float/Int => Float + --> Float/Int:Float => Float + --> Int:Int => Int + (div,mod) --> Int:Int => Int + (<,<=,=,>=,>,<>) --> Float:Float/Int => Bool + --> Float/Int:Float => Bool + --> Int:Int => Bool + (and,or,andthen,orelse) --> Bool:Bool => Bool + => Type + ''' + op = ast.op.lower() + # visits (Id, BinaryOp, UnaryOp, CallExpr, ArrayCell) + left_type = self.visit(ast.left, param) + right_type = self.visit(ast.right, param) + + def deferType(acceptableTypes, returnType=None): + if not isinstance(left_type, acceptableTypes): + raise TypeMismatchInExpression(ast) + if not isinstance(right_type, acceptableTypes): + raise TypeMismatchInExpression(ast) + + if returnType is not None: + return returnType + if isinstance(left_type, FloatType) or \ + isinstance(right_type, FloatType): + return FloatType() + if isinstance(left_type, type(right_type)): + return left_type + + raise TypeMismatchInExpression(ast) + + if op in ('and', 'or', 'andthen', 'orelse'): + return deferType((BoolType), BoolType()) + + if op in ('div', 'mod'): + return deferType((IntType), IntType()) + + if op in ('+', '-', '*'): + return deferType((IntType, FloatType)) + + if op in ('/'): + return deferType((IntType, FloatType), FloatType()) + + if op in ('<', '<=', '=', '>=', '>', '<>'): + return deferType((IntType, FloatType), BoolType()) + + def visitUnaryOp(self, ast, param): + ''' + ast: UnaryOp + param: List[Symbol] + raise TypeMismatchInExpression + not Bool => Bool + - Int => Int + - Float => Float + => Type + ''' + op = ast.op.lower() + expr = self.visit(ast.body, param) + + if op in ('not'): + if not isinstance(expr, BoolType): + raise TypeMismatchInExpression(ast) + return BoolType() + + if op in ('-'): + if not isinstance(expr, (IntType, FloatType)): + raise TypeMismatchInExpression(ast) + return expr + + def visitCallExpr(self, ast, env): + ''' + ast: CallExpr ~ CallStmt + env: list[Symbol] + raise Undeclared(Function) + raise TypeMismatchInExpression + wrong param size + wrong param type + Array[n..m] of X --> Array[n..m] of X + Float --> Float/Int + X --> X + => Type + ''' + return self.callBody(ast, env) + + def visitId(self, ast, param): + ''' + ast: Id + param: List[Symbol] or { + 'env': List[Symbol] + 'kind': kind expectation + } + raise Undeclared + => Type: Symbol.mtype + ''' + env = param['env'] if not isinstance(param, list) else param + kind = param['kind'] if not isinstance(param, list) else Identifier() + + # type(res) == Symbol + # printDebug("ID", env=env, stop=False) + res = self.lookup(ast.name.lower(), env, lambda e: e.name.lower()) + + if res is None: + raise Undeclared(kind, ast.name) + + if isinstance(kind, Identifier): + if isinstance(res.mtype, MType): + raise Undeclared(kind, ast.name) + return res.mtype + + # param is dict + if isinstance(kind, Function) or isinstance(kind, Procedure): + # check if mtype -- aka function + if not isinstance(res.mtype, MType): + raise Undeclared(kind, ast.name) + + if isinstance(kind, Function): + if isinstance(res.mtype.rettype, VoidType): + raise Undeclared(kind, ast.name) + res.value += 1 + + elif isinstance(kind, Procedure): + if not isinstance(res.mtype.rettype, VoidType): + raise Undeclared(kind, ast.name) + res.value += 1 + return res.mtype + + def visitArrayCell(self, ast, param): + ''' + ast: ArrayCell + param: List[Symbol] + raise TypeMismatchInExpression + arr[idx] --> ArrayType[IntType] => ArrayType.eleType + => Type + ''' + arr = self.visit(ast.arr, param) + idx = self.visit(ast.idx, param) + + if not isinstance(idx, IntType) or \ + not isinstance(arr, ArrayType): + raise TypeMismatchInExpression(ast) + + return arr.eleType + + ''' + Statements are passed with a dict() with simple params: + env: List[Symbol] # local referencing environment + inloop: Bool # in loop flag + rettype: Type # return type of function/procedure + Simple statements deal only with 'env', + Continue/Break statements use 'inloop' to check for non in loop call + Return statements use 'rettype' to check for type compatibility when return + + Function/Procedure statements pass all these params, + For/While statements pass inloop as True when processing loop statements + while preserving 'rettype + If statements pass param to then/else statements + With statements ... + + All other statements uses param as read only + + Return of statements are the status of the function/procedure whether + it has returned or not + + For example: + + procedure main(); + begin + if (n and mask) then + return 1; + else + return 0; + end + + the corresponding AST Tree will be: + Program([ + FuncDecl(Id(main),[],VoidType(),[],[ + If(BinaryOp(and,Id(n),Id(mask)),[ + Return(Some(IntLiteral(1))) + ],[ + Return(Some(IntLiteral(0))) + ]) + ]) + ]) + + The If in AST will return True as both of the branch returns. + There is no else if statement, but this algorithm works as + expect that in if statement, all branch returns means the function is ended + + The same logic can also be applied to for/while/with statements + ''' + + def visitAssign(self, ast, param): + ''' + ast: Assign + param: { + 'env': List[Symbol] + 'inloop': Bool + } + raise TypeMismatchInStatement + Float := Float/Int + Int := Int + Bool := Bool + // no String, Array + => Returned? + ''' + env = param['env'] + left_type = self.visit(ast.lhs, env) + right_type = self.visit(ast.exp, env) + + if isinstance(left_type, (StringType, ArrayType)): + raise TypeMismatchInStatement(ast) + + self.checkTypeCompatibility( + left_type, right_type, TypeMismatchInStatement(ast)) + + return False + + def visitWith(self, ast, param): + ''' + ast: With + param: { + 'env': List[Symbol], + 'inloop': Bool, + 'rettype': Type + } + ''' + env = param['env'] + + with_scope = reduce( + lambda with_scope, decl: + [self.visit(decl, {'env': with_scope})] + with_scope, + ast.decl, + [] + ) + with_scope += env + + return self.processStatement( + ast.stmt, + { + 'env': with_scope, + 'inloop': param['inloop'], + 'rettype': param['rettype'] + }) + + def visitIf(self, ast, param): + ''' + ast: If + param: { + 'env': List[Symbol] + 'inloop': Bool + 'rettype': Type + } + => Returned? + ''' + expr = self.visit(ast.expr, param['env']) + if not isinstance(expr, BoolType): + raise TypeMismatchInStatement(ast) + + thenReturnFlag = False + for stmt in ast.thenStmt: + if thenReturnFlag: + raise UnreachableStatement(ast) + if self.visit(stmt, param): + thenReturnFlag = True + + elseReturnFlag = False + for stmt in ast.elseStmt: + if elseReturnFlag: + raise UnreachableStatement(ast) + if self.visit(stmt, param): + elseReturnFlag = True + + return thenReturnFlag and elseReturnFlag + + def visitFor(self, ast, param): + ''' + ast: For + param: { + 'env': List[Symbol], + 'rettype': Type + } + ''' + env = param['env'] + idType = self.visit(ast.id, env) + expr1 = self.visit(ast.expr1, env) + expr2 = self.visit(ast.expr2, env) + + if not isinstance(idType, IntType) or \ + not isinstance(expr1, IntType) or \ + not isinstance(expr2, IntType): + raise TypeMismatchInStatement(ast) + + return self.loopBody(ast.loop, param) + + def visitContinue(self, ast, param): + ''' + ast: Continue + param: { + 'inloop': Bool + } + ''' + if not param['inloop']: + raise ContinueNotInLoop() + return True + + def visitBreak(self, ast, param): + ''' + ast: Break + param: { + 'inloop': Bool + } + ''' + if not param['inloop']: + raise BreakNotInLoop() + return True + + def visitReturn(self, ast, param): + ''' + ast: Return + param: { + 'env': List[Symbol] + 'rettype': Type + } + ''' + rettype = param['rettype'] + env = param['env'] + if isinstance(rettype, VoidType): + if ast.expr is not None: + raise TypeMismatchInStatement(ast) + else: + if ast.expr is None: + raise TypeMismatchInStatement(ast) + self.checkTypeCompatibility( + rettype, self.visit(ast.expr, env), + TypeMismatchInStatement(ast) + ) + + return True + + def visitWhile(self, ast, param): + ''' + ast: While + param: { + 'env': List[Symbol], + 'inloop': Bool, + 'rettype': Type + } + ''' + env = param['env'] + + exp = self.visit(ast.exp, env) + if not isinstance(exp, BoolType): + raise TypeMismatchInStatement(ast) + + return self.loopBody(ast.sl, param) + + def visitCallStmt(self, ast, param): + ''' + ast: CallStmt + param: { + 'env': list[Symbol] + } + raise Undeclared(Procedure) + raise TypeMismatchInStatement + wrong param size + wrong param type + Array[n..m] of X --> Array[n..m] of X + Float --> Float/Int + X --> X + => Returned? + ''' + self.callBody(ast, param['env']) # skips return + return False + + def visitIntLiteral(self, asttree, param): + return IntType() + + def visitFloatLiteral(self, asttree, param): + return FloatType() + + def visitBooleanLiteral(self, asttree, param): + return BoolType() + + def visitStringLiteral(self, asttree, param): + return StringType() diff --git a/checker/StaticError.py b/checker/StaticError.py new file mode 100644 index 0000000..838a4c4 --- /dev/null +++ b/checker/StaticError.py @@ -0,0 +1,125 @@ +from abc import ABC + + +class Kind(ABC): + pass + + +class Function(Kind): + def __str__(self): + return "Function" + + +class Procedure(Kind): + def __str__(self): + return "Procedure" + + +class Parameter(Kind): + def __str__(self): + return "Parameter" + + +class Variable(Kind): + def __str__(self): + return "Variable" + + +class Identifier(Kind): + def __str__(self): + return "Identifier" + + +class StaticError(Exception): + pass + + +class Undeclared(StaticError): + """k: Kind + n: string: name of identifier """ + + def __init__(self, k, n): + self.k = k + self.n = n + + def __str__(self): + return "Undeclared " + str(self.k) + ": " + self.n + + +class Redeclared(StaticError): + """k: Kind + n: string: name of identifier """ + + def __init__(self, k, n): + self.k = k + self.n = n + + def __str__(self): + return "Redeclared " + str(self.k) + ": " + self.n + + +class TypeMismatchInExpression(StaticError): + """exp: AST.Expr""" + + def __init__(self, exp): + self.exp = exp + + def __str__(self): + return "Type Mismatch In Expression: " + str(self.exp) + + +class TypeMismatchInStatement(StaticError): + """stmt:AST.Stmt""" + + def __init__(self, stmt): + self.stmt = stmt + + def __str__(self): + return "Type Mismatch In Statement: " + str(self.stmt) + + +class FunctionNotReturn(StaticError): + """m is a string that is the name of the function""" + + def __init__(self, m): + self.m = m + + def __str__(self): + # return "Function " + self.m + "Not Return " + return "Function " + self.m + " Not Return" + + +class BreakNotInLoop(StaticError): + def __str__(self): + return "Break Not In Loop" + + +class ContinueNotInLoop(StaticError): + def __str__(self): + return "Continue Not In Loop" + + +class NoEntryPoint(StaticError): + def __str__(self): + return "No entry point" + + +class UnreachableStatement(StaticError): + """stmt is AST.Stmt""" + + def __init__(self, stmt): + self.stmt = stmt + + def __str__(self): + return "Unreachable statement: " + str(self.stmt) + + +class Unreachable(StaticError): + """m is a string that is the name of the unreachable function/procedure""" + + def __init__(self, k, m): + self.k = k + self.m = m + + def __str__(self): + return "Unreachable " + str(self.k) + ": " + self.m diff --git a/checker/__init__.py b/checker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codegen/CodeGenError.py b/codegen/CodeGenError.py new file mode 100644 index 0000000..843b6c8 --- /dev/null +++ b/codegen/CodeGenError.py @@ -0,0 +1,16 @@ +class IllegalOperandException(Exception): + def __init__(self, msg): + # msg:string + self.s = msg + + def __str__(self): + return "Illegal Operand: " + self.s + "\n" + + +class IllegalRuntimeException(Exception): + def __init__(self, msg): + # msg:string + self.s = msg + + def __str__(self): + return "Illegal Runtime: " + self.s + "\n" diff --git a/codegen/CodeGenerator.py b/codegen/CodeGenerator.py new file mode 100644 index 0000000..efb16d7 --- /dev/null +++ b/codegen/CodeGenerator.py @@ -0,0 +1,940 @@ +from utils.Utils import Utils +from utils.Visitor import BaseVisitor +from checker.StaticCheck import MType, Symbol +from utils.AST import ( + # Type, + IntType, + FloatType, + BoolType, + StringType, + # ArrayType, + VoidType, + # Program, + # Decl, + # VarDecl, + FuncDecl, + # Stmt, + Assign, + # If, + While, + # For, + # Break, + # Continue, + # Return, + # With, + # CallStmt, + # Expr, + BinaryOp, + # UnaryOp, + # CallExpr, + # LHS, + Id, + # ArrayCell, + # Literal, + IntLiteral, + # FloatLiteral, + # StringLiteral, + # BooleanLiteral, + ArrayPointerType, + ClassType +) +from codegen.Emitter import Emitter +from codegen.Frame import Frame +from abc import ABC # , abstractmethod +# from functools import reduce + + +class CodeGenerator(Utils): + def __init__(self): + self.libName = "io" + + def init(self): + return [ + # from specification, section 7: Built-in Functions/Procedures + Symbol( + "getInt", + MType([], IntType()), + CName(self.libName) + ), + Symbol( + "putInt", + MType([IntType()], VoidType()), + CName(self.libName) + ), + Symbol( + "putIntLn", + MType([IntType()], VoidType()), + CName(self.libName) + ), + Symbol( + "getFloat", + MType([], FloatType()), + CName(self.libName) + ), + Symbol( + "putFloat", + MType([FloatType()], VoidType()), + CName(self.libName) + ), + Symbol( + "putFloatLn", + MType([FloatType()], VoidType()), + CName(self.libName) + ), + Symbol( + "putBool", + MType([BoolType()], VoidType()), + CName(self.libName) + ), + Symbol( + "putBoolLn", + MType([BoolType()], VoidType()), + CName(self.libName) + ), + Symbol( + "putString", + MType([StringType()], VoidType()), + CName(self.libName) + ), + Symbol( + "putStringLn", + MType([StringType()], VoidType()), + CName(self.libName) + ), + Symbol( + "putLn", + MType([], VoidType()), + CName(self.libName) + ) + ] + + def gen(self, ast, dir_, name): + # ast: AST + # dir_: String + + gl = self.init() + gc = CodeGenVisitor(ast, gl, dir_, name) + gc.visit(ast, None) # visits Program + + +class SubBody(): + def __init__(self, frame, sym): + # frame: Frame + # sym: List[Symbol] + + self.frame = frame + self.sym = sym + self.gen = False + self.isFor = False + + def __str__(self): + return "SubBody({},{})".format( + str(self.frame), + ','.join([str(x) for x in self.sym]) + ) + + +class Access(): + def __init__(self, frame, sym, **kwargs): + # frame: Frame + # sym: List[Symbol] + # isLeft: Boolean + # isFirst: Boolean + + self.frame = frame + self.sym = sym + self.isLeft = kwargs.get('isLeft', False) + self.isFirst = kwargs.get('isFirst', False) + + +class Val(ABC): + pass + + +class Index(Val): + def __init__(self, value): + # value: Int + + self.value = value + + +class CName(Val): + def __init__(self, value): + # value: String + + self.value = value + + +class CodeGenVisitor(BaseVisitor, Utils): + def __init__(self, astTree, env, dir_, name): + ''' + astTree: AST + env: List[Symbol] + dir_: File + ''' + + self.astTree = astTree + self.env = env[:] # safety reason + self.className = name + self.path = dir_ + self.emit = Emitter( + self.path + "/" + self.className + ".j") + + # import sys + # print(astTree, file=sys.stderr) + + def genMETHOD(self, consdecl, o, frame): + ''' + consdecl: FuncDecl + o: SubBody, + frame: Frame + + Submethod to generate code for body, + most of this is given by teacher himself + with slight modifications, + add local variable code generation + ''' + + isInit = consdecl.returnType is None + isMain = consdecl.name.name == "main" and \ + len(consdecl.param) == 0 and \ + isinstance(consdecl.returnType, VoidType) + + if isInit: + returnType = VoidType() + methodName = '' + else: + returnType = consdecl.returnType + methodName = consdecl.name.name + + if isMain: + intype = [ArrayPointerType(StringType())] + else: + intype = [v.varType for v in consdecl.param] + + mtype = MType(intype, returnType) + + self.emit.printout( + self.emit.emitMETHOD( + methodName, + mtype, + not isInit, + frame + )) + + frame.enterScope(True) + + glenv = o + + # Generate code for parameter declarations + if isInit: + self.emit.printout( + self.emit.emitVAR( + frame.getNewIndex(), + "this", + ClassType(self.className), + frame.getStartLabel(), + frame.getEndLabel(), + frame + )) + if isMain: + self.emit.printout( + self.emit.emitVAR( + frame.getNewIndex(), + "args", + ArrayPointerType(StringType()), + frame.getStartLabel(), + frame.getEndLabel(), + frame + )) + + # set up local variables + if glenv is None: + glenv = [] + local = [] + for p in consdecl.param: + s = self.visit( + p, + SubBody(frame, local + glenv) + ) + local = [s] + local + for l in consdecl.local: + s = self.visit( + l, + SubBody(frame, local + glenv) + ) + local = [s] + local + glenv = local + glenv + + body = consdecl.body + self.emit.printout( + self.emit.emitLABEL( + frame.getStartLabel(), + frame + )) + + # Generate code for statements + if isInit: + self.emit.printout( + self.emit.emitREADVAR( + "this", + ClassType(self.className), + 0, + frame + )) + self.emit.printout( + self.emit.emitINVOKESPECIAL(frame) + ) + + # visit statement + list(map(lambda x: + self.visit( + x, + SubBody(frame, glenv)), + body + )) + + self.emit.printout( + self.emit.emitLABEL( + frame.getEndLabel(), + frame + )) + ''' + prevents: + LabelX: + LabelY: + end + ''' + self.emit.printout( + self.emit.emitRETURN( + VoidType(), + frame + )) + self.emit.printout( + self.emit.emitENDMETHOD(frame) + ) + frame.exitScope() + + def callBody(self, ast, ctxt): + ''' + ast: CallExpr | CallStmt + ctxt: SubBody if CallStmt + Access if CallExpr + => name, type if CallExpr + + CallExpr and CallStmt has similar routine, + Except that CallExpr returns (name, type) + of the function being called + ''' + frame = ctxt.frame + sym = ctxt.sym + + symbol, ctype = self.visit( # visits Id + ast.method, + Access(frame, sym, isFirst=True) + ) + cname = symbol.value + params = ctype.partype + + in_ = ("", list()) + for i, arg in enumerate(ast.param): + str1, typ1 = self.visit( + arg, + Access(frame, sym, isFirst=True) + ) + if isinstance(typ1, Symbol): + typ1 = typ1.mtype.rettype + + if not isinstance(typ1, type(params[i])): + # foo(float) ==> foo(int) + # others: Checker catches + str1 += self.emit.emitI2F(frame) + + in_ = (in_[0] + str1, in_[1] + [typ1]) + + # funcname = cname.value + '/' + ast.method.name + funcname = cname.value + '/' + symbol.name + code = '' + code += in_[0] + code += self.emit.emitINVOKESTATIC( + funcname, + ctype, + frame + ) + return code, symbol + + def visitProgram(self, ast, c): + ''' + ast: Program + c: Unknown + => c + ''' + + self.emit.printout( + self.emit.emitPROLOG( + self.className, + "java.lang.Object" + )) + e = SubBody(None, self.env) + + # generate global declare list + for x in ast.decl: + # returns Symbol on no Gen + self.env += [self.visit(x, e)] + + e.gen = True + for x in ast.decl: + # recall that e = SubBody(None, self.env) + # and e.sym = self.env as reference + self.visit(x, e) + + # generate default constructor + self.genMETHOD( + FuncDecl( + Id(""), + list(), + list(), + list(), + None + ), + c, + Frame("", VoidType) + ) + self.emit.emitEPILOG() + return c + + def visitFuncDecl(self, ast, subbody): + ''' + ast: FuncDecl + subbody: SubBody + => None if generate code + => else Symbol + ''' + + if subbody.gen: + # on gen + frame = Frame(ast.name, ast.returnType) + self.genMETHOD( + ast, + subbody.sym, + frame + ) + else: + # on first scan to get global Symbol + params = [v.varType for v in ast.param] + return Symbol( + ast.name.name, + MType(params, ast.returnType), + CName(self.className) + ) + + def visitVarDecl(self, ast, subbody): + ''' + ast: VarDecl + subbody: SubBody + => Symbol(,,CName) for global VarDecl + => Symbol(,,Index) for local VarDecl + ''' + if subbody.gen: + return + if subbody.frame is None: + # generate code for global var decl + # returns Symbol with CName + code = self.emit.emitATTRIBUTE( + ast.variable.name, + ast.varType, + False, + '' + ) + self.emit.printout(code) + return Symbol( + ast.variable.name, + ast.varType, + CName(self.className) + ) + else: + # generate code for local var decl + # returns Symbol with Index + frame = subbody.frame + idx = frame.getNewIndex() + code = self.emit.emitVAR( + idx, + ast.variable.name, + ast.varType, + frame.getStartLabel(), + frame.getEndLabel(), + frame + ) + self.emit.printout(code) + return Symbol( + ast.variable.name, + ast.varType, + Index(idx) + ) + + def visitBinaryOp(self, ast, param): + ''' + ast: BinaryOp + param: Access + => code, type + + Rules: + (/) --> Float/Int:Float/Int => Float + (+,-,*) --> Float:Float/Int => Float + --> Float/Int:Float => Float + --> Int:Int => Int + (div,mod) --> Int:Int => Int + (<,<=,=,>=,>,<>) --> Float:Float/Int => Bool + --> Float/Int:Float => Bool + --> Int:Int => Bool + (and,or,andthen,orelse) --> Bool:Bool => Bool + ''' + frame = param.frame + sym = param.sym + + lhs, ltype = self.visit( + ast.left, + Access(frame, sym) + ) + rhs, rtype = self.visit( + ast.right, + Access(frame, sym) + ) + if isinstance(ltype, MType): + ltype = ltype.rettype + if isinstance(rtype, MType): + rtype = rtype.rettype + + if (isinstance(ltype, type(rtype))): + # because idiv returns int not float + # while MP / returns float on all cases + if ast.op == '/' and\ + isinstance(ltype, IntType): + lhs += self.emit.emitI2F(frame) + rhs += self.emit.emitI2F(frame) + intype = FloatType() + rettype = FloatType() + else: + intype = ltype + rettype = ltype + else: + # only Float:Int or Int:Float + # I2F on Int + if isinstance(ltype, IntType): + lhs += self.emit.emitI2F(frame) + if isinstance(rtype, IntType): + rhs += self.emit.emitI2F(frame) + intype = FloatType() + rettype = FloatType() + + code = '' + code += lhs + code += rhs + # self.emit.printout(lhs) # push left + # self.emit.printout(rhs) # push right + + # generate code for each operator + if ast.op in ('+', '-'): + code += self.emit.emitADDOP( + ast.op, + intype, + frame + ) + elif ast.op in ('*', '/'): + code += self.emit.emitMULOP( + ast.op, + intype, + frame + ) + elif ast.op in ('<', '<=', '=', '>=', '>', '<>'): + code += self.emit.emitREOP( + ast.op, + intype, + frame + ) + rettype = BoolType() + elif ast.op.lower() == 'div': + code += self.emit.emitDIV(frame) + elif ast.op.lower() == 'mod': + code += self.emit.emitMOD(frame) + elif ast.op.lower() == 'and': + code += self.emit.emitANDOP(frame) + elif ast.op.lower() == 'or': + code += self.emit.emitOROP(frame) + elif ast.op.lower() == 'andthen': + code = '' + falseLabel = frame.getNewLabel() + endLabel = frame.getNewLabel() + code += lhs + code += self.emit.emitIFEQ(falseLabel, frame) + code += rhs + code += self.emit.emitIFEQ(falseLabel, frame) + code += self.emit.emitPUSHICONST(1, frame) + code += self.emit.emitGOTO(endLabel, frame) + code += self.emit.emitLABEL(falseLabel, frame) + code += self.emit.emitPUSHICONST(0, frame) + code += self.emit.emitLABEL(endLabel, frame) + rettype = BoolType() + elif ast.op.lower() == 'orelse': + code = '' + trueLabel = frame.getNewLabel() + endLabel = frame.getNewLabel() + code += lhs + # if lhs != 0 True + code += self.emit.emitIFNE(trueLabel, frame) + code += rhs + # if rhs != 0 True + code += self.emit.emitIFNE(trueLabel, frame) + code += self.emit.emitPUSHICONST(0, frame) + code += self.emit.emitGOTO(endLabel, frame) + code += self.emit.emitLABEL(trueLabel, frame) + code += self.emit.emitPUSHICONST(1, frame) + code += self.emit.emitLABEL(endLabel, frame) + rettype = BoolType() + + return code, rettype + + def visitUnaryOp(self, ast, param): + frame = param.frame + sym = param.sym + + bodycode, bodytype = self.visit( + ast.body, + Access(frame, sym) + ) + + code = '' + code += bodycode + if ast.op.lower() == 'not': + code += self.emit.emitNOT(bodytype, frame) + elif ast.op.lower() == '-': + code += self.emit.emitNEGOP(bodytype, frame) + + return code, bodytype + + def visitCallExpr(self, ast, o): + ''' + ast: CallExpr + o: Access + => (name, type) of function being called + ''' + return self.callBody(ast, o) + + def visitId(self, ast, param): + ''' + ast: Id + param: Access + ''' + res = self.lookup( + ast.name, + param.sym, + lambda env: env.name + ) + + # not likely, checker catches + if res is None: + return None, None + + # function call + if isinstance(res.mtype, MType): + return res, res.mtype + + frame = param.frame + # global variable + # res: Symbol(,,CName) + if isinstance(res.value, CName): + varname = res.value.value + '/' + res.name + if param.isLeft: + code = self.emit.emitPUTSTATIC( + varname, + res.mtype, + frame + ) + else: + code = self.emit.emitGETSTATIC( + varname, + res.mtype, + frame + ) + return code, res.mtype + + # Local variable + if param.isLeft: + code = self.emit.emitWRITEVAR( + res.name, + res.mtype, + res.value.value, + frame + ) + else: + code = self.emit.emitREADVAR( + res.name, + res.mtype, + res.value.value, + frame + ) + return code, res.mtype + + def visitArrayCell(self, ast, param): + ''' + ast: ArrayCell, + param: Access + ''' + frame = param.frame + sym = param.sym + arrcode, arrtype = self.visit( + ast.arr, + Access(frame, sym) + ) + idxcode, idxtype = self.visit( + ast.idx, + Access(frame, sym) + ) + return None + + def visitAssign(self, ast, param): + ''' + ast: Assign + param: SubBody + ''' + frame = param.frame + sym = param.sym + expcode, exptype = self.visit( + ast.exp, + Access(frame, sym) + ) + lhscode, lhstype = self.visit( + ast.lhs, + Access(frame, sym, isLeft=True) + ) + + if isinstance(exptype, Symbol): + exptype = exptype.mtype.rettype + + if not isinstance(exptype, type(lhstype)): + # float = int + # others => checker catches + expcode += self.emit.emitI2F(param.frame) + + code = '' + code += expcode + lhscode + self.emit.printout(code) + + def visitWith(self, ast, param): + ''' + ast: With + param: SubBody + ''' + frame = param.frame + sym = param.sym + + frame.enterScope(False) + startLabel = self.emit.emitLABEL(frame.getStartLabel(), frame) + endLabel = self.emit.emitLABEL(frame.getEndLabel(), frame) + + local = sym[:] + for var in ast.decl: + local = [self.visit(var, SubBody(frame, local))] + local + + self.emit.printout(startLabel) + for stmt in ast.stmt: + self.visit(stmt, SubBody(frame, local)) + self.emit.printout(endLabel) + frame.exitScope() + + def visitIf(self, ast, param): + ''' + ast: If + param: SubBody + ''' + frame = param.frame + sym = param.sym + haveElse = True if len(ast.elseStmt) > 0 \ + else False + + code = '' + expcode, exptype = self.visit( + ast.expr, Access(frame, sym) + ) + code += expcode + + elseLabel = frame.getNewLabel() + + if haveElse: + endLabel = frame.getNewLabel() + + code += self.emit.emitIFEQ(elseLabel, frame) + + self.emit.printout(code) + code = '' + + # printout then code + for stmt in ast.thenStmt: + self.visit(stmt, param) + + if haveElse: + code += self.emit.emitGOTO(endLabel, frame) + code += self.emit.emitLABEL(elseLabel, frame) + self.emit.printout(code) + code = '' + + if not haveElse: + return + + # printout else code + for stmt in ast.elseStmt: + self.visit(stmt, param) + + code += self.emit.emitLABEL(endLabel, frame) + self.emit.printout(code) + + def visitFor(self, ast, param): + conditionOp = '<=' if ast.up else '>=' + continueOp = '+' if ast.up else '-' + + loopCondition = BinaryOp(conditionOp, ast.id, ast.expr2) + continueInstruction = Assign( + ast.id, BinaryOp( + continueOp, ast.id, IntLiteral(1))) + loop = ast.loop[:] + [continueInstruction] + + param.isFor = True + self.visit(Assign(ast.id, ast.expr1), param) + self.visit(While(loopCondition, loop), param) + + def visitContinue(self, ast, param): + frame = param.frame + continueLabel = frame.getContinueLabel() + code = self.emit.emitGOTO(continueLabel, frame) + self.emit.printout(code) + + def visitBreak(self, ast, param): + frame = param.frame + breakLabel = frame.getBreakLabel() + code = self.emit.emitGOTO(breakLabel, frame) + self.emit.printout("\t; Break\n") + self.emit.printout(code) + + def visitReturn(self, ast, param): + frame = param.frame + sym = param.sym + rettype = frame.returnType + + if ast.expr: + expcode, exptype = self.visit( + ast.expr, + Access(frame, sym) + ) + else: + expcode, exptype = '', VoidType() + + code = '' + code += expcode + if not isinstance(exptype, type(rettype)): + # return int in float function + # others -> Checker catches + code += self.emit.emitI2F(frame) + code += self.emit.emitRETURN(rettype, frame) + self.emit.printout(code) + + def visitWhile(self, ast, param): + ''' + ast: While + param: SubBody + ''' + frame = param.frame + sym = param.sym + isFor = param.isFor + + param.isFor = False + code = '' + + frame.enterLoop() + breakLabel = frame.getBreakLabel() + continueLabel = frame.getContinueLabel() + + expcode, exptype = self.visit( + ast.exp, + Access(frame, sym) + ) + if isFor: + continueInstruction = ast.sl[-1] + ast.sl = ast.sl[:-1] + + code += expcode + code += self.emit.emitIFEQ(breakLabel, frame) + + bodyLabel = frame.getNewLabel() + code += '; body\n' + code += self.emit.emitLABEL(bodyLabel, frame) + + self.emit.printout(code) + code = '' + + for stmt in ast.sl: + self.visit(stmt, param) + + code += '; continue\n' + code += self.emit.emitLABEL(continueLabel, frame) + if isFor: + self.emit.printout(code) + code = '' + self.visit(continueInstruction, param) + code += expcode + code += self.emit.emitIFNE(bodyLabel, frame) + else: + code += expcode + code += self.emit.emitIFNE(bodyLabel, frame) + + code += '; break\n' + code += self.emit.emitLABEL(breakLabel, frame) + self.emit.printout(code) + frame.exitLoop() + + def visitCallStmt(self, ast, o): + ''' + ast: CallStmt + o: SubBody + ''' + code, symbol = self.callBody(ast, o) + self.emit.printout(code) + + def visitIntLiteral(self, ast, param): + ''' + ast: IntLiteral + param: Access + => code, type + ''' + frame = param.frame + return self.emit.emitPUSHICONST( + ast.value, + frame + ), IntType() + + def visitFloatLiteral(self, ast, param): + ''' + ast: FloatLiteral + param: Access + => code, type + ''' + frame = param.frame + return self.emit.emitPUSHFCONST( + str(ast.value), + frame + ), FloatType() + + def visitBooleanLiteral(self, ast, param): + frame = param.frame + return self.emit.emitPUSHICONST( + str(ast.value).lower(), + frame + ), BoolType() + + def visitStringLiteral(self, ast, param): + frame = param.frame + return self.emit.emitPUSHCONST( + # emitLDC doesn't add " + '"{}"'.format(ast.value), + StringType(), + frame + ), StringType() diff --git a/codegen/Emitter.py b/codegen/Emitter.py new file mode 100644 index 0000000..2eac32e --- /dev/null +++ b/codegen/Emitter.py @@ -0,0 +1,784 @@ +from codegen.CodeGenError import ( + # Utils, + # IllegalEscape, + IllegalOperandException, + # IllegalRuntimeException +) +# from Utils import * +from checker.StaticCheck import MType # , Symbol +# from StaticError import ( +# # Kind, +# Function, +# Procedure, +# Variable, +# Parameter, +# Identifier, +# # StaticError, +# Undeclared, +# Redeclared, +# TypeMismatchInExpression, +# TypeMismatchInStatement, +# FunctionNotReturn, +# BreakNotInLoop, +# ContinueNotInLoop, +# NoEntryPoint, +# UnreachableStatement, +# Unreachable +# ) +from utils.AST import ( + IntType, + FloatType, + BoolType, + StringType, + # ArrayType, + VoidType, + # Program, + # Decl, + # VarDecl, + # FuncDecl, + # Stmt, + # Assign, + # If, + # While, + # For, + # Break, + # Continue, + # Return, + # With, + # CallStmt, + # Expr, + # BinaryOp, + # UnaryOp, + # CallExpr, + # LHS, + # Id, + # ArrayCell, + # Literal, + IntLiteral, + # FloatLiteral, + # StringLiteral, + # BooleanLiteral, + ArrayPointerType, + ClassType +) +# from codegen.CodeGenerator import ArrayPointerType, ClassType +from codegen.MachineCode import JasminCode + + +class Emitter(): + def __init__(self, filename): + self.filename = filename + self.buff = list() + self.jvm = JasminCode() + + def getJVMType(self, inType): + ''' + https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2 + ''' + typeIn = type(inType) + if typeIn is IntType: + return "I" + elif typeIn is FloatType: + return "F" + elif typeIn is BoolType: + return "Z" + elif typeIn is StringType: + return "Ljava/lang/String;" + elif typeIn is VoidType: + return "V" + elif typeIn is ArrayPointerType: + return "[" + self.getJVMType(inType.eleType) + elif typeIn is MType: + return "({}){}".format( + "".join(list(map(lambda x: self.getJVMType(x), + inType.partype))), + self.getJVMType(inType.rettype) + ) + # return "(" + "".join(list(map(lambda x: self.getJVMType(x), + # inType.partype))) + ")" + self.getJVMType(inType.rettype) + elif typeIn is ClassType: + return "L" + inType.cname + ";" + + def getFullType(inType): + typeIn = type(inType) + if typeIn is IntType: + return "int" + elif typeIn is StringType: + return "java/lang/String" + elif typeIn is VoidType: + return "void" + + def emitPUSHICONST(self, in_, frame): + # in: Int or Sring + # frame: Frame + + frame.push() + if isinstance(in_, int): + i = in_ + if i >= -1 and i <= 5: + return self.jvm.emitICONST(i) + elif i >= -128 and i <= 127: + return self.jvm.emitBIPUSH(i) + elif i >= -32768 and i <= 32767: + return self.jvm.emitSIPUSH(i) + elif isinstance(in_, str): + if in_ == "true": + return self.emitPUSHICONST(1, frame) + elif in_ == "false": + return self.emitPUSHICONST(0, frame) + else: + return self.emitPUSHICONST(int(in_), frame) + + def emitPUSHFCONST(self, in_, frame): + # in_: String + # frame: Frame + + f = float(in_) + frame.push() + rst = "{0:.4f}".format(f) + if rst == "0.0" or rst == "1.0" or rst == "2.0": + return self.jvm.emitFCONST(rst) + else: + return self.jvm.emitLDC(in_) + + ''' + * generate code to push a constant onto the operand stack. + * @param in the lexeme of the constant + * @param typ the type of the constant + ''' + + def emitPUSHCONST(self, in_, typ, frame): + # in_: String + # typ: Type + # frame: Frame + + if isinstance(typ, IntType): + return self.emitPUSHICONST(in_, frame) + elif isinstance(typ, BoolType): + return self.emitPUSHICONST(in_, frame) + elif isinstance(typ, StringType): + frame.push() + return self.jvm.emitLDC(in_) + else: + raise IllegalOperandException(in_) + + ############################################### + + def emitALOAD(self, in_, frame): + # in_: Type + # frame: Frame + # ..., arrayref, index, value -> ... + + frame.pop() + if isinstance(in_, IntType): + return self.jvm.emitIALOAD() + elif isinstance(in_, ArrayPointerType) or\ + isinstance(in_, ClassType) or\ + isinstance(in_, StringType): + return self.jvm.emitAALOAD() + else: + raise IllegalOperandException(str(in_)) + + def emitASTORE(self, in_, frame): + # in_: Type + # frame: Frame + # ..., arrayref, index, value -> ... + + frame.pop() + frame.pop() + frame.pop() + if isinstance(in_, IntType): + return self.jvm.emitIASTORE() + elif isinstance(in_, ArrayPointerType) or\ + isinstance(in_, ClassType) or\ + isinstance(in_, StringType): + return self.jvm.emitAASTORE() + else: + raise IllegalOperandException(str(in_)) + + ''' generate the var directive for a local variable. + * @param in the index of the local variable. + * @param varName the name of the local variable. + * @param inType the type of the local variable. + * @param fromLabel the starting label of the scope + * where the variable is active. + * @param toLabel the ending label of the scope + * where the variable is active. + ''' + + def emitVAR(self, in_, varName, inType, fromLabel, toLabel, frame): + # in_: Int + # varName: String + # inType: Type + # fromLabel: Int + # toLabel: Int + # frame: Frame + + return self.jvm.emitVAR( + in_, + varName, + self.getJVMType(inType), + fromLabel, + toLabel) + + def emitREADVAR(self, name, inType, index, frame): + # name: String + # inType: Type + # index: Int + # frame: Frame + # ... -> ..., value + + frame.push() + if isinstance(inType, (IntType, BoolType)): + return self.jvm.emitILOAD(index) + elif isinstance(inType, FloatType): + return self.jvm.emitFLOAD(index) + elif isinstance(inType, ArrayPointerType) or\ + isinstance(inType, ClassType) or\ + isinstance(inType, StringType): + return self.jvm.emitALOAD(index) + else: + raise IllegalOperandException(name) + + ''' generate the second instruction for array cell access + * + ''' + + def emitREADVAR2(self, name, typ, frame): + # name: String + # typ: Type + # frame: Frame + # ... -> ..., value + + # frame.push() + raise IllegalOperandException(name) + + ''' + * generate code to pop a value on top of the operand stack + * and store it to a block-scoped variable. + * @param name the symbol entry of the variable. + ''' + + def emitWRITEVAR(self, name, inType, index, frame): + # name: String + # inType: Type + # index: Int + # frame: Frame + # ..., value -> ... + + frame.pop() + + if isinstance(inType, (IntType, BoolType)): + return self.jvm.emitISTORE(index) + elif isinstance(inType, FloatType): + return self.jvm.emitFSTORE(index) + elif isinstance(inType, ArrayPointerType) or\ + isinstance(inType, ClassType) or\ + isinstance(inType, StringType): + return self.jvm.emitASTORE(index) + else: + raise IllegalOperandException(name) + + ''' generate the second instruction for array cell access + * + ''' + + def emitWRITEVAR2(self, name, typ, frame): + # name: String + # typ: Type + # frame: Frame + # ..., value -> ... + + # frame.push() + raise IllegalOperandException(name) + + ''' generate the field (static) directive for a class mutable or + * immutable attribute. + * @param lexeme the name of the attribute. + * @param in the type of the attribute. + * @param isFinal true in case of constant; false otherwise + ''' + + def emitATTRIBUTE(self, lexeme, in_, isFinal, value): + # lexeme: String + # in_: Type + # isFinal: Boolean + # value: String + + return self.jvm.emitSTATICFIELD(lexeme, self.getJVMType(in_), False) + + def emitGETSTATIC(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + + frame.push() + return self.jvm.emitGETSTATIC(lexeme, self.getJVMType(in_)) + + def emitPUTSTATIC(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + + frame.pop() + return self.jvm.emitPUTSTATIC(lexeme, self.getJVMType(in_)) + + def emitGETFIELD(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + + return self.jvm.emitGETFIELD(lexeme, self.getJVMType(in_)) + + def emitPUTFIELD(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + + frame.pop() + frame.pop() + return self.jvm.emitPUTFIELD(lexeme, self.getJVMType(in_)) + + ''' generate code to invoke a static method + * @param lexeme the qualified name of the method + * (i.e., class-name/method-name) + * @param in the type descriptor of the method. + ''' + + def emitINVOKESTATIC(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + + typ = in_ + list(map(lambda x: frame.pop(), typ.partype)) + if not isinstance(typ.rettype, VoidType): + frame.push() + return self.jvm.emitINVOKESTATIC(lexeme, self.getJVMType(in_)) + + ''' generate code to invoke a special method + * @param lexeme the qualified name of the method + * (i.e., class-name/method-name) + * @param in the type descriptor of the method. + ''' + + def emitINVOKESPECIAL(self, frame, lexeme=None, in_=None): + # lexeme: String + # in_: Type + # frame: Frame + + if lexeme is not None and in_ is not None: + typ = in_ + list(map(lambda x: frame.pop(), typ.partype)) + frame.pop() + if not isinstance(typ.rettype, VoidType): + frame.push() + return self.jvm.emitINVOKESPECIAL(lexeme, self.getJVMType(in_)) + elif lexeme is None and in_ is None: + frame.pop() + return self.jvm.emitINVOKESPECIAL() + + ''' generate code to invoke a virtual method + * @param lexeme the qualified name of the method + * (i.e., class-name/method-name) + * @param in the type descriptor of the method. + ''' + + def emitINVOKEVIRTUAL(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + + typ = in_ + list(map(lambda x: frame.pop(), typ.partype)) + frame.pop() + if not isinstance(typ, VoidType): + frame.push() + return self.jvm.emitINVOKEVIRTUAL(lexeme, self.getJVMType(in_)) + + ''' + * generate ineg, fneg. + * @param in the type of the operands. + ''' + + def emitNEGOP(self, in_, frame): + # in_: Type + # frame: Frame + # ..., value -> ..., result + + if isinstance(in_, IntType): + return self.jvm.emitINEG() + else: + return self.jvm.emitFNEG() + + def emitNOT(self, in_, frame): + # in_: Type + # frame: Frame + + label1 = frame.getNewLabel() + label2 = frame.getNewLabel() + result = list() + result.append(self.emitIFTRUE(label1, frame)) + result.append(self.emitPUSHCONST("true", in_, frame)) + result.append(self.emitGOTO(label2, frame)) + result.append(self.emitLABEL(label1, frame)) + result.append(self.emitPUSHCONST("false", in_, frame)) + result.append(self.emitLABEL(label2, frame)) + return ''.join(result) + + ''' + * generate iadd, isub, fadd or fsub. + * @param lexeme the lexeme of the operator. + * @param in the type of the operands. + ''' + + def emitADDOP(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + # ..., value1, value2 -> ..., result + + frame.pop() + if lexeme == "+": + if isinstance(in_, IntType): + return self.jvm.emitIADD() + else: + return self.jvm.emitFADD() + else: + if isinstance(in_, IntType): + return self.jvm.emitISUB() + else: + return self.jvm.emitFSUB() + + ''' + * generate imul, idiv, fmul or fdiv. + * @param lexeme the lexeme of the operator. + * @param in the type of the operands. + ''' + + def emitMULOP(self, lexeme, in_, frame): + # lexeme: String + # in_: Type + # frame: Frame + # ..., value1, value2 -> ..., result + + frame.pop() + if lexeme == "*": + if isinstance(in_, IntType): + return self.jvm.emitIMUL() + else: + return self.jvm.emitFMUL() + else: + if isinstance(in_, IntType): + return self.jvm.emitIDIV() + else: + return self.jvm.emitFDIV() + + def emitDIV(self, frame): + # frame: Frame + + frame.pop() + return self.jvm.emitIDIV() + + def emitMOD(self, frame): + # frame: Frame + + frame.pop() + return self.jvm.emitIREM() + + ''' + * generate iand + ''' + + def emitANDOP(self, frame): + # frame: Frame + + frame.pop() + return self.jvm.emitIAND() + + ''' + * generate ior + ''' + + def emitOROP(self, frame): + # frame: Frame + + frame.pop() + return self.jvm.emitIOR() + + def emitREOP(self, op, in_, frame): + # op: String + # in_: Type + # frame: Frame + # ..., value1, value2 -> ..., result + + result = list() + labelF = frame.getNewLabel() + labelO = frame.getNewLabel() + + frame.pop() + frame.pop() + + if isinstance(in_, FloatType): + frame.push() + result.append(self.jvm.emitFCMPL()) + result.append(self.emitPUSHICONST(0, frame)) + frame.pop() + + if op == ">": + result.append(self.jvm.emitIFICMPLE(labelF)) + elif op == ">=": + result.append(self.jvm.emitIFICMPLT(labelF)) + elif op == "<": + result.append(self.jvm.emitIFICMPGE(labelF)) + elif op == "<=": + result.append(self.jvm.emitIFICMPGT(labelF)) + elif op == "<>": + result.append(self.jvm.emitIFICMPEQ(labelF)) + elif op == "=": + result.append(self.jvm.emitIFICMPNE(labelF)) + result.append(self.emitPUSHCONST("1", IntType(), frame)) + frame.pop() + result.append(self.emitGOTO(labelO, frame)) + result.append(self.emitLABEL(labelF, frame)) + result.append(self.emitPUSHCONST("0", IntType(), frame)) + result.append(self.emitLABEL(labelO, frame)) + return ''.join(result) + + def emitRELOP(self, op, in_, trueLabel, falseLabel, frame): + # op: String + # in_: Type + # trueLabel: Int + # falseLabel: Int + # frame: Frame + # ..., value1, value2 -> ..., result + + result = list() + + frame.pop() + frame.pop() + if op == ">": + result.append(self.jvm.emitIFICMPLE(falseLabel)) + result.append(self.emitGOTO(trueLabel)) + elif op == ">=": + result.append(self.jvm.emitIFICMPLT(falseLabel)) + elif op == "<": + result.append(self.jvm.emitIFICMPGE(falseLabel)) + elif op == "<=": + result.append(self.jvm.emitIFICMPGT(falseLabel)) + elif op == "!=": + result.append(self.jvm.emitIFICMPEQ(falseLabel)) + elif op == "==": + result.append(self.jvm.emitIFICMPNE(falseLabel)) + result.append(self.jvm.emitGOTO(trueLabel)) + return ''.join(result) + + ''' generate the method directive for a function. + * @param lexeme the qualified name of the method + * (i.e., class-name/method-name). + * @param in the type descriptor of the method. + * @param isStatic true if the method is static; + * false otherwise. + ''' + + def emitMETHOD(self, lexeme, in_, isStatic, frame): + # lexeme: String + # in_: Type + # isStatic: Boolean + # frame: Frame + + return self.jvm.emitMETHOD(lexeme, self.getJVMType(in_), isStatic) + + ''' generate the end directive for a function. + ''' + + def emitENDMETHOD(self, frame): + # frame: Frame + + buffer = list() + buffer.append(self.jvm.emitLIMITSTACK(frame.getMaxOpStackSize())) + buffer.append(self.jvm.emitLIMITLOCAL(frame.getMaxIndex())) + buffer.append(self.jvm.emitENDMETHOD()) + return ''.join(buffer) + + def getConst(self, ast): + # ast: Literal + if isinstance(ast, IntLiteral): + return (str(ast.value), IntType()) + + ''' generate code to initialize a local array variable.

+ * @param index the index of the local variable. + * @param in the type of the local array variable. + ''' + + ''' generate code to initialize local array variables. + * @param in the list of symbol entries corresponding to + * local array variable. + ''' + + def emitIFEQ(self, label, frame): + frame.pop() + return self.jvm.emitIFEQ(label) + + def emitIFNE(self, label, frame): + frame.pop() + return self.jvm.emitIFNE(label) + + ''' generate code to jump to label if the value on top of + * operand stack is true.

+ * ifgt label + * @param label the label where the execution continues if + * the value on top of stack is true. + ''' + + def emitIFTRUE(self, label, frame): + # label: Int + # frame: Frame + + frame.pop() + return self.jvm.emitIFGT(label) + + ''' + * generate code to jump to label if the value on top of + * operand stack is false.

+ * ifle label + * @param label the label where the execution continues if + * the value on top of stack is false. + ''' + + def emitIFFALSE(self, label, frame): + # label: Int + # frame: Frame + + frame.pop() + return self.jvm.emitIFLE(label) + + def emitIFICMPGT(self, label, frame): + # label: Int + # frame: Frame + + frame.pop() + return self.jvm.emitIFICMPGT(label) + + def emitIFICMPLT(self, label, frame): + # label: Int + # frame: Frame + + frame.pop() + return self.jvm.emitIFICMPLT(label) + + ''' generate code to duplicate the value on the top of the operand stack.

+ * Stack:

+ * Before: ...,value1

+ * After: ...,value1,value1

+ ''' + + def emitDUP(self, frame): + # frame: Frame + + frame.push() + return self.jvm.emitDUP() + + def emitPOP(self, frame): + # frame: Frame + + frame.pop() + return self.jvm.emitPOP() + + ''' generate code to exchange an integer on top of + stack to a floating-point number. + ''' + + def emitI2F(self, frame): + # frame: Frame + + return self.jvm.emitI2F() + + ''' generate code to return. + *

    + *
  • ireturn if the type is IntegerType or BooleanType + *
  • freturn if the type is RealType + *
  • return if the type is null + *
+ * @param in the type of the returned expression. + ''' + + def emitRETURN(self, in_, frame): + # in_: Type + # frame: Frame + + if isinstance(in_, (IntType, BoolType)): + frame.pop() + return self.jvm.emitIRETURN() + elif isinstance(in_, FloatType): + frame.pop() + return self.jvm.emitFRETURN() + elif isinstance(in_, VoidType): + return self.jvm.emitRETURN() + + ''' generate code that represents a label + * @param label the label + * @return code Label