Skip to content

Commit

Permalink
v1.2.4
Browse files Browse the repository at this point in the history
Added: indefinite article filter (although consonant detection is too basic)
Added: Inventory now supports an 'initial_contents'
Added: Inventory autocreates properties for each slot as '<slot_id>_contents'
Fixes: validate parents before allowing further compilation
Fixed: can't set an invalid card in a scene
Fixes: RezDecision result wasn't being set right
Fixes: Don't copy $... attributes from parents (e.g. $template)
Fixes: ^p property attributes broke on nested {}
Changed: improved property documentation
  • Loading branch information
mmower committed Feb 29, 2024
1 parent 3bdd70a commit 9e2ed74
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 48 deletions.
2 changes: 1 addition & 1 deletion assets/templates/runtime/rez_decision.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
function RezDecision(purpose, data = {}) {
this.purpose = purpose;
this.made = false;
this.decision = false;
this.result = false;
this.hidden = false;
this.reason = "";
this.data = data;
Expand Down
15 changes: 15 additions & 0 deletions assets/templates/runtime/rez_inventory.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ RezInventory.prototype = {
addSlot(slot_id) {
const items = this.getAttribute("items");
items[slot_id] = [];

Object.defineProperty(this, `${slot_id}_contents`, {
get: function () {
return this.getAttribute("items")[slot_id];
},
});
},

/**
Expand All @@ -42,6 +48,15 @@ RezInventory.prototype = {
for (const slot_id of slots) {
this.addSlot(slot_id);
}

const initial_contents = this.getAttribute("initial_contents");
if(typeof(initial_contents) === "object") {
for(const [slot_id, contents] of Object.entries(initial_contents)) {
for(const item_id of contents) {
this.addItemToSlot(slot_id, item_id);
}
}
}
},

/**
Expand Down
7 changes: 4 additions & 3 deletions assets/templates/runtime/rez_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ RezList.prototype = {
// Random without starvation (as per jkj yuio from intfiction.org)
//---------------------------------------------------------------------------

warmStarvationPool(pool_id) {
for(let i = 0; i<this.values.length; i++ ) {
warmStarvationPool(pool_id = "$default") {
const warming_count = 2*this.values.length;
for(let i = 0; i<warming_count; i++ ) {
this.randomWithoutStarvation(pool_id);
}
},

randomWithoutStarvation(pool_id) {
randomWithoutStarvation(pool_id = "$default") {
let stats = this.getAttributeValue(`$pool_${pool_id}`, Array.n_of(this.values.length, 0));
const len = stats.length;
const max = Math.floor(len + (len+2)/3);
Expand Down
2 changes: 1 addition & 1 deletion assets/templates/runtime/rez_scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ RezScene.prototype = {
},

playCardWithId(card_id, params = {}) {
this.playCard($(card_id), params);
this.playCard($(card_id, true), params);
},

playCard(new_card, params = {}) {
Expand Down
26 changes: 26 additions & 0 deletions assets/templates/stdlib.rez.eex
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@
}
}

@patch STRING_BEGINS_WITH_CONSONANT {
patch: "String"
method: "beginsWithConsonant"
impl: function() {
return /^[^aeiouAEIOU]/.test(this);
}
}

@patch STRING_BEGINS_WITH_VOWEL {
patch: "String"
method: "beginsWithVowel"
impl: function() {
return /^[aeiouAEIOU]/.test(string);
}
}

@patch STRING_CAPITALIZE {
patch: "String"
method: "capitalize"
Expand Down Expand Up @@ -729,6 +745,16 @@
impl: (s) => {return `"${s}"`;}
}

@filter STRING_INDEFINITE_ARTICLE_FILTER {
%% String -> String

name: "i_article"
impl: (s) => {
const article = s.beginsWithConsonant() ? "a" : "an";
return `${article} ${s}`;
}
}

@filter ARRAY_LENGTH_FILTER {
%% Array -> Integer

Expand Down
7 changes: 3 additions & 4 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# use Mix.Config
import Config

if Mix.env() == :dev do
config :mix_test_watch, clear: true, extra_extensions: [".rez"]
end
# if Mix.env() == :dev do
# config :mix_test_watch, clear: true, extra_extensions: [".rez"]
# end
32 changes: 30 additions & 2 deletions docs/REZ.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -449,18 +449,46 @@ It's not elegant but it's feasible. This will likely get cleaned up in a future

=== Dynamic Initializer

A dynamic initializer uses the form `^i{...}` to run an expression once at the time the object is created. This is useful for setting a generated value (e.g. a random value) after which the attribute behaves normally using getters/setters.
A dynamic initializer uses the form `^i{...}` to run an expression once at the time the object is created.

This is useful for setting a generated value (e.g. a random value) after which the attribute behaves normally using getters/setters.

Note that this is not a function, the initializer uses the last expression as the value. In the following example we name an actor using a randomly generated given & family name.

....
@actor random_npc {
name: ^i{
const given_name = $("given_names").randomElement();
const family_name = $("family_names").randomElement();
`${given_name} ${family_name}`;
}
}
....

=== Dynamic Value

A dynamic value uses the form `^v{...}` to create an expression that gets evaluated each time it is referenced. This should be mostly superceded by the use of `^p{...}` to create properties.

....
class_name: ^v{class == "g" ? "Gunslinger" : class =="s" ? "Sleuth" : "Crook"}
@actor random_npc {
class_name: ^v{class == "g" ? "Gunslinger" : class =="s" ? "Sleuth" : "Crook"}
}
....

Note that there is an implicit `return` statement to which this value code is appended.

=== Dynamic Property

A dynamic property is a property generated from an expression in the form `^p{}` for example:

....
@actor random_npc {
class_name: ^p{
return this.class === "g" ? "Gunslinger" : class === "s" ? "Sleuth" : "Crook";
}
}
....

== What's in a Game?

The simplest possible Rez game would look something like this:
Expand Down
10 changes: 9 additions & 1 deletion lib/AST/game.ex
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ defmodule Rez.AST.Game do
|> Map.put(:by_id, Map.put(by_id, child_id, child))
end

def recognises_id?(game, id) when is_atom(id) do
recognises_id?(game, to_string(id))
end

def recognises_id?(%Game{by_id: id_map}, id) when is_binary(id) do
Map.has_key?(id_map, id)
end

@doc """
## Examples
iex> alias Rez.AST.{Attribute, Game}
Expand Down Expand Up @@ -164,6 +172,7 @@ defmodule Rez.AST.Game do

# Due to their dependency locations & zones are init'd separately
@js_classes_to_init [
:lists,
:behaviours,
:actors,
:assets,
Expand All @@ -173,7 +182,6 @@ defmodule Rez.AST.Game do
:groups,
:inventories,
:items,
:lists,
:plots,
:relationships,
:scenes,
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/inventory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ defimpl Rez.AST.Node, for: Rez.AST.Inventory do
)
)
),
attribute_if_present?(
"initial_contents",
attribute_has_type?(:table)
),
attribute_if_present?(
"apply_effects",
attribute_has_type?(:boolean)
Expand Down
3 changes: 3 additions & 0 deletions lib/AST/node_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ defmodule Rez.AST.NodeHelper do
String.starts_with?(attribute.name, "_") ->
node

String.starts_with?(attribute.name, "$") ->
node

has_attr?(node, attribute.name) ->
node

Expand Down
68 changes: 58 additions & 10 deletions lib/AST/node_validator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,46 @@ defmodule Rez.AST.NodeValidator do

def validate(%Validation{} = validation) do
validation
|> validate_parents()
|> validate_specification()
|> validate_enums()
|> validate_children()
|> record_validation()
end

def validate_parents(%Validation{errors: [_head | _tail]} = validation) do
validation
end

def validate_parents(%Validation{game: %Game{} = game, node: node} = validation) do
case NodeHelper.get_attr_value(node, "_parents", []) do
[] ->
validation

parents ->
unknown_parents =
parents
|> Enum.map(fn {:keyword, parent_id} -> parent_id end)
|> Enum.filter(fn parent_id -> !Game.recognises_id?(game, parent_id) end)

case unknown_parents do
[] ->
validation

unknown_parents ->
Validation.add_error(
validation,
node,
"#{NodeHelper.description(node)} references unknown parents: #{Enum.join(unknown_parents, ", ")}"
)
end
end
end

def validate_specification(%Validation{errors: [_head | _tail]} = validation) do
validation
end

def validate_specification(%Validation{game: game, node: node} = pre_validation) do
node
|> Node.validators()
Expand All @@ -72,6 +106,10 @@ defmodule Rez.AST.NodeValidator do
)
end

def validate_enums(%Validation{errors: [_head | _tail]} = validation) do
validation
end

def validate_enums(
%Validation{game: %{enums: enums}, node: %{attributes: attributes} = node} =
pre_validation
Expand All @@ -98,6 +136,10 @@ defmodule Rez.AST.NodeValidator do
end)
end

def validate_children(%Validation{errors: [_head | _tail]} = validation) do
validation
end

def validate_children(%Validation{game: game, node: node} = parent_validation) do
Enum.reduce(
Node.children(node),
Expand All @@ -108,6 +150,10 @@ defmodule Rez.AST.NodeValidator do
)
end

def record_validation(%Validation{errors: [_head | _tail]} = validation) do
validation
end

def record_validation(%Validation{node: node, validated: validated} = validation) do
%{validation | validated: [NodeHelper.description(node) | validated]}
end
Expand Down Expand Up @@ -153,19 +199,17 @@ defmodule Rez.AST.NodeValidator do

def get_parents_of_node(node_map) do
fn %{attributes: attributes} = _node ->
default_no_parents = Attribute.list("_parents", [])

attributes
|> Map.get("_parents", default_no_parents)
|> Map.get("_parents", Attribute.list("_parents", []))
|> Map.get(:value)
|> Enum.map(fn {:keyword, parent_id} ->
Map.get(node_map, to_string(parent_id))
end)
end
end

def find_attribute(%Game{by_id: node_map}, node, attr_key)
when not is_nil(node) and is_binary(attr_key) do
def find_attribute(%Game{by_id: node_map}, %{} = node, attr_key)
when is_binary(attr_key) do
Search.search(
node,
find_attribute_in_node(attr_key),
Expand Down Expand Up @@ -194,12 +238,16 @@ defmodule Rez.AST.NodeValidator do

def attribute_if_present?(attr_key, chained_validator) when not is_nil(chained_validator) do
fn node, game ->
case find_attribute(game, node, attr_key) do
nil ->
:ok
if is_nil(game) do
{:error, "Game is null in attribute_if_present!"}
else
case find_attribute(game, node, attr_key) do
nil ->
:ok

%Attribute{} = attr ->
chained_validator.(attr, node, game)
%Attribute{} = attr ->
chained_validator.(attr, node, game)
end
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/parser/utility_parsers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ defmodule Rez.Parser.UtilityParsers do

def amp(), do: ParserCache.get_parser("ampersand", fn -> char(?&) end)

def percent(), do: ParserCache.get_parser("percent", fn -> char(?%) end)

def caret(), do: ParserCache.get_parser("caret", fn -> char(?^) end)

def forward_slash(), do: ParserCache.get_parser("forward_slash", fn -> char(?/) end)
Expand Down
2 changes: 1 addition & 1 deletion lib/parser/value_parsers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ defmodule Rez.Parser.ValueParsers do
[
ignore(caret()),
ignore(char(?p)),
text_delimited_by_parsers(open_brace(), close_brace())
text_delimited_by_nested_parsers(open_brace(), close_brace())
],
ast: fn [f] -> {:property, f} end
)
Expand Down
10 changes: 6 additions & 4 deletions lib/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ defmodule Rez.Utils do
The search_fn/1 takes a node and determines whether it matches the search
critera. If so it returns {:found, result} otherwise :not_found
"""
def search(node, search_fn, child_fn) when not is_nil(node) do
def search(%{} = node, search_fn, child_fn) do
case search_fn.(node) do
:not_found ->
find_in_children(node, search_fn, child_fn)
Expand All @@ -147,10 +147,12 @@ defmodule Rez.Utils do
end
end

defp find_in_children(node, search_fn, child_fn) do
defp find_in_children(node, search_fn, child_fn) when not is_nil(node) do
children = child_fn.(node)

Enum.find_value(
child_fn.(node),
fn child ->
children,
fn child when not is_nil(child) ->
search(child, search_fn, child_fn)
end
)
Expand Down
Loading

0 comments on commit 9e2ed74

Please sign in to comment.