rohansi / mond Goto Github PK
View Code? Open in Web Editor NEWA scripting language for .NET Core
Home Page: https://mond.rohan.dev/
License: MIT License
A scripting language for .NET Core
Home Page: https://mond.rohan.dev/
License: MIT License
enableThis
is stupid magic that is confusing and doesn't even work well (breaks metamethods).
Currently I see two pretty major inconsistencies in Mond, one having to do with functions inside objects, and the other having to do with list comprehensions.
Generally in Mond, when you create a function or a sequence using the fun
/seq
keywords a declaration is made. This means that code like this is valid and works:
var x = banana(fun bap() { return true; }, y, z);
bap();
This however, is not the case with object literals. This code is invalid, as no declaration is made for bap
:
var x = { fun bap() { return true; } };
bap();
List comprehensions are inconsistent both in syntax and semantics. They're inconsistent syntactically because generators inside list comprehensions syntactically look like x in list
compared to var x in list
in foreach
loops. They're inconsistent semantically because the 'core' expression of a list comprehension can refer to declarations before they physically appear in the source file, and inside list comprehensions is the only time in Mond where you can do that. Example:
[x : x in list]
I suggest the follow changes be made to Mond:
add: (a, b) -> { ... }
or add: fun (a, b) { ... }
.Infinity
and NaN
are not valid in JSON. The serializer outputs them but it should be an error.
The Pop
method can just decrement the stack pointer and we can clean up the old values from the stack later. This would need to track the max number of stack slots used at compile time so the VM knows how many slots to clear.
There won't always be a VM stacktrace, so it can end up being null. It's better to have both to avoid confusion.
Relational operators can currently dispatch on the right hand side when it is an object. This behavior is inconsistent with #47 but I'm not really sure if it's the best decision.
@SirTony What are your thoughts on this? #63 copies the RHS dispatch from __gt
but doesn't implement it properly, but I feel like it just shouldn't be supported.
I thought it'd be prudent to get feedback on this idea before anything gets implemented and pull requests opened. Because this idea is admittedly quite niche, simple though it is. Although if the array prototype had methods like map
/filter
& co I could see it having a much stronger use case.
The idea is to add a new operator similar to pipeline, except it points in the other direction and works in reverse. The function call is on the left, the value to be passed is on the right, and rather than inserting the value as the first parameter, it inserts it as the last parameter.
The main inspiration behind this is Ruby's blocks, as it would allow Mond to emulate this to a degree.
def qsort( collection, &comparer )
...
end
strings = [ "Watermelon", "Apple", "Grape", "Pear", "Honeydew" ]
sorted = qsort( strings ) do | a, b |
# Do some super-complex comparison magic
a.length - b.length
end
fun qsort( collection, comparer ) {
...
}
var strings = [ "Watermelon", "Apple", "Grape", "Pear", "Honeydew" ];
var sorted = qsort( string ) <| fun( a, b ) {
// Even more magic
return a.length() - b.length();
}
var arr = [];
printLn(arr.foo);
Should this evaluate to undefined
or throw an exception? The same applies to other types.
It's a bit trickier for objects because it could be very breaking.
I suggest closing this branch and moving to https://github.com/LuaDist/lua
Using metamethods it's currently possible to create a hack to simulate infix operators:
fun Infix( callback ) {
return {
__or: fun( left ) {
return {
__or: fun( _, right ) {
return callback( left, right );
}
};
}
};
}
const times = Infix( ( left, right ) -> left * right );
printLn( 5 |times| 10 ); //=> 50
I propose to borrow an idea from F# by allowing the programmer to truly define their own operator using the tools the language provides, rather than relying on clever hacks to simulate it.
These user-defined operators (UDOs) would have a few simple rules to follow:
+
to give it new behaviour will not fly)UDOs will have the following syntax (PEG notation. not nailed down, suggestions more than welcome):
userDefinedOperator = "operator" "(" udoToken+ ")" "(" udoArguments ")" functionBody
udoToken = '<' / '>' / '.' / '/' / '?' / ':' / '~' / '='
/ '!' / '%' / '^' / '&' / '*' / '-' / '+' / '|'
udoArguments = identifier / ( identifier ',' identifier )
functionBody = lambdaBody / block
lambdaBody = "->" expression
block = '{' statement+ '}'
Some trivial examples:
// Simulate range literals
operator( .. )( begin, end ) {
// return a sequence that counts from begin to end
// (upper bound inclusive range)
return ( seq() {
for( var i = begin; i <= end; ++i )
yield i;
} )();
}
// Existing operators are exempt from rule 1 only if the UDO form
// has a different arity than the built-in form. See below.
operator( ... )( begin, end ) {
// upper bound exclusive range
return ( seq() {
for( var i = begin; i < end; ++i )
yield i;
} )();
}
// uppercase a string
// This is exempt from rule 1 because the XOR operator is a binary operator,
// while this UDO is a unary operator.
operator( ^ )( str ) -> str.toUpper();
var oneToFive = 1 .. 5;
var oneToFour = 1 ... 5;
var shouting = ^"hello";
printLn( oneToFive.serialize() ); //=> [ 1, 2, 3, 4, 5 ]
printLn( oneToFour.serialize() ); //=> [ 1, 2, 3, 4 ]
printLn( shouting ); //=> 'HELLO'
I've got a semi-working proof of concept on a local build (that completely defies rules 1-3, but I digress), with only a little modification to the parser (most of it is in ParseExpression()
) I was able to get things up and running.
In keeping with the F# spirit, some less trivial examples of UDOs would be their usage in FAKE, which uses UDOs to great effect for build scripts.
Since MondValue
implements IComparable<MondValue>
we can do this easily. Probably no parameters, sort
and sortDescending
?
The current async library works but really isn't nice to use. Errors inside tasks are impossible to handle and don't have meaningful stack traces.
Async functions should be able to fix both of these because the await
expression would be able to do more than just save state and return.
More information will be added here later.
Enable moving to the beginning of a line with the HOME key, and the end of a line with the END key.
Not sure if it would actually be useful...
IMondLibraryCollection
s by name?
When MondLibraryManager
was added the code required to run scripts became a lot more complex. Something should be done so you don't need to use MondLibraryManager
unless you don't want the default libraries.
I am creating calculator with Mond and Avalonia. I want to see all global variables on right side. How can I do that? how can I enumerate variables?
Currency I am using hack like this
var globalVariables = state.Run("return global;").Object.Where(x=>x.Value.Type != MondValueType.Function && x.Value.Type != MondValueType.Object);
but it lokks and feels wrong.
fun (x, y) { return x + y; } //valid
(x, y) -> x + y //valid
(x, y) -> { return x + y; } //invalid
Mond currently doesn't really have any kind of a standard library aside from the prototype functions. The functions and modules defined in the REPL should be moved into the Mond assembly.
Th standard library should ideally:
require
should support custom loaders instead of just using File.ReadAllText
.Having __gt
(and __eq
) also do the work of >=
, <
, <=
potentially makes using metamethods more confusing and restrictive for little gain. The main problem is that it makes it impossible to target specifically <
, <=
, and >=
for different functionality from >
, or disallowing the use of >
entirely, meaning that overloading the comparison operators for completely different functionality (similarly to how C++ overloads the shift operators for streams) is of little usefulness when <
and >
end up calling the same function.
Admittedly this only really become a problem when working on mapping .NET operator overloads to metamethods for SirTony/Mond.BindingEx#3 because the CLR allows overloading all four separately.
When using a function like this:
seq ints() {
var i = 0;
while (true) {
var ii = i++;
yield () -> ii;
}
}
You will currently get this error:
mondbox(line 5): 'yield' cannot be used in functions
This is due to the new LoopContext
class but that error hides a trickier issue. The fix used in 749a117 added a new leave
instruction intended to be paired with enter
. These create a new frame for locals when functions are created in loops, but this wont work in sequences because they can re-enter in the middle of the function.
The generated assembly of the loop body would look something like this:
enter 1
...
ret // yield
resumeLocation:
...
leave
This looks fine but ret
discards all of the frames created by enter
in the current function, meaning when we resume it will be missing one.
My current idea to fix this involves adding two new instructions to save/restore the current locals
value in another frame. This could improve sequence performance slightly and greatly simplify the compiler code.
Essentially, range literals would be more or less syntax sugar on generators or list comprehensions. Where var x = 1 .. 5;
is functionally equivalent to var x = irange( 1, 5 );
, and var x = 1 ... 5;
is equivalent to var x = xrange( 1, 5 );
.
The double-dot generates the values 1, 2, 3, 4, and 5, and the ellipsis generates 1, 2, 3, and 4.
//Upper-bound inclusive range.
seq irange( x, y ) {
var min = Math.min( x, y );
var max = Math.max( x, y );
for( var i = min; i <= min; ++i )
yield i;
}
//Upper-bound exclusive range.
seq xrange( x, y ) {
var min = Math.min( x, y );
var max = Math.max( x, y );
for( var i = min; i < min; ++i )
yield i;
}
This could also be extended to arrays (and possibly strings) by way of a slice syntax in the form of:
var slice = array[1 .. 5]; // [ 1, 2, 3, 4, 5 ]
and
var slice2 = array[ 1 ... 5 ]; // [ 1, 2, 3, 4 ]
where
var array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
Ruby allows chars to be used in range literals (i.e. alphabet = 'A' .. 'Z'
), although for the sake of simplicity I think they should be restricted to numbers only, and instead they can be combined with comprehensions for extended functionality (var alphabet = [ chr( x + 65 ) : x in 0 ... 26
).
Dealing with unicode manually is a pain
while ((fun banana() -> true)()) { }
banana();
This is valid, but doing foreach(var x in xs) {}
won't give you access to x
after the loop. Should be fixed by making the declaration of banana
part of the loop block.
For MondFunctionAttribute
. Ideally we only want people using the name override if the name is actually different (ex: metamethods).
var array = [ 1, 2 ];
var [ a, b, c, ...d, e ] = array;
return [ a, b, c, d, e ]; // => [ 1, 2, undefined, [], 2 ]
var [ ...x, y, z ] = [ 1, 2 ];
return [ x, y, z ]; // => [ [], undefined, 2 ]
Possible usecase:
const valuePrototype = {}.getPrototype().getPrototype();
const structMetas = {
__get: fun (this, index) {
error("unknown field '" + index + "' (get)");
},
__set: fun (this, index, value) {
error("unknown field '" + index + "' (set)");
}
}.setPrototype(valuePrototype);
fun struct(...properties) {
foreach (var property in properties) {
// if (property.getType() != "number")
// error("struct field names must be numbers");
}
return fun (...values)
{
var pl = properties.length();
if (values.length() != pl)
{
error(string.Format("struct requires {0} value{1}.", pl, pl > 1 ? "s" : ""));
}
var value = {};
for (var i = 0; i < properties.length(); i++) {
value[properties[i]] = values[i];
}
return value.setPrototype(structMetas);
};
}
var Point = struct("x", "y");
var p1 = Point(1, 2);
var p2 = Point(10, 20);
printLn("p1.x = " + p1.x);
printLn("p1.y = " + p1.y);
printLn();
printLn("p2.x = " + p2.x);
printLn("p2.y = " + p2.y);
Currently this (rightfully) errors with:
mondbox(line 24): Undefined identifier 'string'
This is also the case with String
, System.String
and System.string
.
Fixed 32-bit could maybe work, but 16-bit with an optional 16-bit extension could be good too. Primary reason is decode performance. The 32-bit reads into byte[]
seem to upset the JIT so some normal array access should help out a lot. Plus things will be aligned.
Currently using object
values as keys is flawed because there's no way to provide consistent hashes for equivalent values.
Hi,
is there any possibility to add a list or an array to MondState
?
var mondState = new MondState();
mondState["global.strValue"] = "my string value";
mondState["global.arrayValue"] = new List<string>();
Line 2 works fine, but in line 3 I get a compiler error Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'Mond.MondValue'
.
I could not find any information about how to convert List<string>
to Mond.MondValue
in the documentation.
Any help is appreciated. :-)
The binding API currently errors when trying to bind more than one function with the same name. The binding API should be updated to handle this (and default parameters) automatically.
Need this to make MondNet
Array indexing allows negative indices, the array methods do not.
The remote debugger only allows local variables to be used as watch expressions. Supporting any expression would be best but it should at least support value navigation (x.y
, x[y]
).
OnBreak
should also immediately return while refreshing watch values because accessing it can trigger code to run.
Compiling watch expressions as Mond scripts would be best but local variables need to be mapped correctly. Maybe this can be done in MondDebugContext
so we can rewrite the expression tree?
Should also have help...
UserData
is null, should have a check for this?
I'm a very private person.
Not all platforms support the expression trees generated by the binding API. For better support on other platforms an alternative should be provided, preferably as a compile-time option.
The type checks done on function bindings don't play nice with implicit casts:
const two = {
__number: this -> 2,
};
return Math.sin(two);
Math.sin: argument 1 must be of type number
There should be an option to disable type checks for the class/module/function/parameter, or they should be skipped for objects.
It's probably best to skip checks for objects and just assume they will have a cast, but I'm not sure yet.
Is there any way to support .NET 3.5 (mono)?
Yes I know this would prob bamboozle the Async/Task support but thats not something that important for me.
I plan to integrate this into the RocketMod plugin framework (at https://github.com/RocketMod/Rocket.Scripting) but it targets only .NET 3.5 as some games it supports still use Unity 5.
We call MondClassBinder::Bind
when generating the code but it hasn't finished yet so it ends up running again. Should have a prototype cache in MondState
maybe?
This makes implementation of overloads complicated for little benefit. I believe we should just dispatch on the left-most value (except for in
of course).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.