Skip to content
This repository was archived by the owner on Aug 31, 2021. It is now read-only.

Commit 45d2e24

Browse files
committed
LCB: Add array expressions to language.
Add array expressions to the LiveCode Builder language. They have the grammar: array ::= "{" [key-datum-list] "}" key-datum-list ::= key-datum ("," key-datum)* key-datum ::= expression ":" expression
1 parent ca0b026 commit 45d2e24

File tree

16 files changed

+434
-0
lines changed

16 files changed

+434
-0
lines changed

docs/guides/LiveCode Builder Language Reference.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ The return value of a handler is subsequently available by using **the result**
522522
| VariableExpression
523523
| ResultExpression
524524
| ListExpression
525+
| ArrayExpression
525526
| CallExpression
526527

527528
There are a number of expressions which are built-in and allow constant values, access to call results, list construction and calls. The remaining syntax for expressions is defined in auxiliary modules.
@@ -577,6 +578,23 @@ The elements list is optional, so the empty list can be specified as *[]*.
577578

578579
List expressions are not assignable.
579580

581+
## Array Expressions
582+
583+
ArrayExpression
584+
: '{' [ <Contents: ArrayDatumList> ] '}'
585+
ArrayDatumList
586+
: <Head: ArrayDatum> [ ',' <Tail: ArrayDatumList> ]
587+
ArrayDatum
588+
: <Key: Expression> ':' <Value: Expression>
589+
590+
An array expression evaluates all of the key and value expressions
591+
from left to right, and constructs an **Array** value as appropriate.
592+
Each key expression must evaluate to a **String**.
593+
594+
The contents are optional, so the empty array can be written as `{}`.
595+
596+
Array expressions are not assignable.
597+
580598
## Call Expressions
581599

582600
CallExpression
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# LiveCode Builder Language
2+
## Expressions
3+
4+
* It is now possible to use array constants and array expressions in
5+
LiveCode Builder programs. For example:
6+
7+
variable tArray as Array
8+
put {"key": true} into tArray
9+
10+
Keys in array literals must be strings.
11+
12+
# LiveCode Builder Tools
13+
## lc-compile
14+
### Errors
15+
16+
* List expressions with more than 254 elements will now generate an error.
17+
* Array expressions with more than 127 key-value pairs will now generate an error.
18+
* Array constants with non-string keys will now generate an error.

