Closed faultyserver closed 6 years ago
To expand a bit on how native library methods can be added:
The interpreter provides a few helper macros under the NativeLib
module, namely method
, def_method
, and def_instance_method
. These end up getting used by files in the native_lib
folder to create and register methods on types in the Kernel.
Adding a new method to the native library is a two-step process:
use NativeLib.method
to define the method implementation. For example, the String#size
implementation looks like this:
https://github.com/myst-lang/myst/blob/aea25887f8bde444351f5590c69a11f256d61a4d/src/myst/interpreter/native_lib/string.cr#L56-L58
The first argument to this function is the name of the implementation. This is often made up of the name of the type the method is being defined on, followed by the name of the method being implemented. The second argument is the type to use for the self
object (available as the variable this
) while inside the method.
Any further arguments are parameters for the method, and require a type restriction (normally just Value
). The other methods in native_lib/string.cr
show this usage.
In the init_*
method for the type (e.g., init_string
in native_lib/string.cr
), use NativeLib.def_instance_method
to register the implementation with the type it belongs to. For example, the String#size
method shown above is registered with a line like this:
NativeLib.def_instance_method(string_type, :size, :string_size)
Here, string_type
is the Type that represents String
in Myst (see it's instantiation at the beginning of init_string
). :size
is the name of the method as it should appear in Myst, and string_size
is the name of the implementation method.
def_instance_method
is used to define instance methods for Types. It will register the method in the instance_scope
of the given TType
. def_method
is used to define static methods, or methods for Modules (e.g., IO.puts
). The arguments for the two use the same structure.
Hopefully that helps clear up how the Native Library is built up. Feel free to ask any more questions in the #help channel in the discord, or on here.
I've removed the Dir.[pattern : String]
method from this issue because it doesn't really fit any of the other things here. Without more methods on Dir and File, it's pretty useless anyway.
For reference, here is the original text:
Dir.[pattern : String]
Return a List containing the file names that match the given pattern
, which can be given as a grep pattern like in Bash. Crystal provides this same functionality, so this function can act as a direct passthrough to that method.
With that, this issue is complete! Thank you to everyone who helped implement these methods! This has been a long-lived issue, but 0.3.0 will be a great release because of it :) Thanks again.
This issue has been marked as a "Good First Issue"! If you'd like to tackle this issue as your first contribution to the project, be sure to read the Get Involved section of the README for some help with how to get started.
This is also a very large issue with many parts. It is meant to be tackled piece-by-piece. A contribution that implements even one of these methods will gladly be accepted.
For the past few weeks, I've been working on a Getting Started guide for Myst. In that guide, I make some references to standard library functions that don't actually exist yet. The goal of this issue is to list all of the functions that are referenced that do not yet have an implementation so that they can be added one by one. Since the guide is not finished, entries may be added to this issue in the future.
Additionally, there are some other functions that I think would be good additions for the next release.
Some of these functions will be part of the native library (written in Crystal), while others will be part of the standard library (written in Myst). Each entry here has a small description of what it should do. For the most part, these follow the versions from Ruby, so feel free to look there for inspiration.
Native library
[x]
Map#+(other : Map)
Add two Maps together into a new Map. If a key exists in both Maps, the value from the second map should be used.
[x]
List#-(other : List)
Return a new List with only the entries from the first List that do not exist in the second.
[x]
Integer#<(other)
Return
true
if the integer is numerically less thanother
. Returnfalse
otherwise.[x]
Integer#<=(other)
Return
true
if the integer is numerically less than or equal toother
. Returnfalse
otherwise.[x]
Integer#>=(other)
Return
true
if the integer is numerically greater than or equal toother
. Returnfalse
otherwise.[x]
Integer#>(other)
Return
true
if the integer is numerically greater thanother
. Returnfalse
otherwise.[x]
Float#<(other)
See
Integer#<
.[x]
Float#<=(other)
See
Integer#<=
.[x]
Float#>=(other)
See
Integer#>=
.[x]
Float#>(other)
See
Integer#>
.[x]
List#<(other : List)
Return
true
if the List is a proper subset ofother
. That is, if every element of the List appears inother
, butother
also contains at least one more value. Returnfalse
otherwise.[x]
List#<=(other : List)
Same as
List#<(other)
, but also returntrue
if there are no extra elements inother
.[x]
Map#<(other : Map)
Return
true
if the Map is a proper subset ofother
. That is, if every key of the Map appears inother
, butother
also contains at least one more key. Returnfalse
otherwise.The values of the Map are not important. Only the keys determine the subset.
[x]
Map#<=(other: Map)
Same as
Map#<(other)
, but also returntrue
if there are no extra elements inother
.[x]
IO.print(string : String)
Print the given string to the
output
of the Interpreter, with no formatting or conversions done. The argument should be expected to already be a String.[x]
List#size
Return the number of elements in the List as an Integer.
[x]
Map#size
Return the number of elements in the Map as an Integer.
Standard library
[X]
String#size
Return the number of characters in the String as an Integer.
[x]
String#empty?
Return
true
if the String contains 0 characters. Returnfalse
otherwise.[x]
List#empty?
Return
true
if the List contains 0 elements. Returnfalse
otherwise.[x]
Map#empty?
Return
true
if the Map contains 0 elements. Returnfalse
otherwise.[x]
Enumerable#all?(&block)
Pass each element of the enumerable to
block
, returningtrue
if all elements in the enumerable causesblock
to return a truthy value.[x]
Enumerable#any?(&block)
Pass each element of the enumerable to
block
, returningtrue
if any element in the enumerable causesblock
to return a truthy value.[x]
Enumerable#find(default=nil, &block)
Iterate the enumerable, passing each element to
block
. The return value should be the first element for which the block is truthy. If no elements cause a truthy return value, return the default value instead (which itself should default tonil
).[x]
Enumerable#min
Iterate the enumerable, finding the element with the lowest value as determined by
<
.[x]
Enumerable#max
Iterate the enumerable, finding the element with the highest value as determined by
>
.[x]
Enumerable#select(&block)
Returns an array containing all the elements of the enumerable that cause
block
to return a truthy value.[x]
Enumerable#sort
Sort the elements of the enumerable with the ordering determined by the
<=
method for each element. To start with, the sort can be something simple like insertion sort, eventually replaced by a proper hybrid quick sort.[x]
Enumerable#size
Return the number of elements in the enumerable, determined by incrementing a counter for each element yielded by
#each
.[x]
Enumerable#to_list
Return a List containing all the elements of the enumerable.
[x]
Enumerable#reduce(&block(acc, elem))
For every element in the enumerable, call
block
with the result of the previous call and the current element as arguments. The first element is used as the initial value of the accumulator; it does not get a separate call to the block.[x]
Enumerable#reduce(initial, &block(acc, elem))
Same as
Enumerable#reduce(&block(acc, elem))
, but also specifying an initial value to use for the accumulator. In this version, the first element will get its own call to the block.[x]
Int#times(&block)
Call
block
as many times as the value of this integer. For example,3.times(...)
would callblock
3 times.Implementation
Obviously, there are a lot of things to add. I don't expect that all of these would be added in a single PR. Tackling them one at a time is fine by me.
Adding functions to the native library can be done in the
src/myst/interpreter/native_lib
folder. All of the types listed above should already exist there. UseNativeLib.method
to write the implementation for a function, then callNativeLib.def_instance_method
orNativeLib.def_method
to add it to the appropriate module. There are plenty of examples in the code already that should help you out.The standard library exists in the
stdlib
folder at the top level. Look at the existing entries (specifically,Enumerable
) to see how new functions can be added.Also, please try to add a descriptive comment to each method describing the arguments that it accepts, the values that might be returned and a description of what the method does. The
Enumerable
module) has some good examples of these comments.If you'd like to pick up one or more of these functions, please comment below saying which function you would like to implement so that others know they are taken.
As always, if you have any other questions, feel free to ask them here or let me know directly so I can help out :) Good luck!