大端字节序码流中取出2字节_产生字节码

大端字节序码流中取出2字节

在这篇文章中,我们将看到如何为我们的语言生成字节码。 到目前为止,我们已经看到了如何构建一种语言来表达我们想要的东西,如何验证该语言,如何为该语言构建编辑器,但实际上我们还不能运行代码。 是时候解决这个问题了。 通过为JVM进行编译,我们的代码将能够在各种平台上运行。 对我来说听起来很棒!

jvm_bytecode_write_your_own_compiler

建立自己的语言的系列

以前的帖子:

  1. 建立一个词法分析器
  2. 建立一个解析器
  3. 创建带有语法突出显示的编辑器
  4. 使用自动补全功能构建编辑器
  5. 将解析树映射到抽象语法树
  6. 建模转换
  7. 验证方式

代码在GitHub上的标签为08_bytecode

添加打印声明

在跳入字节码生成之前,我们只需在我们的语言中添加一条打印语句即可。 这很容易:我们只需要在词法分析器和解析器定义中更改几行,就可以了。

// Changes to lexer
PRINT              : 'print';// Changes to parser
statement : varDeclaration # varDeclarationStatement| assignment     # assignmentStatement| print          # printStatement ;print : PRINT LPAREN expression RPAREN ;

我们的编译器的一般结构

让我们从编译器的入口点开始。 我们将从标准输入或文件中获取代码(将被指定为第一个参数)。 一旦获得代码,我们将尝试构建AST并检查词汇和语法错误。 如果没有,我们将验证AST并检查语义错误。 如果仍然没有错误,我们继续进行字节码生成。

fun main(args: Array<String>) {val code : InputStream? = when (args.size) {0 -> System.`in`1 -> FileInputStream(File(args[0]))else -> {System.err.println("Pass 0 arguments or 1")System.exit(1)null}}val parsingResult = SandyParserFacade.parse(code!!)if (!parsingResult.isCorrect()) {println("ERRORS:")parsingResult.errors.forEach { println(" * L${it.position.line}: ${it.message}") }return}val root = parsingResult.root!!println(root)val errors = root.validate()if (errors.isNotEmpty()) {println("ERRORS:")errors.forEach { println(" * L${it.position.line}: ${it.message}") }return}val bytes = JvmCompiler().compile(root, "MyClass")val fos = FileOutputStream("MyClass.class")fos.write(bytes)fos.close()
}

请注意,在此示例中,我们始终会生成一个名为MyClass的类文件。 大概以后,我们想找到一种为类文件指定名称的方法,但是现在这已经足够了。

使用ASM生成字节码

现在,让我们潜入有趣的部分。 JvmCompiler编译方法是我们生成字节的地方,以后我们将其保存到类文件中。 我们如何产生这些字节? 在ASM的帮助下,ASM是一个用于生成字节码的库。 现在,我们可以自己生成bytes数组,但要点是,它将涉及一些无聊的任务,例如生成类池结构。 ASM为我们做到了。 我们仍然需要对JVM的结构有所了解,但是我们可以生存下来而无需成为专家的精髓。