libscript/include/libscript/script.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ void MCScriptAddValueToModule(MCScriptModuleBuilderRef builder, MCValueRef value
304304
void MCScriptBeginListValueInModule(MCScriptModuleBuilderRef builder);
305305
void MCScriptContinueListValueInModule(MCScriptModuleBuilderRef builder, uindex_t index);
306306
void MCScriptEndListValueInModule(MCScriptModuleBuilderRef builder, uindex_t& r_index);
307+
void MCScriptBeginArrayValueInModule(MCScriptModuleBuilderRef builder);
308+
void MCScriptContinueArrayValueInModule(MCScriptModuleBuilderRef builder, uindex_t key_index, uindex_t value_index);
309+
void MCScriptEndArrayValueInModule(MCScriptModuleBuilderRef builder, uindex_t& r_index);
307310

308311
void MCScriptAddDefinedTypeToModule(MCScriptModuleBuilderRef builder, uindex_t index, uindex_t& r_type);
309312
void MCScriptAddForeignTypeToModule(MCScriptModuleBuilderRef builder, MCStringRef p_binding, uindex_t& r_type);
@@ -359,6 +362,9 @@ void MCScriptEmitAssignInModule(MCScriptModuleBuilderRef builder, uindex_t dst_r
359362
void MCScriptEmitBeginAssignListInModule(MCScriptModuleBuilderRef builder, uindex_t reg);
360363
void MCScriptEmitContinueAssignListInModule(MCScriptModuleBuilderRef builder, uindex_t reg);
361364
void MCScriptEmitEndAssignListInModule(MCScriptModuleBuilderRef builder);
365+
void MCScriptEmitBeginAssignArrayInModule(MCScriptModuleBuilderRef builder, uindex_t reg);
366+
void MCScriptEmitContinueAssignArrayInModule(MCScriptModuleBuilderRef builder, uindex_t reg);
367+
void MCScriptEmitEndAssignArrayInModule(MCScriptModuleBuilderRef builder);
362368
void MCScriptEmitReturnInModule(MCScriptModuleBuilderRef builder, uindex_t reg);
363369
void MCScriptEmitReturnUndefinedInModule(MCScriptModuleBuilderRef builder);
364370
void MCScriptBeginInvokeInModule(MCScriptModuleBuilderRef builder, uindex_t handler_index, uindex_t result_reg);

libscript/src/script-builder.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
1616

1717
#include <foundation.h>
18+
#include <foundation-auto.h>
1819

1920
#include "libscript/script.h"
2021
#include "script-private.h"
@@ -64,6 +65,7 @@ struct MCScriptModuleBuilder
6465
uindex_t current_line;
6566

6667
MCProperListRef current_list_value;
68+
MCArrayRef current_array_value;
6769
};
6870

6971
////////////////////////////////////////////////////////////////////////////////
@@ -125,6 +127,7 @@ void MCScriptBeginModule(MCScriptModuleKind p_kind, MCNameRef p_name, MCScriptMo
125127
self -> current_line = 0;
126128

127129
self -> current_list_value = nil;
130+
self -> current_array_value = nil;
128131

129132
r_builder = self;
130133
}
@@ -164,6 +167,7 @@ bool MCScriptEndModule(MCScriptModuleBuilderRef self, MCStreamRef p_stream)
164167
MCMemoryDeleteArray(self -> operands);
165168

166169
MCValueRelease(self -> current_list_value);
170+
MCValueRelease(self -> current_array_value);
167171

168172
MCMemoryDelete(self);
169173

@@ -325,6 +329,49 @@ void MCScriptEndListValueInModule(MCScriptModuleBuilderRef self, uindex_t& r_ind
325329
self -> current_list_value = nil;
326330
}
327331

332+
void MCScriptBeginArrayValueInModule(MCScriptModuleBuilderRef self)
333+
{
334+
if (self == nil || !self -> valid)
335+
return;
336+
337+
if (self -> current_array_value != nil ||
338+
!MCArrayCreateMutable(self -> current_array_value))
339+
{
340+
self -> valid = false;
341+
return;
342+
}
343+
}
344+
345+
void MCScriptContinueArrayValueInModule(MCScriptModuleBuilderRef self,
346+
uindex_t p_key_idx,
347+
uindex_t p_value_idx)
348+
{
349+
MCNewAutoNameRef t_key;
350+
351+
if (self == nil || !self -> valid)
352+
return;
353+
354+
if (self -> current_array_value == nil ||
355+
!MCNameCreate(reinterpret_cast<MCStringRef>(self->module.values[p_key_idx]), &t_key) ||
356+
!MCArrayStoreValue(self -> current_array_value, false,
357+
*t_key, self -> module . values[p_value_idx]))
358+
{
359+
self -> valid = false;
360+
return;
361+
}
362+
}
363+
364+
void MCScriptEndArrayValueInModule(MCScriptModuleBuilderRef self, uindex_t& r_index)
365+
{
366+
if (self == nil || !self -> valid)
367+
return;
368+
369+
MCScriptAddValueToModule(self, self -> current_array_value, r_index);
370+
371+
MCValueRelease(self -> current_array_value);
372+
self -> current_array_value = nil;
373+
}
374+
328375
void MCScriptAddTypeToModule(MCScriptModuleBuilderRef self, MCNameRef p_name, uindex_t p_type, uindex_t p_index)
329376
{
330377
if (self == nil || !self -> valid)
@@ -1394,6 +1441,31 @@ void MCScriptEmitEndAssignListInModule(MCScriptModuleBuilderRef self)
13941441
__end_instruction(self);
13951442
}
13961443

1444+
void MCScriptEmitBeginAssignArrayInModule(MCScriptModuleBuilderRef self, uindex_t p_reg)
1445+
{
1446+
if (self == nil || !self -> valid)
1447+
return;
1448+
1449+
__begin_instruction(self, kMCScriptBytecodeOpAssignArray);
1450+
__continue_instruction(self, p_reg);
1451+
}
1452+
1453+
void MCScriptEmitContinueAssignArrayInModule(MCScriptModuleBuilderRef self, uindex_t p_reg)
1454+
{
1455+
if (self == nil || !self -> valid)
1456+
return;
1457+
1458+
__continue_instruction(self, p_reg);
1459+
}
1460+
1461+
void MCScriptEmitEndAssignArrayInModule(MCScriptModuleBuilderRef self)
1462+
{
1463+
if (self == nil || !self -> valid)
1464+
return;
1465+
1466+
__end_instruction(self);
1467+
}
1468+
13971469
void MCScriptEmitAssignInModule(MCScriptModuleBuilderRef self, uindex_t p_dst_reg, uindex_t p_src_reg)
13981470
{
13991471
if (self == nil || !self -> valid)

libscript/src/script-instance.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,11 @@ bool MCScriptThrowNotABooleanError(MCValueRef p_value)
279279
return MCErrorCreateAndThrow(kMCScriptNotABooleanValueErrorTypeInfo, "value", p_value, nil);
280280
}
281281

282+
bool MCScriptThrowNotAStringError(MCValueRef p_value)
283+
{
284+
return MCErrorCreateAndThrow(kMCScriptNotAStringValueErrorTypeInfo, "value", p_value, nil);
285+
}
286+
282287
bool MCScriptThrowWrongNumberOfArgumentsForInvokeError(MCScriptModuleRef p_module, MCScriptDefinition *p_definition, uindex_t p_provided)
283288
{
284289
// TODO: Encode provided / expected.
@@ -2360,6 +2365,58 @@ bool MCScriptCallHandlerOfInstanceInternal(MCScriptInstanceRef self, MCScriptHan
23602365
MCMemoryDeleteArray(t_values);
23612366
}
23622367
break;
2368+
case kMCScriptBytecodeOpAssignArray:
2369+
{
2370+
int t_dst;
2371+
t_dst = t_arguments[0];
2372+
2373+
MCAutoArrayRef t_array;
2374+
if (!MCArrayCreateMutable(&t_array))
2375+
{
2376+
t_success = false;
2377+
}
2378+
2379+
for (int t_arg_ofs = 1; t_success && t_arg_ofs + 1 < t_arity; t_arg_ofs += 2)
2380+
{
2381+
MCValueRef t_raw_key, t_value;
2382+
MCNewAutoNameRef t_key;
2383+
if (t_success)
2384+
{
2385+
t_success = MCScriptCheckedFetchFromRegisterInFrame(t_frame, t_arguments[t_arg_ofs], t_raw_key);
2386+
}
2387+
if (t_success)
2388+
{
2389+
t_success = MCScriptCheckedFetchFromRegisterInFrame(t_frame, t_arguments[t_arg_ofs + 1], t_value);
2390+
}
2391+
if (t_success)
2392+
{
2393+
// FIXME allow construction of arrays from
2394+
// string-bridging foreign values.
2395+
if (MCValueGetTypeCode(t_raw_key) != kMCValueTypeCodeString)
2396+
{
2397+
t_success = MCScriptThrowNotAStringError(t_raw_key);
2398+
}
2399+
}
2400+
if (t_success)
2401+
{
2402+
t_success = MCNameCreate(reinterpret_cast<MCStringRef>(t_raw_key), &t_key);
2403+
}
2404+
if (t_success)
2405+
{
2406+
t_success = MCArrayStoreValue(*t_array, false, *t_key, t_value);
2407+
}
2408+
}
2409+
2410+
if (t_success)
2411+
{
2412+
t_success = t_array.MakeImmutable();
2413+
}
2414+
if (t_success)
2415+
{
2416+
t_success = MCScriptCheckedStoreToRegisterInFrame(t_frame, t_dst, *t_array);
2417+
}
2418+
}
2419+
break;
23632420
}
23642421

23652422
// If we failed, then make sure the frame address is up to date.

libscript/src/script-module.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,18 @@ bool MCScriptValidateModule(MCScriptModuleRef self)
403403
for(uindex_t i = 0; i < t_arity; i++)
404404
t_temporary_count = MCMax(t_temporary_count, t_operands[i] + 1);
405405
break;
406+
case kMCScriptBytecodeOpAssignArray:
407+
// assignarray <dst>, [ <key_1>, <value_1>, ..., <key_n>, <value_n> ]
408+
if (t_arity < 1)
409+
goto invalid_bytecode_error;
410+
if (t_arity % 2 != 1) // parameters must come in pairs
411+
goto invalid_bytecode_error;
412+
413+
for (uindex_t i = 0; i < t_arity; ++i)
414+
{
415+
t_temporary_count = MCMax(t_temporary_count, t_operands[i] + 1);
416+
}
417+
break;
406418
}
407419
}
408420

