Skip to content

Commit

Permalink
feat: Optimize engine in javascript
Browse files Browse the repository at this point in the history
Engine is no longer being compiled to a
recursive function. Which means that we no longer
depend on the JS engine doing tail call optimization
correctly. Which it usually didn't.
  • Loading branch information
Olian04 committed Jul 10, 2024
1 parent 5a5f2e7 commit bda14e3
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 59 deletions.
1 change: 1 addition & 0 deletions src/handles.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ pub fn run(
})

engine.run(ast, ctx, partials_dict, string_builder.new())
|> result.map(string_builder.to_string)
}
179 changes: 120 additions & 59 deletions src/handles/internal/engine.gleam
Original file line number Diff line number Diff line change
@@ -1,37 +1,132 @@
import gleam/bool
import gleam/dict
import gleam/result
import gleam/list
import gleam/string_builder
import handles/ctx
import handles/error
import handles/internal/ctx_utils
import handles/internal/parser

fn run_each(
ctxs: List(ctx.Value),
ast: List(parser.AST),
builder: string_builder.StringBuilder,
partials: dict.Dict(String, List(parser.AST)),
) -> Result(String, error.RuntimeError) {
case ctxs {
[] -> builder |> string_builder.to_string |> Ok
[ctx, ..rest] ->
run(ast, ctx, partials, string_builder.new())
|> result.try(fn(it) {
run_each(rest, ast, builder |> string_builder.append(it), partials)
})
}
type Action {
Append(String)
SetCtx(ctx.Value)
Continue(List(parser.AST))
Stop(error.RuntimeError)
}

fn run_if(
bool: Bool,
children: List(parser.AST),
pub fn eval(
actions: List(Action),
ctx: ctx.Value,
partials: dict.Dict(String, List(parser.AST)),
) -> Result(String, error.RuntimeError) {
case bool {
False -> Ok("")
True -> run(children, ctx, partials, string_builder.new())
builder: string_builder.StringBuilder,
) -> Result(string_builder.StringBuilder, error.RuntimeError) {
case actions {
[] -> Ok(builder)

[Stop(err)] -> Error(err)

[Stop(err), ..] -> Error(err)

[Append(value), ..rest_action] ->
eval(rest_action, ctx, partials, string_builder.append(builder, value))

[SetCtx(ctx), ..rest_action] -> eval(rest_action, ctx, partials, builder)

[Continue([]), ..rest_action] -> eval(rest_action, ctx, partials, builder)

[Continue([parser.Constant(_, value), ..rest_ast]), ..rest_action] ->
eval(
[Append(value), Continue(rest_ast), ..rest_action],
ctx,
partials,
builder,
)

[Continue([parser.Property(index, path), ..rest_ast]), ..rest_action] ->
case ctx_utils.get_property(path, ctx, index) {
Ok(str) ->
eval(
[Append(str), Continue(rest_ast), ..rest_action],
ctx,
partials,
builder,
)
Error(err) -> eval([Stop(err)], ctx, partials, builder)
}

[Continue([parser.Partial(index, id, path), ..rest_ast]), ..rest_action] ->
case dict.get(partials, id) {
Error(_) ->
eval([Stop(error.UnknownPartial(index, id))], ctx, partials, builder)
Ok(partial) ->
case ctx_utils.get(path, ctx, index) {
Error(err) -> eval([Stop(err)], ctx, partials, builder)
Ok(new_ctx) ->
eval(
[
SetCtx(new_ctx),
Continue(partial),
SetCtx(ctx),
Continue(rest_ast),
..rest_action
],
ctx,
partials,
builder,
)
}
}
[
Continue([parser.IfBlock(index, path, children), ..rest_ast]),
..rest_action
] ->
case ctx_utils.get_bool(path, ctx, index) {
Error(err) -> eval([Stop(err)], ctx, partials, builder)
Ok(False) ->
eval([Continue(rest_ast), ..rest_action], ctx, partials, builder)
Ok(True) ->
eval(
[Continue(children), Continue(rest_ast), ..rest_action],
ctx,
partials,
builder,
)
}
[
Continue([parser.UnlessBlock(index, path, children), ..rest_ast]),
..rest_action
] ->
case ctx_utils.get_bool(path, ctx, index) {
Error(err) -> eval([Stop(err)], ctx, partials, builder)
Ok(True) ->
eval([Continue(rest_ast), ..rest_action], ctx, partials, builder)
Ok(False) ->
eval(
[Continue(children), Continue(rest_ast), ..rest_action],
ctx,
partials,
builder,
)
}
[
Continue([parser.EachBlock(index, path, children), ..rest_ast]),
..rest_action
] ->
case ctx_utils.get_list(path, ctx, index) {
Error(err) -> eval([Stop(err)], ctx, partials, builder)
Ok([]) ->
eval([Continue(rest_ast), ..rest_action], ctx, partials, builder)
Ok(ctxs) ->
eval(
ctxs
|> list.flat_map(fn(new_ctx) {
[SetCtx(new_ctx), Continue(children), SetCtx(ctx)]
})
|> list.append([Continue(rest_ast), ..rest_action]),
ctx,
partials,
builder,
)
}
}
}

Expand All @@ -40,40 +135,6 @@ pub fn run(
ctx: ctx.Value,
partials: dict.Dict(String, List(parser.AST)),
builder: string_builder.StringBuilder,
) -> Result(String, error.RuntimeError) {
case ast {
[] -> builder |> string_builder.to_string |> Ok
[node, ..rest] ->
case node {
parser.Constant(_, value) -> Ok(value)
parser.Property(index, path) -> ctx_utils.get_property(path, ctx, index)
parser.Partial(index, id, path) ->
case dict.get(partials, id) {
Error(_) -> Error(error.UnknownPartial(index, id))
Ok(partial) ->
ctx_utils.get(path, ctx, index)
|> result.try(run(partial, _, partials, string_builder.new()))
}

parser.IfBlock(index, path, children) ->
ctx_utils.get_bool(path, ctx, index)
|> result.try(run_if(_, children, ctx, partials))
parser.UnlessBlock(index, path, children) ->
ctx_utils.get_bool(path, ctx, index)
|> result.map(bool.negate)
|> result.try(run_if(_, children, ctx, partials))
parser.EachBlock(index, path, children) ->
ctx_utils.get_list(path, ctx, index)
|> result.try(run_each(_, children, string_builder.new(), partials))
}
|> result.try(fn(it) {
run(
rest,
ctx,
partials,
builder
|> string_builder.append(it),
)
})
}
) -> Result(string_builder.StringBuilder, error.RuntimeError) {
eval([Continue(ast)], ctx, partials, builder)
}
10 changes: 10 additions & 0 deletions test/unit_tests/engine_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub fn hello_world_test() {
[parser.Constant(0, "Hello World")]
|> engine.run(ctx.Dict([]), dict.new(), string_builder.new())
|> should.be_ok
|> string_builder.to_string
|> should.equal("Hello World")
}

Expand All @@ -20,13 +21,15 @@ pub fn hello_name_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("Hello Oliver")
}

pub fn self_tag_test() {
[parser.Property(0, [])]
|> engine.run(ctx.Str("Hello"), dict.new(), string_builder.new())
|> should.be_ok
|> string_builder.to_string
|> should.equal("Hello")
}

Expand All @@ -38,6 +41,7 @@ pub fn nested_property_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("42")
}

Expand All @@ -52,6 +56,7 @@ pub fn truthy_if_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("42")
}