class JvmCompiler {fun compile(root: SandyFile, name: String) : ByteArray {// this is how we tell ASM that we want to start writing a new class. We ask it to calculate some values for usval cw = ClassWriter(ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS)// here we specify that the class is in the format introduced with Java 8 (so it would require a JRE >= 8 to run)// we also specify the name of the class, the fact it extends Object and it implements no interfacescw.visit(V1_8, ACC_PUBLIC, name, null, "java/lang/Object", null)// our class will have just one method: the main method. We have to specify its signature// this string just says that it takes an array of Strings and return nothing (void)val mainMethodWriter = cw.visitMethod(ACC_PUBLIC or ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null)mainMethodWriter.visitCode()// labels are used by ASM to mark points in the codeval methodStart = Label()val methodEnd = Label()// with this call we indicate to what point in the method the label methodStart correspondsmainMethodWriter.visitLabel(methodStart)// Variable declarations:// we find all variable declarations in our code and we assign to them an index value// our vars map will tell us which variable name corresponds to which indexvar nextVarIndex = 0val vars = HashMap<String, Var>()root.specificProcess(VarDeclaration::class.java) {val index = nextVarIndex++vars[it.varName] = Var(it.type(vars), index)mainMethodWriter.visitLocalVariable(it.varName, it.type(vars).jvmDescription, null, methodStart, methodEnd, index)}// time to generate bytecode for all the statementsroot.statements.forEach { s ->when (s) {is VarDeclaration -> {// we calculate the type of the variable (more details later)val type = vars[s.varName]!!.type// the JVM is a stack based machine: it operated with values we have put on the stack// so as first thing when we meet a variable declaration we put its value on the stacks.value.pushAs(mainMethodWriter, vars, type)// now, depending on the type of the variable we use different operations to store the value// we put on the stack into the variable. Note that we refer to the variable using its index, not its namewhen (type) {IntType -> mainMethodWriter.visitVarInsn(ISTORE, vars[s.varName]!!.index)DecimalType -> mainMethodWriter.visitVarInsn(DSTORE, vars[s.varName]!!.index)else -> throw UnsupportedOperationException(type.javaClass.canonicalName)}}is Print -> {// this means that we access the field "out" of "java.lang.System" which is of type "java.io.PrintStream"mainMethodWriter.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")// we push the value we want to print on the stacks.value.push(mainMethodWriter, vars)// we call the method println of System.out to print the value. It will take its parameter from the stack// note that we have to tell the JVM which variant of println to call. To do that we describe the signature of the method,// depending on the type of the value we want to print. If we want to print an int we will produce the signature "(I)V",// we will produce "(D)V" for a doublemainMethodWriter.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(${s.value.type(vars).jvmDescription})V", false)}is Assignment -> {val type = vars[s.varName]!!.type// This code is the same we have seen for variable declarationss.value.pushAs(mainMethodWriter, vars, type)when (type) {IntType -> mainMethodWriter.visitVarInsn(ISTORE, vars[s.varName]!!.index)DecimalType -> mainMethodWriter.visitVarInsn(DSTORE, vars[s.varName]!!.index)else -> throw UnsupportedOperationException(type.javaClass.canonicalName)}}else -> throw UnsupportedOperationException(s.javaClass.canonicalName)}}// We just says that here is the end of the methodmainMethodWriter.visitLabel(methodEnd)// And we had the return instructionmainMethodWriter.visitInsn(RETURN)mainMethodWriter.visitEnd()mainMethodWriter.visitMaxs(-1, -1)cw.visitEnd()return cw.toByteArray()}}

关于类型

好的,我们已经看到我们的代码使用类型。 这是必需的,因为根据类型,我们需要使用不同的说明。 例如,将值放入整数变量中,我们使用ISTORE;而将值放入双重变量中,我们使用DSTORE 。 当我们以整数调用System.out.println时,我们需要指定签名(I)V,而当我们调用它以打印双精度字符时,则需要指定(D)V

为此,我们需要了解每个表达式的类型。 在我们超简单的语言中,我们现在仅使用intdouble 。 在真实的语言中,我们可能想使用更多的类型,但这足以向您展示这些原理。

interface SandyType {// given a type we want to get the corresponding string used in the JVM// for example: int -> I, double -> D, Object -> Ljava/lang/Object; String -> [Ljava.lang.String;val jvmDescription: String
}object IntType : SandyType {override val jvmDescription: Stringget() = "I"
}object DecimalType : SandyType {override val jvmDescription: Stringget() = "D"
}fun Expression.type(vars: Map<String, Var>) : SandyType {return when (this) {// an int literal has type int. Easy :)is IntLit -> IntTypeis DecLit -> DecimalType// the result of a binary expression depends on the type of the operandsis BinaryExpression -> {val leftType = left.type(vars)val rightType = right.type(vars)if (leftType != IntType && leftType != DecimalType) {throw UnsupportedOperationException()}if (rightType != IntType && rightType != DecimalType) {throw UnsupportedOperationException()}// an operation on two integers produces integersif (leftType == IntType && rightType == IntType) {return IntType// if at least a double is involved the result is a double} else {return DecimalType}}// when we refer to a variable the type is the type of the variableis VarReference -> vars[this.varName]!!.type// when we cast to a value, the resulting value is that type :)is TypeConversion -> this.targetType.toSandyType()else -> throw UnsupportedOperationException(this.javaClass.canonicalName)}
}

表达方式

如我们所见,JVM是基于堆栈的计算机。 因此,每次我们想使用一个值时,都会将其压入堆栈,然后执行一些操作。 让我们看看如何将值推入堆栈

// Convert, if needed
fun Expression.pushAs(methodWriter: MethodVisitor, vars: Map<String, Var>, desiredType: SandyType) {push(methodWriter, vars)val myType = type(vars)if (myType != desiredType) {if (myType == IntType && desiredType == DecimalType) {methodWriter.visitInsn(I2D)} else if (myType == DecimalType && desiredType == IntType) {methodWriter.visitInsn(D2I)} else {throw UnsupportedOperationException("Conversion from $myType to $desiredType")}}
}fun Expression.push(methodWriter: MethodVisitor, vars: Map<String, Var>) {when (this) {// We have specific operations to push integers and double valuesis IntLit -> methodWriter.visitLdcInsn(Integer.parseInt(this.value))is DecLit -> methodWriter.visitLdcInsn(java.lang.Double.parseDouble(this.value))// to push a sum we first push the two operands and then invoke an operation which// depend on the type of the operands (do we sum integers or doubles?)is SumExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(IADD)DecimalType -> methodWriter.visitInsn(DADD)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}is SubtractionExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(ISUB)DecimalType -> methodWriter.visitInsn(DSUB)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}is DivisionExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(IDIV)DecimalType -> methodWriter.visitInsn(DDIV)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}is MultiplicationExpression -> {left.pushAs(methodWriter, vars, this.type(vars))right.pushAs(methodWriter, vars, this.type(vars))when (this.type(vars)) {IntType -> methodWriter.visitInsn(IMUL)DecimalType -> methodWriter.visitInsn(DMUL)else -> throw UnsupportedOperationException("Summing ${this.type(vars)}")}}// to push a variable we just load the value from the symbol tableis VarReference -> {val type = vars[this.varName]!!.typewhen (type) {IntType -> methodWriter.visitVarInsn(ILOAD, vars[this.varName]!!.index)DecimalType -> methodWriter.visitVarInsn(DLOAD, vars[this.varName]!!.index)else -> throw UnsupportedOperationException(type.javaClass.canonicalName)}}// the pushAs operation take care of conversions, as neededis TypeConversion -> {this.value.pushAs(methodWriter, vars, this.targetType.toSandyType())}else -> throw UnsupportedOperationException(this.javaClass.canonicalName)}
}

Gradle

我们还可以创建gradle任务来编译源文件

main = "me.tomassetti.sandy.compiling.JvmKt"args = "$sourceFile"classpath = sourceSets.main.runtimeClasspath
}

结论

我们没有详细介绍,我们急于浏览代码。 我的目的只是给您概述用于生成字节码的一般策略。 当然,如果您想构建一种严肃的语言,则需要做一些研究并理解JVM的内部,这是无可避免的。 我只是希望这个简短的介绍足以使您了解到这并不那么令人恐惧或复杂,大多数人都认为。

翻译自: https://www.javacodegeeks.com/2016/09/generating-bytecode.html

大端字节序码流中取出2字节

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/335478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python写520_用Python做一个520表白神器,值得收藏

本文最后给出了打包好的软件&#xff0c;无需安装Python环境和各种依赖&#xff0c;直接下载软件&#xff0c;解压后&#xff0c;双击exe文件即可使用。先来看一下具体的效果。运行程序。用Python做一个520表白神器&#xff0c;值得收藏点击「选择图片」 选择JPG/JPGE/PNG三种中…

为什么java抗并发_用最通熟易懂的话说明,为什么要使用java并发编程

老早之前的计算机只有一个处理器&#xff0c;而 一个处理器在同一时刻只能处理一条指令 &#xff0c;换句话说&#xff0c;我们的代码需要一行一行的按顺序被计算机执行&#xff0c;计算机只能把一个程序完整的执行完&#xff0c;然后再执行第二个程序。所以计算机专业的同学们…

不同坐标系下角速度_最伟大的数学发明,坐标系的诞生,是人类史上的方向盘...

【想要了解更多精彩文章、视频&#xff0c;欢迎关注创鹏科学堂】人生最大的意义&#xff0c;莫过于过得更方便&#xff1b;数学最大的意义&#xff0c;莫过于帮助人类过得更方便。几千年来&#xff0c;自从数学出现之后&#xff0c;它就一直以人类生活为导向&#xff0c;以宇宙…

php中的ol标签,html5中ol标签的用法详解

这篇文章主要介绍了详解HTML5中ol标签的用法,是HTML5入门学习中的基础知识,需要的朋友可以参考下定义和用法标签定义有序列表。HTML 4.01 与 HTML 5 之间的差异在 HTML 4.01 中&#xff0c;不赞成使用 "start" 属性&#xff0c;在 HTML 5 中是允许的。在 HTML 4.01 中…

portlet_平台策略:从Portlet到OpenSocial小工具再到渐进式Web应用程序:最新技术

portlet介绍 由于世界仍在Java的掌控之中&#xff0c;因此我们经常定义所谓的基于组件的平台 。 我在2000年拥有OpenUSS&#xff08;开放大学支持系统&#xff09;的经验。 当时我有一个想法&#xff0c;就是开发一个可以使用组件体系结构和J2EE技术​​&#xff08; OpenUSS C…

keil5函数 默认返回值_C++ 函数的定义

“ C对于函数的基本用法”01—函数的定义//函数声明&#xff1a;[返回值类型] [函数名称] (参数列表)int Function(int a, int b);//函数定义int Function(int a, int b){ //函数体 return a b;}02—函数的默认参数定义函数时可以在参数列表中为形参指定默认值int Function2…

已经创建了AWS EC2实例,Linux系统默认没有root用户,那么如何创建root用户并更改为root用户登录呢?

文章目录1. 如何创建ROOT及设置密码2.更改登陆方式&#xff0c;采用ROOT用户登陆a. 编辑EC2实例的ssh登录方式b. 再编辑authorized_keys文件&#xff0c;将ssh-rsa 前面的文字全部删除&#xff0c;确保ssh-rsa没有任何文字&#xff0c;包括空格。3. 重新登陆对于刚创建AWS EC2实…

输入一个正整数求所有素数因子_一起来聊聊素数的两个性质

素数(prime number)&#xff0c;又称质数&#xff0c;有无限个。定义&#xff1a;在大于1的自然数中&#xff0c;除了1和它本身以外不再有其他因数。来介绍两个简单的性质&#xff1a;质数的个数是无穷的。欧几里得的《几何原本》曾有一经典证明&#xff0c;用的是反证法。当然…

基于docker微服务架构_使用基于微服务的流架构更好地进行大规模的复杂事件处理(第1部分)...

基于docker微服务架构基于微服务的流架构与开源规则引擎相结合&#xff0c;使实时业务规则变得容易 这篇文章旨在详细介绍我将OSS业务规则引擎与Kafka风格的现代流消息传递系统集成在一起的项目。 该项目的目标&#xff08;即众所周知的复杂事件处理&#xff08;CEP&#xff0…

php怎样下载网上的文件,php怎样实现文件下载

用head() 和 fread()函数把文件直接输出到浏览器。具体代码&#xff1a;<?php $file_name "down";$file_name "down.zip"; //下载文件名$file_dir "./down/"; //下载文件存放目录//检查文件是否存在if (! file_exists ( $file_dir . $fil…

Windows 系统下,移动硬盘无法识别怎么修复?

方案1&#xff1a;移动硬盘没有驱动器号 有时移动硬盘插入计算机&#xff0c;但无法在我的计算机上显示。此时&#xff0c;可能只是因为移动硬盘没有驱动器号。 右键单击此计算机&#xff08;计算机&#xff09;&#xff0c;然后单击[管理]将其打开。 点击左边的[磁盘管理]&a…

欢乐鼠标自动点击器_使用ESP32 DIY蓝牙自动点击器,滑屏器

前言因为爱薅羊毛&#xff0c;然后正好最近免费车一趟接一趟&#xff0c;咪咕开完&#xff0c;京东开&#xff0c;京东开完掌阅开&#xff0c;下一个不知道是谁&#xff0c;&#xff0c;&#xff0c;&#xff0c;可我又不是什么很有耐心的人&#xff0c;虽然我喜欢看书&#xf…

java单词按字典排序_最终Java日志字典:开发人员最常记录的单词是什么?

java单词按字典排序最终的记录字典&#xff0c;或者&#xff1a;我们记录的最常见单词是什么&#xff1f; 日志文件是调试应用程序的最常用方法&#xff0c;当解决错误时&#xff0c;它们肯定可以引导我们朝正确的方向发展。 但是&#xff0c;大多数日志文件每天都会增加一百万…

winscp使用密钥登录远程linux系统

说明&#xff1a; WinSCP是一个Windows环境下使用SSH的开源图形化SFTP客户端。同时putty也是一个开源连接ssh的windows客户端&#xff0c;本文介绍puttyWinSCP密钥对登录linux服务器。 1. 创建密钥对 安装openssl yum –y install openssl #openssl一般服务器都会预装 创建密…

php api 实例maccms,苹果cmsV10API接口说明

api接口仅供提供数据&#xff0c;可以直接在苹果CMS后台联盟采集中加入-并提供给他人采集。联盟资源分配唯一标识ID&#xff0c;用来区别绑定分类&#xff0c;这个ID一般由苹果CMS官方提供&#xff0c;不可随意修改设置&#xff0c;否则造成入库分类错乱。视频接口同时支持老板…

dml语句包括哪些_聊聊MySQL基本操作DDL,DML,DQL,DCL

本篇文章来回顾一下MySQL的基本操作之DDL&#xff0c;DML&#xff0c;DQL&#xff0c;DCL&#xff0c;每种操作都有各自不同的语法&#xff0c;常用的操作汇总如下。一、DDL-数据定义语言作用&#xff1a;数据定义语言主要用来定义数据库中的各类对象&#xff0c;包括用户、库、…

Windows 系统下使用 putty 客户端通过 SSH 远程连接 AWS 服务器

找到在购买亚马逊的AWS服务器时保存的密钥文件&#xff08;假设为abc.pem&#xff09;。 打开PuTTYgen&#xff0c;如下图&#xff0c;点击图中1处的“load”,找到abc.pem文件所在的位置&#xff0c;并选择abc.pem&#xff0c;确定&#xff1b; 然后&#xff0c;选择图中2处…

ip integrator_使用Oracle Data Integrator(和Kafka / MapR流)完善Lambda体系结构

ip integrator“ Lambda体系结构是一种数据处理体系结构&#xff0c;旨在通过利用批处理和流处理方法来处理大量数据。 这种体系结构方法尝试通过使用批处理提供批处理数据的全面而准确的视图&#xff0c;同时使用实时流处理提供在线数据的视图来平衡延迟 &#xff0c; 吞吐量和…

php 如何获取函数类型,PHP如何使用gettype()函数判断变量的类型?

gettype()函数是PHP中的一个内置函数&#xff0c;用于获取变量的类型&#xff1b;它可用于检查现有变量的类型。下面本篇文章就来给大家介绍一下gettype()函数&#xff0c;希望对大家有所帮助。【视频教程推荐&#xff1a;PHP教程】基本语法string gettype ( $var )参数&#x…