libscript/src/script-object.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ MCTypeInfoRef kMCScriptInvalidReturnValueErrorTypeInfo;
2828
MCTypeInfoRef kMCScriptInvalidVariableValueErrorTypeInfo;
2929
MCTypeInfoRef kMCScriptInvalidArgumentValueErrorTypeInfo;
3030
MCTypeInfoRef kMCScriptNotABooleanValueErrorTypeInfo;
31+
MCTypeInfoRef kMCScriptNotAStringValueErrorTypeInfo;
3132
MCTypeInfoRef kMCScriptWrongNumberOfArgumentsErrorTypeInfo;
3233
MCTypeInfoRef kMCScriptForeignHandlerBindingErrorTypeInfo;
3334
MCTypeInfoRef kMCScriptMultiInvokeBindingErrorTypeInfo;
@@ -257,6 +258,8 @@ bool MCScriptInitialize(void)
257258
return false;
258259
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.lang.NotABooleanValueError"), MCNAME("runtime"), MCSTR("Value is not a boolean"), kMCScriptNotABooleanValueErrorTypeInfo))
259260
return false;
261+
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.lang.NotAStringValueError"), MCNAME("runtime"), MCSTR("Value is not a string"), kMCScriptNotAStringValueErrorTypeInfo))
262+
return false;
260263
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.lang.WrongNumberOfArgumentsError"), MCNAME("runtime"), MCSTR("Wrong number of arguments passed to handler %{module}.%{handler}"), kMCScriptWrongNumberOfArgumentsErrorTypeInfo))
261264
return false;
262265
if (!MCNamedErrorTypeInfoCreate(MCNAME("livecode.lang.ForeignHandlerBindingError"), MCNAME("runtime"), MCSTR("Unable to bind foreign handler %{module}.%{handler}"), kMCScriptForeignHandlerBindingErrorTypeInfo))