Expand All @@ -63,6 +68,7 @@ pub fn falsy_if_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("")
}

Expand All @@ -74,6 +80,7 @@ pub fn truthy_unless_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("")
}

Expand All @@ -88,6 +95,7 @@ pub fn falsy_unless_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("42")
}

Expand Down Expand Up @@ -115,6 +123,7 @@ pub fn each_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("They are Knatte, Fnatte, Tjatte, and Kalle")
}

Expand All @@ -131,5 +140,6 @@ pub fn empty_each_test() {
string_builder.new(),
)
|> should.be_ok
|> string_builder.to_string
|> should.equal("")
}
1 change: 1 addition & 0 deletions test/user_stories/hello_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ pub fn parser_test() {
pub fn engine_test() {
engine.run(expected_ast, input_context, dict.new(), string_builder.new())
|> should.be_ok
|> string_builder.to_string
|> should.equal(expected_output)
}
1 change: 1 addition & 0 deletions test/user_stories/knattarna_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ pub fn parser_test() {
pub fn engine_test() {
engine.run(expected_ast, input_context, dict.new(), string_builder.new())
|> should.be_ok
|> string_builder.to_string
|> should.equal(expected_output)
}
1 change: 1 addition & 0 deletions test/user_stories/nested_block_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,6 @@ pub fn parser_test() {
pub fn engine_test() {
engine.run(expected_ast, input_context, dict.new(), string_builder.new())
|> should.be_ok
|> string_builder.to_string
|> should.equal(expected_output)
}

0 comments on commit bda14e3

Please sign in to comment.