主函数
你或许也发现了,我们目前为止的语句都是直接写在类定义里的,没有入口函数是如何编译成字节码进而执行的呢
再来看这张图,很明显,我们默认生成了一个main
函数来包装我们的语句
一、主函数
标题为 一、主函数要定义主函数,我们首先要确定的时函数的语法,我的大致设想是这样的
type Dog { fn main (args:string[]) -> void { var name = "小白"; print name; var age = 3; print age; }}
如果将其抽象一下,大致是这种格式
type 类名 { fn 函数名 (参数值:参数类型) -> 返回类型 { 语句 }}
其中参数值以及返回类型都是缺省的
二、调整antlr配置
标题为 二、调整antlr配置确定了目标,我们首先来修改一下antlr配置
// 定义语言名称grammar Qing;
// 定义解析规则// 根规则:所有的代码文本只有`variable`和`print`两种类型,EOF表示文件结束compilationUnit : classDeclaration EOF;classDeclaration : CLASS_IDENTIFIER className '{' classBody '}';classBody : ( function )* ;
functionName : ID;parameterName : ID;function: functionDeclaration '{' (functionStatement SEMICOLON)* '}' ;functionDeclaration: FUCTION_IDENTIFIER functionName '(' parametersList? ')' functionReturn;parametersList: parameter (',' parameter)*;parameter: parameterName COLON type;functionReturn: (POINT type)?;functionStatement : variableDeclarationStatement | printStatement;
variableDeclarationStatement : (type | VAR_IDENTIFIER) name EQUALS expression;printStatement : PRINT expression ;
name :ID;expression : variableReference #VarReference | value #ValueExpr ;variableReference : ID ;
value : NUMBER | STRING ; //must be NUMBER or STRING value (defined below)
type: primitiveType;primitiveType: 'boolean' ('[' ']')* | 'string' ('[' ']')*| 'char' ('[' ']')*| 'byte' ('[' ']')*| 'short' ('[' ']')*| 'int' ('[' ']')*| 'long' ('[' ']')*| 'float' ('[' ']')*| 'double' ('[' ']')*| 'void' ('[' ']')* ;
// 符号规则,代码切割为符号的规则CLASS_IDENTIFIER : 'type';FUCTION_IDENTIFIER : 'fn';VAR_IDENTIFIER : 'var' ;
PRINT : 'print' ;EQUALS : '=' ;NUMBER : [0-9]+ ; // 数字STRING : '"'~('"')*'"' ; // "任意值"ID : [a-zA-Z0-9]+ ; // 需要是字母和数字值WS: [ \t\n\r]+ -> skip ; // 用来过滤空行的特殊符号className : ID ;
STRING_DOUBLEQUOTE:'"';COLON:':';SEMICOLON:';';POINT:'->';
配置文件也比较简单,不过其中有几个变动值得注意一下:
- 添加了基本数据类型的定义 - primitiveType
- 定义变量和输出语句被抽象为了一个表达式 -expression
类型和表达式都会在后面的工作中逐渐完善
三、方法定义
标题为 三、方法定义一个方法的定义包含如下的信息
可能还有一些其它的标识,我们在这篇文章暂时不会考虑,比如:
- [?] 是否是静态方法
- [?] 访问控制修饰符
这样以来我们可以针对性地定义一个方法的构成部分,如下
public class MethodDeclaration implements DescriptorProvider { private String name; private List<Parameter> parameters; private Type returnType; private List<Statement> statements;
private int access = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC;
@Override public String getDescriptor() { String parametersDescriptor = parameters.stream() .map(parameter -> parameter.getType().getDescriptor()) .collect(Collectors.joining("", "(", ")")); String returnDescriptor = returnType.getDescriptor(); return parametersDescriptor + returnDescriptor; }
}
四、变量定义域
标题为 四、变量定义域目前我们还没有实现类的成员变量,所以所有变量的定义都是在方法中的,一个方法可以定义多个变量,也可以在不同地方引用这些变量(打印语句或者返回值),变量的定义和使用有如下的要求:
- 引用某个变量,该变量需要在引用前定义
- 变量不可以重复定义
public class Scope {
private static LinkedMap<String,LocalVariable> localVariables = new LinkedMap<>(); public static LocalVariable getLocalVariable(String varName) { return localVariables.get(varName); } public static int getLocalVariableIndex(String varName) { return localVariables.indexOf(varName); }
public static void addLocalVariable(LocalVariable localVariable) { localVariables.put(localVariable.getName(), localVariable); }}
我暂时添加了一个类用来维护编译过程中的一些变量,这种写法是有问题的,你可以明显的看到它并没有区分方法,整个类中的变量全部融合在了一起,这些问题我们会在后面整体再调整修复
我们变量添加的入口是VariableDeclarationStatement
即变量定义语句中,所以需要在下方代码执行时同时维护变量域
public Statement visitVariableDeclarationStatement(QingParser.VariableDeclarationStatementContext ctx) { String varName = ctx.name().getText(); Expression expression = ctx.expression().accept(new ExpressionVisitor()); // 添加变量加入scope Scope.addLocalVariable(new LocalVariable(varName, expression.getType())); return new VariableDeclarationStatement(varName,expression);}
这样我们就把变量名与变量的表达式统一维护起来了
五、生成字节码
标题为 五、生成字节码我们一直都没有涉及对象的生成,所以每个方法只能通过静态的方式调用
private int access = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC;
通过visitMethod
来进行字节码的组装
public class MethodGenerator extends AbstractByteGenerator {
private final ClassWriter classWriter;
public MethodGenerator(ClassWriter classWriter) { this.classWriter = classWriter; }
public void generate(MethodDeclaration method) { MethodVisitor mv = classWriter.visitMethod(method.getAccess(), method.getName(), method.getDescriptor(), null, null); mv.visitCode(); StatementGenerator statementScopeGenerator = new StatementGenerator(mv); method.getStatements().stream().forEach(stmt -> statementScopeGenerator.generate(stmt) ); mv.visitMaxs(-1,-1); mv.visitEnd(); }}
六、运行调试
标题为 六、运行调试type Dog { fn main (args:string[]) -> void { var name = "小白"; print name; var age = 3; print age; }
fn test (x:int) { var a = 123; print name; }}
生成的字节码文件
// class version 52.0 (52)// access flags 0x21public class Dog {
// access flags 0x9 public static main([Ljava/lang/String;)V LDC "\u5c0f\u767d" ASTORE 0 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ALOAD 0 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V SIPUSH 3 ISTORE 1 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ILOAD 1 INVOKEVIRTUAL java/io/PrintStream.println (I)V RETURN MAXSTACK = 2 MAXLOCALS = 2
// access flags 0x9 public static test(I)V SIPUSH 123 ISTORE 2 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ALOAD 0 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V RETURN MAXSTACK = 2 MAXLOCALS = 3}