libscript/src/script-private.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ extern MCTypeInfoRef kMCScriptInvalidReturnValueErrorTypeInfo;
3333
extern MCTypeInfoRef kMCScriptInvalidVariableValueErrorTypeInfo;
3434
extern MCTypeInfoRef kMCScriptInvalidArgumentValueErrorTypeInfo;
3535
extern MCTypeInfoRef kMCScriptNotABooleanValueErrorTypeInfo;
36+
extern MCTypeInfoRef kMCScriptNotAStringValueErrorTypeInfo;
3637
extern MCTypeInfoRef kMCScriptWrongNumberOfArgumentsErrorTypeInfo;
3738
extern MCTypeInfoRef kMCScriptForeignHandlerBindingErrorTypeInfo;
3839
extern MCTypeInfoRef kMCScriptMultiInvokeBindingErrorTypeInfo;
@@ -576,6 +577,13 @@ enum MCScriptBytecodeOp
576577
// build a list. (This will be replaced by an invoke when variadic bindings are
577578
// implemented).
578579
kMCScriptBytecodeOpAssignList,
580+
581+
// Array creation assignment.
582+
// assign-array <dst>, <key_1>, <value_1>, ..., <key_n>, <value_n>
583+
// Dst is a register. The remaining arguments are registers and
584+
// are used, pair-wise, to build an array. (This will be replaced by an invoke
585+
// when variadic bindings are implemented).
586+
kMCScriptBytecodeOpAssignArray,
579587
};
580588

581589
bool MCScriptBytecodeIterate(byte_t*& x_bytecode, byte_t *p_bytecode_limit, MCScriptBytecodeOp& r_op, uindex_t& r_arity, uindex_t *r_arguments);

tests/lcb/compiler/literals.lcb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,26 @@ public handler TestLiteralNul()
2828
test "literal nuls" when the number of chars in tString is 5
2929
end handler
3030

31+
public handler TestLiteralList()
32+
test "empty list constant" when the number of elements in [] is 0
33+
34+
test "non-empty list constant" when the number of elements in [1] is 1
35+
36+
variable tValue
37+
put "bar" into tValue
38+
test "non-empty list expression" when the number of elements in [tValue] is 1
39+
end handler
40+
41+
public handler TestLiteralArray()
42+
test "empty array constant" when the number of elements in {} is 0
43+
44+
test "non-empty array constant" when the number of elements in {"foo": 1} is 1
45+
46+
variable tKey
47+
put "foo" into tKey
48+
variable tValue
49+
put {} into tValue
50+
test "non-empty array expression" when the number of elements in {tKey: tValue} is 1
51+
end handler
52+
3153
end module

0 commit comments

Comments
 (0)