feat: formatting strings at runtime
This commit is contained in:
parent
6924e929d8
commit
d2cd7fa888
10 changed files with 211 additions and 34 deletions
|
|
@ -29,7 +29,6 @@ pub const Node = struct {
|
||||||
number_literal,
|
number_literal,
|
||||||
string_literal,
|
string_literal,
|
||||||
string_expr,
|
string_expr,
|
||||||
empty_string,
|
|
||||||
identifier,
|
identifier,
|
||||||
add_expr,
|
add_expr,
|
||||||
subtract_expr,
|
subtract_expr,
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ fn nodeTagToString(tag: Ast.Node.Tag) []const u8 {
|
||||||
.true_literal => "TrueLiteral",
|
.true_literal => "TrueLiteral",
|
||||||
.number_literal => "NumberLiteral",
|
.number_literal => "NumberLiteral",
|
||||||
.string_literal => "StringLiteral",
|
.string_literal => "StringLiteral",
|
||||||
.empty_string => "EmptyString",
|
|
||||||
.identifier => "Identifier",
|
.identifier => "Identifier",
|
||||||
.add_expr => "AddExpr",
|
.add_expr => "AddExpr",
|
||||||
.subtract_expr => "SubtractExpr",
|
.subtract_expr => "SubtractExpr",
|
||||||
|
|
@ -348,7 +347,6 @@ fn renderAstWalk(
|
||||||
.true_literal,
|
.true_literal,
|
||||||
.number_literal,
|
.number_literal,
|
||||||
.string_literal,
|
.string_literal,
|
||||||
.empty_string,
|
|
||||||
.identifier,
|
.identifier,
|
||||||
.parameter_decl,
|
.parameter_decl,
|
||||||
.ref_parameter_decl,
|
.ref_parameter_decl,
|
||||||
|
|
@ -359,6 +357,7 @@ fn renderAstWalk(
|
||||||
.block_stmt,
|
.block_stmt,
|
||||||
.choice_stmt,
|
.choice_stmt,
|
||||||
.content_stmt,
|
.content_stmt,
|
||||||
|
.string_expr,
|
||||||
=> {
|
=> {
|
||||||
const data = node.data.list;
|
const data = node.data.list;
|
||||||
for (data.items) |child_node| try children.append(gpa, child_node);
|
for (data.items) |child_node| try children.append(gpa, child_node);
|
||||||
|
|
@ -416,7 +415,6 @@ fn renderAstWalk(
|
||||||
.knot_prototype,
|
.knot_prototype,
|
||||||
.function_decl,
|
.function_decl,
|
||||||
.stitch_decl,
|
.stitch_decl,
|
||||||
.string_expr,
|
|
||||||
.divert_expr,
|
.divert_expr,
|
||||||
.selector_expr,
|
.selector_expr,
|
||||||
.call_expr,
|
.call_expr,
|
||||||
|
|
|
||||||
|
|
@ -739,9 +739,29 @@ fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||||
return gi.addStr(str.index, str.len);
|
return gi.addStr(str.index, str.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stringExpr(gen: *GenIr, expr_node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
fn stringExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||||
const first_node = expr_node.data.bin.lhs.?;
|
const data = node.data.list;
|
||||||
return stringLiteral(gen, first_node);
|
const gpa = gi.astgen.gpa;
|
||||||
|
const scratch_top = gi.astgen.scratch.items.len;
|
||||||
|
|
||||||
|
for (data.items) |sub_node| {
|
||||||
|
const result = switch (sub_node.tag) {
|
||||||
|
.inline_logic_expr => try inlineLogicExpr(gi, scope, sub_node),
|
||||||
|
.string_literal => try stringLiteral(gi, sub_node),
|
||||||
|
inline else => |_| unreachable,
|
||||||
|
};
|
||||||
|
try gi.astgen.scratch.append(gpa, @intFromEnum(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
const multi_op_len = gi.astgen.scratch.items.len - scratch_top;
|
||||||
|
const extra_len = @typeInfo(Ir.Inst.MultiOp).@"struct".fields.len + multi_op_len;
|
||||||
|
|
||||||
|
try gi.astgen.extra.ensureUnusedCapacity(gpa, extra_len);
|
||||||
|
const extra_index = gi.astgen.addExtraAssumeCapacity(Ir.Inst.MultiOp{
|
||||||
|
.operands_len = @intCast(multi_op_len),
|
||||||
|
});
|
||||||
|
gi.astgen.appendBlockBody(@ptrCast(gi.astgen.scratch.items[scratch_top..]));
|
||||||
|
return gi.addPayloadNodeWithIndex(.str_format, node, extra_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identifier(
|
fn identifier(
|
||||||
|
|
@ -764,8 +784,7 @@ fn expr(gi: *GenIr, scope: *Scope, optional_node: ?*const Ast.Node) InnerError!I
|
||||||
.false_literal => return .bool_false,
|
.false_literal => return .bool_false,
|
||||||
.number_literal => return numberLiteral(gi, node),
|
.number_literal => return numberLiteral(gi, node),
|
||||||
.string_literal => return stringLiteral(gi, node),
|
.string_literal => return stringLiteral(gi, node),
|
||||||
.string_expr => return stringExpr(gi, node),
|
.string_expr => return stringExpr(gi, scope, node),
|
||||||
.empty_string => return stringLiteral(gi, node),
|
|
||||||
.identifier => return identifier(gi, scope, node),
|
.identifier => return identifier(gi, scope, node),
|
||||||
.add_expr => return binaryOp(gi, scope, node, .add),
|
.add_expr => return binaryOp(gi, scope, node, .add),
|
||||||
.subtract_expr => return binaryOp(gi, scope, node, .sub),
|
.subtract_expr => return binaryOp(gi, scope, node, .sub),
|
||||||
|
|
|
||||||
11
src/Ir.zig
11
src/Ir.zig
|
|
@ -55,6 +55,10 @@ pub fn nullTerminatedString(ir: Ir, index: NullTerminatedString) [:0]const u8 {
|
||||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refSlice(code: Ir, start: usize, len: usize) []Inst.Ref {
|
||||||
|
return @ptrCast(code.extra[start..][0..len]);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bodySlice(ir: Ir, start: usize, len: usize) []Inst.Index {
|
pub fn bodySlice(ir: Ir, start: usize, len: usize) []Inst.Index {
|
||||||
return @ptrCast(ir.extra[start..][0..len]);
|
return @ptrCast(ir.extra[start..][0..len]);
|
||||||
}
|
}
|
||||||
|
|
@ -195,6 +199,8 @@ pub const Inst = struct {
|
||||||
float,
|
float,
|
||||||
/// Uses the `str` union field.
|
/// Uses the `str` union field.
|
||||||
str,
|
str,
|
||||||
|
/// Uses the `payload` union field. Payload is `MultiOp`.
|
||||||
|
str_format,
|
||||||
/// Short-circuiting boolean `and`. `lhs` is a boolean `Ref` and the other operand
|
/// Short-circuiting boolean `and`. `lhs` is a boolean `Ref` and the other operand
|
||||||
/// is a block, which is evaluated if `lhs` is `true`.
|
/// is a block, which is evaluated if `lhs` is `true`.
|
||||||
/// Uses the `payload` union field. Payload is `BoolBr`.
|
/// Uses the `payload` union field. Payload is `BoolBr`.
|
||||||
|
|
@ -291,6 +297,10 @@ pub const Inst = struct {
|
||||||
field_name_start: NullTerminatedString,
|
field_name_start: NullTerminatedString,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const MultiOp = struct {
|
||||||
|
operands_len: u32,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Block = struct {
|
pub const Block = struct {
|
||||||
body_len: u32,
|
body_len: u32,
|
||||||
};
|
};
|
||||||
|
|
@ -383,6 +393,7 @@ pub const Inst = struct {
|
||||||
.int,
|
.int,
|
||||||
.float,
|
.float,
|
||||||
.str,
|
.str,
|
||||||
|
.str_format,
|
||||||
.block,
|
.block,
|
||||||
.condbr,
|
.condbr,
|
||||||
.switch_br,
|
.switch_br,
|
||||||
|
|
|
||||||
|
|
@ -679,22 +679,53 @@ fn parseExpression(p: *Parse) Error!?*Ast.Node {
|
||||||
|
|
||||||
fn parseStringExpr(p: *Parse) Error!*Ast.Node {
|
fn parseStringExpr(p: *Parse) Error!*Ast.Node {
|
||||||
assert(p.token.tag == .double_quote);
|
assert(p.token.tag == .double_quote);
|
||||||
|
const scratch_top = p.scratch.items.len;
|
||||||
const main_token = p.nextToken();
|
const main_token = p.nextToken();
|
||||||
|
var fragment_start = main_token.loc.end;
|
||||||
|
|
||||||
while (true) switch (p.token.tag) {
|
loop: while (true) switch (p.token.tag) {
|
||||||
.double_quote, .newline, .eof => break,
|
.double_quote, .newline, .eof => {
|
||||||
else => _ = p.nextToken(),
|
if (fragment_start < p.token.loc.start) {
|
||||||
|
const node = try Ast.Node.createLeaf(p.arena, .string_literal, .{
|
||||||
|
.start = fragment_start,
|
||||||
|
.end = p.token.loc.start,
|
||||||
|
});
|
||||||
|
try p.scratch.append(p.gpa, node);
|
||||||
|
}
|
||||||
|
break :loop;
|
||||||
|
},
|
||||||
|
.left_brace => {
|
||||||
|
if (fragment_start < p.token.loc.start) {
|
||||||
|
const node = try Ast.Node.createLeaf(p.arena, .string_literal, .{
|
||||||
|
.start = fragment_start,
|
||||||
|
.end = p.token.loc.start,
|
||||||
|
});
|
||||||
|
try p.scratch.append(p.gpa, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lbrace_token = p.nextToken();
|
||||||
|
const expr = try p.expectExpr();
|
||||||
|
const rbrace_token = try p.expectToken(.right_brace, false);
|
||||||
|
const node = try Ast.Node.createBinary(p.arena, .inline_logic_expr, .{
|
||||||
|
.start = lbrace_token.loc.start,
|
||||||
|
.end = rbrace_token.loc.end,
|
||||||
|
}, expr, null);
|
||||||
|
|
||||||
|
try p.scratch.append(p.gpa, node);
|
||||||
|
fragment_start = rbrace_token.loc.end;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
_ = p.nextToken();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const last_token = try p.expectToken(.double_quote, true);
|
const last_token = try p.expectToken(.double_quote, true);
|
||||||
const expr = try Ast.Node.createLeaf(p.arena, .string_literal, .{
|
const list = try p.makeNodeSliceFromScratch(scratch_top);
|
||||||
.start = main_token.loc.end,
|
|
||||||
.end = last_token.loc.start,
|
return .createList(p.arena, .string_expr, .{
|
||||||
});
|
|
||||||
return .createBinary(p.arena, .string_expr, .{
|
|
||||||
.start = main_token.loc.start,
|
.start = main_token.loc.start,
|
||||||
.end = p.token.loc.start,
|
.end = last_token.loc.end,
|
||||||
}, expr, null);
|
}, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseExprStmt(p: *Parse, lhs: ?*Ast.Node) Error!*Ast.Node {
|
fn parseExprStmt(p: *Parse, lhs: ?*Ast.Node) Error!*Ast.Node {
|
||||||
|
|
|
||||||
25
src/Sema.zig
25
src/Sema.zig
|
|
@ -425,6 +425,30 @@ fn irStr(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
||||||
return .{ .value = ip_index };
|
return .{ .value = ip_index };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn irStrFormat(
|
||||||
|
sema: *Sema,
|
||||||
|
builder: *Builder,
|
||||||
|
_: *Block,
|
||||||
|
inst: Ir.Inst.Index,
|
||||||
|
) InnerError!ValueInfo {
|
||||||
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||||
|
const extra = sema.ir.extraData(Ir.Inst.MultiOp, data.extra_index);
|
||||||
|
const args_slice = sema.ir.refSlice(extra.end, extra.data.operands_len);
|
||||||
|
|
||||||
|
try builder.addByteOp(.string_builder);
|
||||||
|
|
||||||
|
var index: usize = 0;
|
||||||
|
while (index < args_slice.len) : (index += 1) {
|
||||||
|
const arg = args_slice[index];
|
||||||
|
const arg_inst = sema.resolveInst(arg);
|
||||||
|
try builder.ensureLoad(arg_inst);
|
||||||
|
try builder.addByteOp(.string_append);
|
||||||
|
}
|
||||||
|
|
||||||
|
try builder.addByteOp(.string_freeze);
|
||||||
|
return .stack;
|
||||||
|
}
|
||||||
|
|
||||||
fn irUnaryOp(
|
fn irUnaryOp(
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
builder: *Builder,
|
builder: *Builder,
|
||||||
|
|
@ -1081,6 +1105,7 @@ fn analyzeBodyInner(
|
||||||
.int => try irInt(sema, inst),
|
.int => try irInt(sema, inst),
|
||||||
.float => try irFloat(sema, inst),
|
.float => try irFloat(sema, inst),
|
||||||
.str => try irStr(sema, inst),
|
.str => try irStr(sema, inst),
|
||||||
|
.str_format => try irStrFormat(sema, builder, block, inst),
|
||||||
.add => try irBinaryOp(sema, builder, inst, .add),
|
.add => try irBinaryOp(sema, builder, inst, .add),
|
||||||
.sub => try irBinaryOp(sema, builder, inst, .sub),
|
.sub => try irBinaryOp(sema, builder, inst, .sub),
|
||||||
.mul => try irBinaryOp(sema, builder, inst, .mul),
|
.mul => try irBinaryOp(sema, builder, inst, .mul),
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ stack: []Value = &.{},
|
||||||
call_stack: []CallFrame = &.{},
|
call_stack: []CallFrame = &.{},
|
||||||
code_chunks: std.ArrayListUnmanaged(*Object.Code) = .empty,
|
code_chunks: std.ArrayListUnmanaged(*Object.Code) = .empty,
|
||||||
/// Linked list of all tracked runtime objects.
|
/// Linked list of all tracked runtime objects.
|
||||||
|
/// We don't currently have a garbage collector, though this will necessary.
|
||||||
gc_objects: std.SinglyLinkedList = .{},
|
gc_objects: std.SinglyLinkedList = .{},
|
||||||
/// Global constants pool.
|
/// Global constants pool.
|
||||||
constants_pool: []const Value = &.{},
|
constants_pool: []const Value = &.{},
|
||||||
|
|
@ -110,6 +111,9 @@ pub const Opcode = enum(u8) {
|
||||||
br_table,
|
br_table,
|
||||||
br_dispatch,
|
br_dispatch,
|
||||||
br_select_index,
|
br_select_index,
|
||||||
|
string_builder,
|
||||||
|
string_append,
|
||||||
|
string_freeze,
|
||||||
_,
|
_,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -138,6 +142,13 @@ pub const Value = union(enum) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn castObject(v: Value, comptime T: type) *T {
|
||||||
|
return switch (v) {
|
||||||
|
.object => |object| return @ptrCast(object),
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn isNumeric(v: Value) bool {
|
pub fn isNumeric(v: Value) bool {
|
||||||
return v == .int or v == .float;
|
return v == .int or v == .float;
|
||||||
}
|
}
|
||||||
|
|
@ -230,24 +241,20 @@ pub const Value = union(enum) {
|
||||||
pub fn eql(lhs: Value, rhs: Value) bool {
|
pub fn eql(lhs: Value, rhs: Value) bool {
|
||||||
return switch (lhs) {
|
return switch (lhs) {
|
||||||
.nil => rhs == .nil,
|
.nil => rhs == .nil,
|
||||||
|
|
||||||
.bool => |l| switch (rhs) {
|
.bool => |l| switch (rhs) {
|
||||||
.bool => |r| l == r,
|
.bool => |r| l == r,
|
||||||
else => false,
|
else => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
.int => |l| switch (rhs) {
|
.int => |l| switch (rhs) {
|
||||||
.int => |r| l == r,
|
.int => |r| l == r,
|
||||||
.float => |r| @as(f64, @floatFromInt(l)) == r,
|
.float => |r| @as(f64, @floatFromInt(l)) == r,
|
||||||
else => false,
|
else => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
.float => |l| switch (rhs) {
|
.float => |l| switch (rhs) {
|
||||||
.int => |r| l == @as(f64, @floatFromInt(r)),
|
.int => |r| l == @as(f64, @floatFromInt(r)),
|
||||||
.float => |r| l == r,
|
.float => |r| l == r,
|
||||||
else => false,
|
else => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
.object => |lobj| switch (rhs) {
|
.object => |lobj| switch (rhs) {
|
||||||
.object => |robj| Object.eql(lobj, robj),
|
.object => |robj| Object.eql(lobj, robj),
|
||||||
else => false,
|
else => false,
|
||||||
|
|
@ -771,6 +778,32 @@ fn step(vm: *Story, variables: *VariablesState) !StepSignal {
|
||||||
return error.InvalidArgument;
|
return error.InvalidArgument;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
.string_builder => {
|
||||||
|
const builder_object = try Object.StringBuilder.create(vm);
|
||||||
|
try pushStack(vm, .{ .object = &builder_object.base });
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.string_append => {
|
||||||
|
if (popStack(vm)) |value| {
|
||||||
|
if (peekStack(vm, 0)) |builder| {
|
||||||
|
const string_builder = builder.castObject(Object.StringBuilder);
|
||||||
|
try string_builder.append(value);
|
||||||
|
frame.ip += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error.InvalidArgument;
|
||||||
|
},
|
||||||
|
.string_freeze => {
|
||||||
|
if (popStack(vm)) |value| {
|
||||||
|
const string_builder = value.castObject(Object.StringBuilder);
|
||||||
|
const frozen = try string_builder.freeze(vm);
|
||||||
|
try pushStack(vm, .{ .object = &frozen.base });
|
||||||
|
frame.ip += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return error.InvalidArgument;
|
||||||
|
},
|
||||||
else => return error.InvalidInstruction,
|
else => return error.InvalidInstruction,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ fn dumpByteInst(
|
||||||
op: Opcode,
|
op: Opcode,
|
||||||
) !usize {
|
) !usize {
|
||||||
const code = knot.code;
|
const code = knot.code;
|
||||||
|
|
||||||
assert(code.bytecode.len > offset + 1);
|
assert(code.bytecode.len > offset + 1);
|
||||||
const arg = code.bytecode[offset + 1];
|
const arg = code.bytecode[offset + 1];
|
||||||
if (op == .load_const) {
|
if (op == .load_const) {
|
||||||
|
|
@ -162,6 +161,9 @@ pub fn dumpInst(
|
||||||
.br_table => return self.dumpSimpleInst(w, offset, op),
|
.br_table => return self.dumpSimpleInst(w, offset, op),
|
||||||
.br_dispatch => return self.dumpSimpleInst(w, offset, op),
|
.br_dispatch => return self.dumpSimpleInst(w, offset, op),
|
||||||
.br_select_index => return self.dumpSimpleInst(w, offset, op),
|
.br_select_index => return self.dumpSimpleInst(w, offset, op),
|
||||||
|
.string_builder => return self.dumpSimpleInst(w, offset, op),
|
||||||
|
.string_append => return self.dumpSimpleInst(w, offset, op),
|
||||||
|
.string_freeze => return self.dumpSimpleInst(w, offset, op),
|
||||||
else => |code| {
|
else => |code| {
|
||||||
try w.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)});
|
try w.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)});
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ pub const Tag = enum {
|
||||||
string,
|
string,
|
||||||
code,
|
code,
|
||||||
knot,
|
knot,
|
||||||
|
string_builder,
|
||||||
|
|
||||||
pub fn tagBytes(tag: Tag) []const u8 {
|
pub fn tagBytes(tag: Tag) []const u8 {
|
||||||
return switch (tag) {
|
return switch (tag) {
|
||||||
.string => "String",
|
.string => "String",
|
||||||
.code => "Code",
|
.code => "Code",
|
||||||
.knot => "Knot",
|
.knot => "Knot",
|
||||||
|
.string_builder => "StringBuilder",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,6 +31,7 @@ pub const Tag = enum {
|
||||||
.string => Object.String,
|
.string => Object.String,
|
||||||
.code => Object.Code,
|
.code => Object.Code,
|
||||||
.knot => Object.Knot,
|
.knot => Object.Knot,
|
||||||
|
.string_builder => Object.StringBuilder,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -43,6 +46,7 @@ pub fn eql(lhs: *Object, rhs: *Object) bool {
|
||||||
},
|
},
|
||||||
.code => |_| false,
|
.code => |_| false,
|
||||||
.knot => |_| false,
|
.knot => |_| false,
|
||||||
|
.string_builder => |_| false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,9 +75,8 @@ pub const String = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.String {
|
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.String {
|
||||||
const gpa = story.gpa;
|
|
||||||
const alloc_len = @sizeOf(Type) + options.bytes.len + 1;
|
const alloc_len = @sizeOf(Type) + options.bytes.len + 1;
|
||||||
const raw = try gpa.alignedAlloc(u8, .of(Type), alloc_len);
|
const raw = try story.gpa.alignedAlloc(u8, .of(Type), alloc_len);
|
||||||
const object: *Type = @ptrCast(raw);
|
const object: *Type = @ptrCast(raw);
|
||||||
|
|
||||||
object.* = .{
|
object.* = .{
|
||||||
|
|
@ -88,16 +91,14 @@ pub const String = struct {
|
||||||
object.bytes = buf.ptr;
|
object.bytes = buf.ptr;
|
||||||
@memcpy(buf[0..options.bytes.len], options.bytes);
|
@memcpy(buf[0..options.bytes.len], options.bytes);
|
||||||
buf[options.bytes.len] = 0;
|
buf[options.bytes.len] = 0;
|
||||||
|
|
||||||
story.gc_objects.prepend(&object.base.node);
|
story.gc_objects.prepend(&object.base.node);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(obj: *String, story: *Story) void {
|
pub fn destroy(obj: *String, story: *Story) void {
|
||||||
const gpa = story.gpa;
|
|
||||||
const alloc_len = @sizeOf(Type) + obj.length + 1;
|
const alloc_len = @sizeOf(Type) + obj.length + 1;
|
||||||
const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj);
|
const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj);
|
||||||
gpa.free(base[0..alloc_len]);
|
story.gpa.free(base[0..alloc_len]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toSlice(obj: *const Object.String) []const u8 {
|
pub fn toSlice(obj: *const Object.String) []const u8 {
|
||||||
|
|
@ -123,10 +124,9 @@ pub const String = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn concat(story: *Story, lhs: *String, rhs: *String) !*Object.String {
|
pub fn concat(story: *Story, lhs: *String, rhs: *String) !*Object.String {
|
||||||
const gpa = story.gpa;
|
|
||||||
const length = lhs.length + rhs.length;
|
const length = lhs.length + rhs.length;
|
||||||
const bytes = try gpa.alloc(u8, length + 1);
|
const bytes = try story.gpa.alloc(u8, length + 1);
|
||||||
defer gpa.free(bytes);
|
defer story.gpa.free(bytes);
|
||||||
|
|
||||||
@memcpy(bytes[0..lhs.length], lhs.bytes[0..lhs.length]);
|
@memcpy(bytes[0..lhs.length], lhs.bytes[0..lhs.length]);
|
||||||
@memcpy(bytes[lhs.length..], rhs.bytes[0..rhs.length]);
|
@memcpy(bytes[lhs.length..], rhs.bytes[0..rhs.length]);
|
||||||
|
|
@ -230,3 +230,41 @@ pub const Knot = struct {
|
||||||
gpa.free(base[0..alloc_len]);
|
gpa.free(base[0..alloc_len]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const StringBuilder = struct {
|
||||||
|
base: Object,
|
||||||
|
inner: std.Io.Writer.Allocating,
|
||||||
|
|
||||||
|
const Type = StringBuilder;
|
||||||
|
|
||||||
|
pub fn create(story: *Story) error{OutOfMemory}!*Object.StringBuilder {
|
||||||
|
const raw = try story.gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type));
|
||||||
|
const obj: *Type = @ptrCast(raw);
|
||||||
|
obj.* = .{
|
||||||
|
.base = .{ .tag = .string_builder },
|
||||||
|
.inner = .init(story.gpa),
|
||||||
|
};
|
||||||
|
|
||||||
|
story.gc_objects.prepend(&obj.base.node);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(obj: *StringBuilder, story: *Story) void {
|
||||||
|
obj.inner.deinit();
|
||||||
|
const alloc_len = @sizeOf(Type);
|
||||||
|
const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj);
|
||||||
|
story.gpa.free(base[0..alloc_len]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(obj: *StringBuilder, value: Story.Value) error{OutOfMemory}!void {
|
||||||
|
return obj.inner.writer.print("{f}", .{value}) catch |err| switch (err) {
|
||||||
|
error.WriteFailed => error.OutOfMemory,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn freeze(obj: *StringBuilder, story: *Story) error{OutOfMemory}!*Object.String {
|
||||||
|
const str_bytes = try obj.inner.toOwnedSlice();
|
||||||
|
defer story.gpa.free(str_bytes);
|
||||||
|
return .create(story, .{ .bytes = str_bytes });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Ir = @import("Ir.zig");
|
const Ir = @import("Ir.zig");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
pub const Writer = struct {
|
pub const Writer = struct {
|
||||||
code: Ir,
|
code: Ir,
|
||||||
|
|
@ -58,11 +59,30 @@ pub const Writer = struct {
|
||||||
try w.print("{d}", .{data});
|
try w.print("{d}", .{data});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeStringInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
fn writeStrInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
||||||
const data = self.code.instructions[@intFromEnum(inst)].data.str;
|
const data = self.code.instructions[@intFromEnum(inst)].data.str;
|
||||||
try self.writeStringRef(w, data.start);
|
try self.writeStringRef(w, data.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn writeMultiOpInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
||||||
|
const data = self.code.instructions[@intFromEnum(inst)].data.payload;
|
||||||
|
const extra = self.code.extraData(Ir.Inst.MultiOp, data.extra_index);
|
||||||
|
const body = self.code.refSlice(extra.end, extra.data.operands_len);
|
||||||
|
assert(body.len != 0);
|
||||||
|
|
||||||
|
try w.writeAll("{");
|
||||||
|
|
||||||
|
var index: u32 = 0;
|
||||||
|
if (body.len > 1) {
|
||||||
|
while (index < body.len - 1) : (index += 1) {
|
||||||
|
try self.writeInstRef(w, body[index]);
|
||||||
|
try w.writeAll(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try self.writeInstRef(w, body[index]);
|
||||||
|
try w.writeAll("}");
|
||||||
|
}
|
||||||
|
|
||||||
fn writeStrTokInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
fn writeStrTokInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
||||||
const data = self.code.instructions[@intFromEnum(inst)].data.str_tok;
|
const data = self.code.instructions[@intFromEnum(inst)].data.str_tok;
|
||||||
try self.writeStringRef(w, data.start);
|
try self.writeStringRef(w, data.start);
|
||||||
|
|
@ -357,7 +377,8 @@ pub const Writer = struct {
|
||||||
.cmp_lte => try self.writeBinaryInst(w, inst),
|
.cmp_lte => try self.writeBinaryInst(w, inst),
|
||||||
.int => try self.writeIntInst(w, inst),
|
.int => try self.writeIntInst(w, inst),
|
||||||
.float => try self.writeFloatInst(w, inst),
|
.float => try self.writeFloatInst(w, inst),
|
||||||
.str => try self.writeStringInst(w, inst),
|
.str => try self.writeStrInst(w, inst),
|
||||||
|
.str_format => try self.writeMultiOpInst(w, inst),
|
||||||
.content_push => try self.writeUnaryInst(w, inst),
|
.content_push => try self.writeUnaryInst(w, inst),
|
||||||
.content_line => try self.writeUnaryInst(w, inst),
|
.content_line => try self.writeUnaryInst(w, inst),
|
||||||
.content_glue => try self.writeUnaryInst(w, inst),
|
.content_glue => try self.writeUnaryInst(w, inst),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue