主函数

你或许也发现了,我们目前为止的语句都是直接写在类定义里的,没有入口函数是如何编译成字节码进而执行的呢

再来看这张图,很明显,我们默认生成了一个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 0x21
public 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
}