|
|
|
@ -2,10 +2,12 @@ const coral = @import("coral");
|
|
|
|
|
|
|
|
|
|
const tokens = @import("./tokens.zig");
|
|
|
|
|
|
|
|
|
|
name: []const coral.io.Byte,
|
|
|
|
|
allocator: coral.io.Allocator,
|
|
|
|
|
arena: coral.arena.Stacking,
|
|
|
|
|
statements: Statement.List,
|
|
|
|
|
error_message: []const coral.io.Byte,
|
|
|
|
|
error_buffer: coral.list.ByteStack,
|
|
|
|
|
tokenizer: tokens.Tokenizer,
|
|
|
|
|
|
|
|
|
|
pub const Expression = union (enum) {
|
|
|
|
|
nil_literal,
|
|
|
|
@ -67,7 +69,7 @@ pub const Expression = union (enum) {
|
|
|
|
|
pub const List = coral.list.Stack(Expression);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ExpressionParser = fn (self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression;
|
|
|
|
|
const ExpressionParser = fn (self: *Self) ParseError!Expression;
|
|
|
|
|
|
|
|
|
|
pub const ParseError = error {
|
|
|
|
|
OutOfMemory,
|
|
|
|
@ -103,25 +105,25 @@ fn binary_operation_parser(
|
|
|
|
|
comptime operators: []const Expression.BinaryOperator) ExpressionParser {
|
|
|
|
|
|
|
|
|
|
const BinaryOperationParser = struct {
|
|
|
|
|
fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
|
|
|
|
fn parse(self: *Self) ParseError!Expression {
|
|
|
|
|
const allocator = self.arena.as_allocator();
|
|
|
|
|
var expression = try parse_next(self, tokenizer);
|
|
|
|
|
var expression = try parse_next(self);
|
|
|
|
|
|
|
|
|
|
inline for (operators) |operator| {
|
|
|
|
|
const token = comptime operator.token();
|
|
|
|
|
|
|
|
|
|
if (tokenizer.is_token(coral.io.tag_of(token))) {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
if (self.tokenizer.is_token(coral.io.tag_of(token))) {
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
if (tokenizer.token == null) {
|
|
|
|
|
return self.raise("expected other half of expression after `" ++ comptime token.text() ++ "`");
|
|
|
|
|
if (self.tokenizer.token == null) {
|
|
|
|
|
return self.report("expected other half of expression after `" ++ comptime token.text() ++ "`");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expression = .{
|
|
|
|
|
.binary_operation = .{
|
|
|
|
|
.operator = operator,
|
|
|
|
|
.lhs_expression = try coral.io.allocate_one(allocator, expression),
|
|
|
|
|
.rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self, tokenizer)),
|
|
|
|
|
.rhs_expression = try coral.io.allocate_one(allocator, try parse_next(self)),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
@ -134,22 +136,33 @@ fn binary_operation_parser(
|
|
|
|
|
return BinaryOperationParser.parse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn error_message(self: Self) []const coral.io.Byte {
|
|
|
|
|
return self.error_buffer.values;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn free(self: *Self) void {
|
|
|
|
|
self.arena.free();
|
|
|
|
|
self.statements.free();
|
|
|
|
|
self.error_buffer.free();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn make(allocator: coral.io.Allocator) Self {
|
|
|
|
|
pub fn make(allocator: coral.io.Allocator, ast_name: []const coral.io.Byte) Self {
|
|
|
|
|
return Self{
|
|
|
|
|
.arena = coral.arena.Stacking.make(allocator, 4096),
|
|
|
|
|
.allocator = allocator,
|
|
|
|
|
.error_buffer = coral.list.ByteStack.make(allocator),
|
|
|
|
|
.statements = Statement.List.make(allocator),
|
|
|
|
|
.error_message = "",
|
|
|
|
|
.tokenizer = .{.source = ""},
|
|
|
|
|
.allocator = allocator,
|
|
|
|
|
.name = ast_name,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn raise(self: *Self, message: []const u8) ParseError {
|
|
|
|
|
self.error_message = message;
|
|
|
|
|
fn report(self: *Self, message: []const coral.io.Byte) ParseError {
|
|
|
|
|
coral.utf8.print_formatted(coral.list.stack_as_writer(&self.error_buffer), "{name}@{line}: {message}", .{
|
|
|
|
|
.name = self.name,
|
|
|
|
|
.line = self.tokenizer.lines_stepped,
|
|
|
|
|
.message = message,
|
|
|
|
|
}) catch return error.OutOfMemory;
|
|
|
|
|
|
|
|
|
|
return error.BadSyntax;
|
|
|
|
|
}
|
|
|
|
@ -158,32 +171,30 @@ pub fn list_statements(self: Self) []const Statement {
|
|
|
|
|
return self.statements.values;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
|
|
|
|
self.free();
|
|
|
|
|
pub fn parse(self: *Self, data: []const coral.io.Byte) ParseError!void {
|
|
|
|
|
self.tokenizer = .{.source = data};
|
|
|
|
|
|
|
|
|
|
const allocator = self.arena.as_allocator();
|
|
|
|
|
var has_returned = false;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
const no_effect_message = "statement has no effect";
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
switch (tokenizer.token orelse return) {
|
|
|
|
|
switch (self.tokenizer.token orelse return) {
|
|
|
|
|
.keyword_return => {
|
|
|
|
|
if (has_returned) {
|
|
|
|
|
return self.raise("multiple returns in function scope but expected only one");
|
|
|
|
|
return self.report("multiple returns in function scope but expected only one");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try self.statements.push_one(get_statement: {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
if (!tokenizer.is_token_null_or(.newline)) {
|
|
|
|
|
break: get_statement .{.return_expression = try self.parse_expression(tokenizer)};
|
|
|
|
|
if (!self.tokenizer.is_token_null_or(.newline)) {
|
|
|
|
|
break: get_statement .{.return_expression = try self.parse_expression()};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!tokenizer.is_token_null_or(.newline)) {
|
|
|
|
|
return self.raise("unexpected token after return");
|
|
|
|
|
if (!self.tokenizer.is_token_null_or(.newline)) {
|
|
|
|
|
return self.report("unexpected token after return");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break: get_statement .return_nothing;
|
|
|
|
@ -193,60 +204,64 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.identifier => |identifier| {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
switch (tokenizer.token orelse return self.raise(no_effect_message)) {
|
|
|
|
|
.newline => return self.raise(no_effect_message),
|
|
|
|
|
const no_effect_message = "statement has no effect";
|
|
|
|
|
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report(no_effect_message)) {
|
|
|
|
|
.newline => return self.report(no_effect_message),
|
|
|
|
|
|
|
|
|
|
.symbol_equals => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
if (tokenizer.token == null) {
|
|
|
|
|
return self.raise("expected expression after `=`");
|
|
|
|
|
if (self.tokenizer.token == null) {
|
|
|
|
|
return self.report("expected expression after `=`");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try self.statements.push_one(.{
|
|
|
|
|
.set_local = .{
|
|
|
|
|
.expression = try self.parse_expression(tokenizer),
|
|
|
|
|
.expression = try self.parse_expression(),
|
|
|
|
|
.identifier = identifier,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!tokenizer.is_token_null_or(.newline)) {
|
|
|
|
|
return self.raise("unexpected token after assignment");
|
|
|
|
|
if (!self.tokenizer.is_token_null_or(.newline)) {
|
|
|
|
|
return self.report("unexpected token after assignment");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => return self.raise("expected `=` after local"),
|
|
|
|
|
else => return self.report("expected `=` after local"),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.special_identifier => |identifier| {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
switch (tokenizer.token orelse return self.raise(no_effect_message)) {
|
|
|
|
|
.newline => return self.raise(no_effect_message),
|
|
|
|
|
const missing_arguments_message = "system call is missing arguments";
|
|
|
|
|
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report(missing_arguments_message)) {
|
|
|
|
|
.newline => return self.report(missing_arguments_message),
|
|
|
|
|
|
|
|
|
|
.symbol_paren_left => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
var expressions_list = Expression.List.make(allocator);
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
if (tokenizer.is_token(.symbol_paren_right)) {
|
|
|
|
|
if (self.tokenizer.is_token(.symbol_paren_right)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try expressions_list.push_one(try self.parse_expression(tokenizer));
|
|
|
|
|
try expressions_list.push_one(try self.parse_expression());
|
|
|
|
|
|
|
|
|
|
switch (tokenizer.token orelse return self.raise("unexpected end after after `(`")) {
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report("unexpected end after after `(`")) {
|
|
|
|
|
.symbol_comma => continue,
|
|
|
|
|
.symbol_paren_right => break,
|
|
|
|
|
else => return self.raise("expected `)` or argument after `(`"),
|
|
|
|
|
else => return self.report("expected `)` or argument after `(`"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
try self.statements.push_one(.{
|
|
|
|
|
.call_system = .{
|
|
|
|
@ -256,11 +271,11 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => return self.raise("expected `=` after local"),
|
|
|
|
|
else => return self.report("expected `=` after local"),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => return self.raise("invalid statement"),
|
|
|
|
|
else => return self.report("invalid statement"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -281,67 +296,67 @@ const parse_expression = binary_operation_parser(parse_equality, &.{
|
|
|
|
|
.subtraction,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
|
|
|
|
|
fn parse_factor(self: *Self) ParseError!Expression {
|
|
|
|
|
const allocator = self.arena.as_allocator();
|
|
|
|
|
|
|
|
|
|
switch (tokenizer.token orelse return self.raise("expected operand after operator")) {
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report("expected operand after operator")) {
|
|
|
|
|
.symbol_paren_left => {
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
if (tokenizer.token == null) {
|
|
|
|
|
return self.raise("expected an expression after `(`");
|
|
|
|
|
if (self.tokenizer.token == null) {
|
|
|
|
|
return self.report("expected an expression after `(`");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const expression = try self.parse_expression(tokenizer);
|
|
|
|
|
const expression = try self.parse_expression();
|
|
|
|
|
|
|
|
|
|
if (!tokenizer.is_token(.symbol_paren_right)) {
|
|
|
|
|
return self.raise("expected a closing `)` after expression");
|
|
|
|
|
if (!self.tokenizer.is_token(.symbol_paren_right)) {
|
|
|
|
|
return self.report("expected a closing `)` after expression");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.keyword_nil => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return .nil_literal;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.keyword_true => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return .true_literal;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.keyword_false => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return .false_literal;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.number => |value| {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{.number_literal = value};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.string => |value| {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{.string_literal = value};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.special_identifier => |identifier| {
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
var expression_list = Expression.List.make(allocator);
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
switch (tokenizer.token orelse return self.raise("expected expression or `)` after `(`")) {
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report("expected expression or `)` after `(`")) {
|
|
|
|
|
.symbol_paren_right => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{
|
|
|
|
|
.call_system = .{
|
|
|
|
@ -352,13 +367,13 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => {
|
|
|
|
|
try expression_list.push_one(try self.parse_expression(tokenizer));
|
|
|
|
|
try expression_list.push_one(try self.parse_expression());
|
|
|
|
|
|
|
|
|
|
switch (tokenizer.token orelse return self.raise("expected `,` or `)` after argument")) {
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report("expected `,` or `)` after argument")) {
|
|
|
|
|
.symbol_comma => continue,
|
|
|
|
|
|
|
|
|
|
.symbol_paren_right => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{
|
|
|
|
|
.call_system = .{
|
|
|
|
@ -368,7 +383,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => return self.raise("expected `,` or `)` after argument"),
|
|
|
|
|
else => return self.report("expected `,` or `)` after argument"),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
@ -376,7 +391,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.identifier => |identifier| {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{.get_local = identifier};
|
|
|
|
|
},
|
|
|
|
@ -384,83 +399,83 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
|
|
|
|
|
.symbol_brace_left => {
|
|
|
|
|
var table_fields = Expression.NamedList.make(allocator);
|
|
|
|
|
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
switch (tokenizer.token orelse return self.raise("unexpected end of table literal")) {
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report("unexpected end of table literal")) {
|
|
|
|
|
.symbol_brace_right => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{.table_literal = table_fields};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.identifier => |identifier| {
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
if (!tokenizer.is_token(.symbol_equals)) {
|
|
|
|
|
return self.raise("expected `=` after identifier");
|
|
|
|
|
if (!self.tokenizer.is_token(.symbol_equals)) {
|
|
|
|
|
return self.report("expected `=` after identifier");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
if (tokenizer.token == null) {
|
|
|
|
|
return self.raise("unexpected end after `=`");
|
|
|
|
|
if (self.tokenizer.token == null) {
|
|
|
|
|
return self.report("unexpected end after `=`");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try table_fields.push_one(.{
|
|
|
|
|
.expression = try self.parse_expression(tokenizer),
|
|
|
|
|
.expression = try self.parse_expression(),
|
|
|
|
|
.identifier = identifier,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
switch (tokenizer.token orelse return self.raise("unexpected end of table")) {
|
|
|
|
|
.symbol_comma => tokenizer.skip(.newline),
|
|
|
|
|
switch (self.tokenizer.token orelse return self.report("unexpected end of table")) {
|
|
|
|
|
.symbol_comma => self.tokenizer.skip(.newline),
|
|
|
|
|
|
|
|
|
|
.symbol_brace_right => {
|
|
|
|
|
tokenizer.step();
|
|
|
|
|
self.tokenizer.step();
|
|
|
|
|
|
|
|
|
|
return Expression{.table_literal = table_fields};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => return self.raise("expected `,` or `}` after expression"),
|
|
|
|
|
else => return self.report("expected `,` or `}` after expression"),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => return self.raise("expected `}` or fields in table literal"),
|
|
|
|
|
else => return self.report("expected `}` or fields in table literal"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.symbol_minus => {
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
if (tokenizer.token == null) {
|
|
|
|
|
return self.raise("expected expression after numeric negation (`-`)");
|
|
|
|
|
if (self.tokenizer.token == null) {
|
|
|
|
|
return self.report("expected expression after numeric negation (`-`)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Expression{
|
|
|
|
|
.unary_operation = .{
|
|
|
|
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor(tokenizer)),
|
|
|
|
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
|
|
|
|
.operator = .numeric_negation,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.symbol_bang => {
|
|
|
|
|
tokenizer.skip(.newline);
|
|
|
|
|
self.tokenizer.skip(.newline);
|
|
|
|
|
|
|
|
|
|
if (tokenizer.token == null) {
|
|
|
|
|
return self.raise("expected expression after boolean negation (`!`)");
|
|
|
|
|
if (self.tokenizer.token == null) {
|
|
|
|
|
return self.report("expected expression after boolean negation (`!`)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Expression{
|
|
|
|
|
.unary_operation = .{
|
|
|
|
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor(tokenizer)),
|
|
|
|
|
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
|
|
|
|
.operator = .boolean_negation,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => return self.raise("unexpected token in expression"),
|
|
|
|
|
else => return self.report("unexpected token in expression"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
There's no benefit to handling the tokenizer as external state being passed to the AST. It may as well just be part of the AST state internally.