Category talk:Wren-dynamic: Difference between revisions

m
Now uses Wren S/H lexer.
(→‎Source code: Flags enums now support an optional zero value member.)
m (Now uses Wren S/H lexer.)
 
(4 intermediate revisions by the same user not shown)
Line 1:
===Generation of classes at runtime===
 
There is often a need in Wren programming for simple classes which would be represented in some other languages by enums, 'flags' enums, constant groups, tuples, structs or unions. None of these are supported directly by Wren and, although they can be easily simulated, they are rather tedious to write.
 
This module aims to rectify this by providing templates to generate such classes at runtime for simple cases. In particular:
Line 7:
* Enum values always start from an initial integer value (often 0) and are incremented by 1 which is the commonest case.
* 'Flags' enums are always powers of 2, starting from 1 (= 2^0).
* Groups represent other groupings of related named constants of any type.
* Structs and tuples both represent data classes but fields for the former are read/write and for the latter read only.
* Union represents a value which can be any one of a set of types. A single storage location stores the value or a reference thereto.
Line 13 ⟶ 14:
To create for example a Point tuple, one could proceed as follows:
 
<langsyntaxhighlight ecmascriptlang="wren">import "/dynamic" for Tuple
 
var Point = Tuple.create("Point", ["x", "y"])
var p = Point.new(1, 2)
System.print([p.x, p.y, p]) // [1, 2, (1, 2)]</langsyntaxhighlight>
 
More complicated cases than these are best dealt with by manual programming as at present.
 
===Source code===
<langsyntaxhighlight ecmascriptlang="wren">/* Module "dynamic.wren" */
 
import "meta" for Meta
Line 30 ⟶ 31:
The enum has:
1. static property getters for each member,
2. a static 'startsFrom' property, and
3. a static 'members' property which returns a list of its members as strings.,
4. a static '[member]' indexer which returns its integer value from its member name (string), and
5. a static 'fromValue(v)' method which returns the member name (string) from its integer value.
*/
class Enum {
Line 50 ⟶ 53:
var mems = members.map { |m| "\"%(m)\"" }.join(", ")
s = s + " static startsFrom { %(startsFrom) }\n"
s = s + " static members { [%(mems)] }\n}\n"
s = s + " static [member] {\n"
s = s + " var ix = members.indexOf(member)\n"
s = s + " return (ix >= 0) ? ix + %(startsFrom) : null\n"
s = s + " }\n"
s = s + " static fromValue(v) {\n"
s = s + " if (!v.isInteger || v < %(startsFrom) || v >= %(startsFrom) + members.count) {\n"
s = s + " return null\n"
s = s + " }\n"
s = s + " return members[v - %(startsFrom)]\n"
s = s + " }\n}\n"
s = s + "return %(name)"
 
return Meta.compile(s).call()
}
Line 64 ⟶ 78:
The flags enum has:
1. static property getters for each member,
2. a static 'hasZero' property which returns whether or not there is a zero member, and
3. a static 'members' property which returns a list of its members as strings.,
4. a static '[member]' indexer which returns its integer value from its member name (string), and
5. a static 'fromValue(v)' method which returns the member name (string) from its integer value.
*/
class Flags {
Line 81 ⟶ 97:
var m = members[i]
if (i == 0 && hasZero) {
s = s + " static %(m) { 0 }\n"
} else if (hasZero) {
s = s + " static %(m) { %(1 << (i-1)) }\n"
Line 90 ⟶ 106:
var mems = members.map { |m| "\"%(m)\"" }.join(", ")
s = s + " static hasZero { %(hasZero) }\n"
s = s + " static members { [%(mems)] }\n}\n"
s = s + " static [member] {\n"
s = s + " var ix = members.indexOf(member)\n"
returnif (hasZero) ? res + 1 : res{
s = s + " if (ix == 0) return 0\n"
s = s + " return 1 << (ix-1)\n"
} else {
s = s + " return 1 << ix\n"
}
s = s + " }\n"
s = s + " static fromValue(v) {\n"
if (hasZero) {
s = s + " if (v == 0) return members[0]\n"
s = s + " if (!v.isInteger || v < 1 || v & (v-1) != 0) return null\n"
s = s + " var ix = (v.log / 2.log).round\n"
s = s + " if (ix + 1 >= members.count) return null\n"
s = s + " return members[ix+1]\n"
} else {
s = s + " if (!v.isInteger || v < 1 || v & (v-1) != 0) return null\n"
s = s + " var ix = (v.log / 2.log).round\n"
s = s + " if (ix >= members.count) return null\n"
s = s + " return members[ix]\n"
}
s = s + " }\n}\n"
s = s + "return %(name)"
return Meta.compile(s).call()
Line 97 ⟶ 136:
// Convenience version of above method which does not have a member with a value of zero.
static create(name, members) { create(name, members, false) }
}
 
/* Group creates a group of related named constants of any type.
// Returns the zero based index into the Fields property
The group has:
// for a given Flags enum member and whether or not there is a zero member.
1. static //property Returnsgetters -1for if theeach member doesn't exist.,
2. a static 'members' property which returns a list of its members as strings, and
static indexOf(member, hasZero, memCount) {
3. a static 'values' property which returns a list of their corresponding values.
if (hasZero.type != Bool) Fiber.abort("'hasZero' must be true or false.")
*/
var maxCount = hasZero ? 33 : 32
class Group {
if (memCount.type != Num || !memCount.isInteger || memCount < 1 || memCount > maxCount) {
// Creates a class for the Group (with an underscore after the name) and
Fiber.abort("Member count must be between 1 and %(maxCount).")
// returns a reference to it.
static create(name, members, values) {
if (hasZeroname.type != BoolString || name == "") Fiber.abort("'hasZero'Name must be truea ornon-empty falsestring.")
if (members.isEmpty) Fiber.abort("A group must have at least one member.")
if (members.count != values.count) Fiber.abort("There must be as many values as members.")
name = name + "_"
var s = "class %(name) {\n"
for (i in 0...members.count) {
var m = members[i]
var v = values[i]
if (v is String) {
s = s + " static %(m) { \"%(v)\" }\n"
} else {
s = s + " static %(m) { %(v) }\n"
}
}
var limitmems = hasZeromembers.map ?{ 1 <<|m| "\"%(memCount-2m)\"" :}.join(", 1 << (memCount-1")
ifvar (member.typevals != Numvalues.map || !member.isInteger{ |v| member(v <is 0String) ||? member"\"%(v)\"" >: limit"%(v)" {}.join(", ")
s = s + Fiber.abort("Member must be a non-negativestatic integermembers <={ [%(limitmems).] }\n")
s = s + " static values { [%(vals)] }\n"
}
if (members == 0) return hasZero ? 0s :+ -1"}\n"
s = s + "return %(name)"
if (member & (member-1) != 0) return -1 // not a power of two
var res =return Meta.compile(members).log / 2.logcall().round
}
return hasZero ? res + 1 : res
}
 
// Convenience versions of above method where the number of members is assumed to be the maximum.
static indexOf(member, hasZero) { indexOf(member, hasZero, hasZero ? 33 : 32) }
static indexOf(member) { indexOf(member, false, 32) } // assumes no zero memmber
}
 
Line 239 ⟶ 289:
"abcdefghijklmnopqrstuvwxyz".toList,
97
)</langsyntaxhighlight>
9,485

edits