c0pperdragon / EV3Basic

A basic compiler to target the Lego Mindstorms EV3 intelligent brick.
51 stars 19 forks source link

Sub input parameters & logic #13

Closed LaiYanKai closed 6 years ago

LaiYanKai commented 6 years ago

Hi. I am an undergraduate and have extensive experience with EV3G. Im trying to introduce EV3 Basic into my high school but I have some problems... 1) there is no xor or not logic operators in ev3 basic, would it be possible to add it into the library, or do I not know something?

2) in designing modules for use in competitions, for example pid linetracing, and particular those with many input parameters, I have to write a chunk of code just to get it set up. This is because i have to write the variables used as parameters, and each variable takes up a line of code. Would it be possible if we can do it the way u did to your library functions (one liner)

LaiYanKai commented 6 years ago

Sorry accidentally pressed submit button... I understand if it has to be a one liner the compiler has to be changed, because i would be changing the basic library itself. Would this be supported?

c0pperdragon commented 6 years ago

Hi! Im glad to hear that you want to recommend my EV3Basic to more students. But unfortunately I can not do much of the things you requested. Since the whole thing is constructed on top of Small Basic I must live with its restrictions. There is just no way to create proper functions and it is also not possible to split a program into seperate files. I thought about providing such features in my compiler as extensions. But then we would have two incompatible systems and this would just not be worth it. About boolean operations: Again the limits of Small Basic forbid to make good use of this. All comparator operations ( = >=, etc) can only be used inside an if or while, so there is no real use for boolean operators. But maybe you think about some bit-manipulation operations that would work on multiple bits at a time? Like shift, not, xor, etc. ? If you make a specific proposal, I could provide these as numerical functions in a library object.

Reinhard Am 28.12.2017 10:44 schrieb "LaiYanKai" notifications@github.com:

Hi. I am an undergraduate and have extensive experience with EV3G. Im trying to introduce EV3 Basic into my high school but I have some problems...

1.

there is no xor or not logic operators in ev3 basic, would it be possible to add it into the library, or do I not know something? 2.

in designing modules for use in competitions, for example pid linetracing, and particular those with many input parameters, I have to write a chunk of code just to get it set up. This is because i have to write the variables used as parameters, and each variable takes up a line of code. Would it be possible if we can do it the way u did to your library functions (one liner)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/c0pperdragon/EV3Basic/issues/13, or mute the thread https://github.com/notifications/unsubscribe-auth/AKaD1J663CVC9mzC19CqPF3mKtGAmGtrks5tE2MTgaJpZM4ROKwA .

LaiYanKai commented 6 years ago

Hello! Thanks for the quick reply.

1) Is there a way to achieve similar functionality to input parameters? I know there's a way, because you are able to do that in the functions you created for Motor, Sensor classes etc. But that would mean modifying the dll and the compiler which is crazy. Would it be possible to create a wrapper function to make the code neater and with less number of lines? Like if I have an one-argument function OAF with a parameter Arg1, I can probably call a method .F1(OAF, Arg1) to run OAF with Arg1 or like .F1Args(Arg1), then .F1Run = OAF. And if I made a three argument function TAF with arguments Arg1, Arg2, Arg3, it will be like .F3(TAF, Arg1, Arg2, Arg3) or .F3Args(Arg1, Arg2, Arg3) and then .F3Run = TAF. And if it is possible the Args passed into this method (I'm assuming it is pass-by-value) have to be cleared after my function has finished running, so an internal check by the run method would not run the function at all if the arguments are empty. This is to prevent things from going haywire.

2) Yes bit logic will be good. Because for high school students this would be a great way to teach boolean algebra and bit manipulation etc, which are fundamental to those who are keen on computing or robotics. The static methods, like what you mention could be as such, a) .Not(bit1, bit2) b) .Xor(bit1, bit2) c) .And(bit1, bit2) d) .Or(bit1, bit2) e) .Nor(bit1, bit2) I don't think this is necessary, but in case. f) .Nand(bit1, bit2) I don't think this is necessary too. g) .Xnor(bit1, bit2) probably not necessary g) .LShift(bit1, bit2) h) .RShift(bit1, bit2) i) .LRotate(bit1, bit2) j) .RRotate(bit1, bit2) k) .Bit(str) This converts the string input into the appropriate bit type. Maybe "True" can correspond to a 1-bit value of 1? "1010101" is of course 1010101. l) .2Dec(bit1) This converts the bit type into a base-10 number. 101 becomes 5. May not be useful though. m) .2Bin(bit1) This converts the bit type into a string of 1s and 0s. n) .2Hex(bit1) Converts the bit type into a string representing the hexadecimal number. May not be useful. o) .2Oct(bit1) Converts the bit type into a string representing the octagonal number. May not be useful. n) .Length(bit1) Returns the number of bits in bit1 o) .Index(bit1, index) Returns the bit on a multi-bit number bit1, LSB is index 0. bit1 and bit2 may have to be of a particular type (which is not numeric) because bits may have to be of a specific length for it to be more useful, particularly if it is used to monitor a set of conditions. For example, if a robot has to pass through a series of black and white areas, with bit 0 representing black and 1 as white, then if the MSB is the last area which is black, then clearly the MSB (0) must not be left out in the binary number representing the series of areas. In this sense, the type cannot be numberic, because it will leave out the MSB, and the bit type is something like a logic array. RShift would be useful here, as well as Xor or Xnor. The sensor typically compares the read value with a threshold value and the code would output true if the threshold is exceeded, and false if it is less than the threshold (or the other way around). Using xnor (or xor), we can then compare the output with the LSB of the multi-bit type and if the xor output is true, we can then Rshift the bit type to remove the current bit at LSB.

LaiYanKai commented 6 years ago

I take back my word on the bit length. I realised if i flipped the bit around so it starts reading the MSB first, that would still be accomplishable if a numeric type is used. So yes you are right pls use an intermediate numeric type?

Also I would like to add a method that reverses the order of the bits. Thats because some beginners may find it easier that way, if for some reason part of their program parses it from right to left and another part from left to right...

LaiYanKai commented 6 years ago

Imo if you are able to create the wrapper function for the muti parameter functions, it would greatly improve the useability of the program for competitons because it is lightweight and incredibly fast. One reason why i hate using ev3g is that a decent program lags the computer by a great amount, and graphic drag and drop really isnt the way to go for some high school students. Ev3dev is immensely powerful but the time it takes to upload files and run them is too long for students to have enough time to debug their programs. The other consideration is ev3basic, which imo is quite powerful, lightweight and fast, but the only problem is the code overhead for using user created functions and the logic operations, which imo isnt too hard to fix for u... i rly hope u can fix the issue, because it would greatly enhance its useability for competitions and its status as the next step for students who have mastered graphical programming.

c0pperdragon commented 6 years ago

About bit manipulation: It would make sense to privide this functionality in a library with functions to perform the standard bitwise functions on normal numerical types. The compiler for "brick" mode only supports 32-bit floating-point numbers, and the programms running on the PC handle everything as strings and convert them to 64-bit floating point numbers as needed. So the limiting factor is the "brick" mode with its 32-bit floats. While a standard 32-bit float has 23 bits as mantissa, only that many bits can be squeezed into a single value. For the sake of consistency with other things that treat the numbers as bits - mainly the I2C-communication features that are byte-oriented and the raw sensor readouts - I would propose to do the bit-manipulation also on single bytes (8 bits).
So, every of these functions will convert the incomming numbers to a 8-bit pattern, do the operation and then convert the result back to a floating-point number. To make the behaviour more obvious, I could call the library "Byte". With Byte.NOT, Byte.AND, Byte.OR, Byte.XOR, Byte.SHL, Byte.SHR, Byte.BIT(bitnumber) being the most important functions. Conversion from and to Strings ("01010101" -> 85 -> "01010101") could also be useful and would make it easier for students to understand the concept of binary representation. Going further to include hex and and octal also is probably too much.

c0pperdragon commented 6 years ago

About the suggested wrapper function: I have given the thing some more consideration, and there is actually a way to do this, even if it is a bit obscure how it needs to be done. The main problem is that Small Basic does not allow me to define library functions that receive a subroutine reference. The only way to pass subroutine references to libraries is by setting a specific property (like some callback handles for the user input on the PC and the Thread.Run property). So, my solution would then be to set up such handler subroutines at program start and later call them with a specific function. For example (lets call the Library 'Module'):

' set up the function and the wrapping Sub DoSomethingSub x = Module.Par[0] y = Module.Par[1] Module.Result = x + y Endsub DoSomething = 1 Module.Function[DoSomething] = DoSomethingSub

' call the function sum = Module.Call2(DoSomething, 14,17)

It is necessary to use the proper CallX - method with the correct number of parameters endocoded into the name. The type of the parameters would be restricted to plain numbers, and I don't see a nicer way to pass the desired function into the Call - method. Just by its number.

All the parameter and result passing will be done using global properties, so it is impossible to have recursive function calls or even functions calling other functions. This would also be made impossible by the inner working of the EV3 byte code interpreter.

But all in all, you can create functions that can be called in a one-liner and the result value can be immediately used for further purposes. Maybe I should make the names shorter for more compact coding:

M.C2(DoSomething, 4,5)

c0pperdragon commented 6 years ago

I just realized, that I can not make these callback-properties as arrays. So setting up the function would more look like

Module.Function = DoSomethingSub DoSomething = Module.FunctionIndex

So, every assignment to the Module.Function property will install a new possible function, and the counter will be maintained by this process automatically and can be read out with the FunctionIndex property and memorized for future use.

I am still not very happy with the fact that the arguments are passed in globally accessible properties. This makes cascading functions hard (but not impossible) and is a real pain to consider when it comes to multithreading.

c0pperdragon commented 6 years ago

About the function calling:

I am still not happy with any of the proposals. Mainly because it conflicts so heavily with other use cases and multithreading in particular (which was a real pain to get working in the first place).

Maybe we could go back to your proposal to at least reduce the number of lines needed for any call. Instead of setting up all parameters individually in one line each, you could create a whole array of parameters in a one-liner and then use the normal subroutine call. Providing a way to initialize an array with different values in a single call was on my to-do list for a long time. This could now be the really useful application for this feature (next to other uses like initializing some static data array).

DoSomethingPar = Vector.I3(4,5,6) DoSomething()

Again this would restrict the use to numbers, but strings can still be passed as individual variables like before.

LaiYanKai commented 6 years ago

Hey! Yes the byte operation thing is fantastic, it should be implemented. This enables the students to exercise their creativity, which i found to be lacking in EV3G. Judges are also looking for creative solutions, so this would be a win-win for us all. The competition I'm looking at is World Robotics Olympiad (WRO), Tertiary (Senior High) category.

Talking about bytes, on a side note, HiTechnic Color sensor V2 stores its Colour, R, G, B and White (CRGBW) data on registers 66, 67, 68, 69, 70, so using your I2C read registers function, we can read the sensor incredibly quickly without changing modes. Having the byte operation can greatly complement the learning value when students are encouraged to use the I2C operation. This sensor is in most ways superior to the EV3 Colour sensor and can be used in WRO.

I understand that all variables are global, so what we did is to use a particular naming convention to differentiate the main program variables from those used in the functions. So this in a sense makes some variables "local" to the program. Just saying.

Since EV3G does not allow recursive calls and concurrent running of the same function (myBlocks in EV3G), the students have more or less lived with it, so probably it is still too early to talk about recursion.

The vector thing is quite useful. But text parameters are particularly important, like "True" and just text. Since we have to live with global properties, there must be a way to pass the parameters (num, logic, text) to the functions.

It is probably okay to start with such inelegant solutions first, because everything is a work in progress...:

'Assign the parameters Sub DoSomethingSub 'the code to lock the module class so i can extract the parameters x = Mod.P1 'the property must accept all ev3 allowed types. y = Mod.P2 'similar to P1 Mod.PUnlock() 'unlock .P method and the P1 and P2 properties etc. and flush them to be used by other functions. maybe return "EMPTY" if they are not properly called? I took this idea from the Threading.Mutex. This should solve the problem of functions calling other functions? 'Do something else Mod.Result = x + y 'accepts all ev3 allowed data types. EndSub

DoSomething = Mod.Key (the FunctionIndex, I called it as such since it is the key to call the function, like in threading classes used in other languages)

sum = Module.C2(DoSomething, Arg1, Arg2) 'this should lock the use of the properties that are used to pass the parameters 'C1 to C10, since EV3G can only accept up to 10 parameters. Of course you might want to design more calls that accept more parameters to be an edge over EV3G...

c0pperdragon commented 6 years ago

So, after a long walk through the winter, I came up with the following solution. It may look strange, but it will probably do the job (I am still not sure about the naming, I call the library "Function" for a start):

' define a function with 2 numeric parameters and a numeric return value Function.F = SUM Function.Define("SUM", "NUMBER NUMBER", "NUMBER") Sub SUM A = Function.Par("SUM",0) B = Function.Par("SUM",1) R = A+B Function.Return("SUM", R) ' or to write it short without polluting the global variable namespace: ' Function.Return("SUM", Function.Par("SUM",0) + Function.Par("SUM",1)) EndSub

' call the defined function X = Function.Call2("SUM", 17,4)

In the PC mode, there are no type restrictions on the variables and function arguments, so there will be no problem to pass arbitrary types. The parameter/return values for every different function will be stored in a global storage, maintained by the Function library. But there will be only a single storage for every function. So while it is perfectly fine for the SUM function to call other functions, it should not call itself because this would overwrite the parameter store. Also concurrently running a function twice leads to the same problem.

For the brick mode, everything needs to have one of the 4 supported types: number, string, array of numbers, array of strings. By making it mandatory to pass the function identifier (the "SUM" in this case) always as a constant string literal, my compiler can figure out all parameter types and result types. Besides the problem with overwriting the global parameters/return values there is no restriction in concurrency as it would be with blocks in the EV3G system. But since a thread without local variable storage is pretty useless, this can not be used anyway.

It is a bit annoying, that you have to explicitly encode the number of parameters in the call, but Small Basic does not support library functions with a variable argument count.

It will be fairly easy to put this feature into the Small Basic extension (PC mode) to experiment with it. Getting it working in the brick mode will require some substantial extensions to the compiler.

What do you think?

LaiYanKai commented 6 years ago

Woah yes. I know nuts about the compiler side but on the user side of things that's perfect since ev3g does not allow concurrency and recursion, but you can implement some form of concurrency. This will be great.

I thought about the concurrency. There are many helper functions which need to be executed concurrently but lasts only a short while. This means that if the user can design a code to lock the function and its data structures by simply monitoring a global variable before running the function, then the overwrite problem for concurrency can be "solved", since the wait isn't too long to be noticed.

And since the same functions are called consecutively during recursion, the user can just design a code to extract all the data before the properties in the Function class are overwritten.

Besides, there is always a simpler solution when the same function is ran concurrently AND recursively... so don't worry about the overwriting yet! Whatever you proposed about the parameters is in my opinion sufficient for high-school students. That's because they can learn about function calling, and also appreciate (and possibly learn) how lower-level codes handle concurrency and recursion.

As for the module and caller names, like what you said, should be kept short so that a function with multiple params can be seen within the editor window when it is called (methods used to extract the parameters will be fine). Maybe if u use Function, it can be shortened to Fx (since thats how they are commonly depicted in STEM illustrations), and Call1 be C1 etc. Maybe NUMBER can be shortened to NUM (if this makes it confusing because "NUMBER" is used elsewhere in the extension, then we can just use "NUMBER"). Just a note EV3G accepts only up to 10 params (input + output).

I am honestly quite amazed by your dedication to this project.

c0pperdragon commented 6 years ago

After some more consideration, I have now found a solution that would work much better. It comes down to indeed having a separate storage area for each individual thread. Using this storage for local variables, there will be no issue with concurrency in accessing this variables. Also true recursion would be possible. The whole thing looks like this:

' define a classic recursion example: towers of hanoi. ' parameter 0: name of stack to take disks from ' parameter 1: name of stack to put disks to ' parameter 2: name of temporary stack ' parameter 3: number of disks to move ' return value: total number of moves ' temporary variables: 3 numbers F.F = HANOI F.Define("HANOI", "STRING STRING STRING NUMBER", "NUMBER", "NUMBER NUMBER NUMBER") Sub HANOI If F.P3<2 then TextWindow.WriteLine(F.P0 + " -> " + F.P1) F.Return = 1 Else F.V0 = F.C4("HANOI", F.P0, F.P2, F.P1, F.P3-1) F.V1 = F.C4("HANOI", F.P0, F.P1, "", 1) F.V2 = F.C4("HANOI", F.P2, F.P0, F.P1, F.P3-1) F.Return = F.V0+F.V1+F.V2 EndIf EndSub

X = F.C4("HANOI", "A", "B", "C", 10) TextWindow.WriteLine("Done in "+X+" moves");

The whole magic more or less happens in the C4 - operation. This will create a new local data storage that has space for the 4 parameters, 3 temporary values and 1 return value. Then the specified subroutine will be called and after its return, the local data storage will be removed and the return value will be passed out of the C4 function. By having a separate data storage stack for each thread, the whole thing can easily be used for a full-blown multi-threaded operation (for example: solving 5 instances of towers of hanoi in parallel)

With this naming scheme, the code is pretty compact. That there are no nice names for the variables, but only this generic properties for the parameters and locals is ugly, but I can not help it. You need to document the parameters properly in comments.

For all this magic to work in the EV3 brick also, I need to restrict the possible types to numbers and strings. And the name of the function to call or define must always be a direct string literal.

LaiYanKai commented 6 years ago

Hey, that's FANTASTIC! 1) Is it possible to take it one step further for the names of the vars and params?:

F.F = HANOI
F.Define("HANOI", "STRING STRING STRING NUMBER", "NUMBER", "NUMBER NUMBER NUMBER")

' Since there is only one result, it doesn't need to be named
' I'm not sure if the literals are case-sensitive, but assuming it is (fine otherwise):
F.DefineNames("HANOI", "StackTake StackPut StackTemp NumDisks", "V1 V2 V3")

Sub HANOI
  If F.P("NumDisks") <2 then
    TextWindow.WriteLine(F.P("StackTake") + " -> " + F.P("StackPut"))
    F.Return = 1
  Else
    F.V("V1") = F.C4("HANOI", F.P("StackTake"), F.P("StackTemp"), F.P("StackPut"), F.P("NumDisks")-1)
    F.V("V2") = F.C4("HANOI", F.P("StackTake"), F.P("StackPut"), "", 1)
    F.V("V3") = F.C4("HANOI", F.P("StackTemp"), F.P("StackTake"), F.P("StackPut"), F.P("NumDisks")-1)
    F.Return = F.V("V1")+F.V("V2")+F.V("V3")
  EndIf
EndSub

'Alternatively
F.F = HANOI2
F.Define("HANOI2", "STRING STRING STRING NUMBER", "NUMBER", "NUMBER NUMBER NUMBER")

Sub HANOI2
  If F.P(3)<2 then
    TextWindow.WriteLine(F.P(0) + " -> " + F.P(1))
    F.Return = 1
  Else
    F.V(0) = F.C4("HANOI", F.P(0), F.P(2), F.P(1), F.P(3)-1)
    F.V(1) = F.C4("HANOI", F.P(0), F.P(1), "", 1)
    F.V(2) = F.C4("HANOI", F.P(2), F.P(0), F.P(1), F.P(3)-1)
    F.Return = F.V(0)+F.V(1)+F.V(2)
  EndIf
EndSub

X = F.C4("HANOI", "A", "B", "C", 10)
TextWindow.WriteLine("Done in "+X+" moves");

2) Just wondering, is there no way to output or input numeric arrays?

' Like this returns the array 
F.Return = Vector.Add(5, F.P("SomeArray"), F.P("AnotherArray"))

' Since it is impossible to do this: 
F.P("SomeArray")[2]
' Maybe we can get the value at the index by doing this:
F.PA("SomeArray", 2)
' Or maybe the vector class can be written to get the index:
Vector.Val(F.P("SomeArray"), 2)
c0pperdragon commented 6 years ago

Well, I could provide some accessor function to read the parameter values by name instead of having numbered propertierties . But having named variables for read and write would be very cumbersome because every write would require a function call instead of an assignment.

... F.Define("HANOI", "FROM:STRING TO:NUMBER TMP:NUMBER DISKS:NUMBER", "NUMBER", "NUMBER NUMBER NUMBER") ... If F.P("DISKS")<2 Then .. F.L0 = F.C4("HANOI", F.P("FROM"), F.P("TMP"), F.P("TO"), F.P("DISKS")-1)

Again I have to say, that I can not have any arrays here. While this would work in Small Basic, I could never get this into the EV3 brick in a sensible way. So arrays are only possible as global variables.

LaiYanKai commented 6 years ago

Hey! Yes. I agree that it is cumbersome too, making the statements quite long. A short comment on the functions will do the job of informing users about how to use the vars and params, so the approach on the earlier post should work fine.

That said, when would you be roughly completing this next version? hehe, I'm quite excited to get my hands on it.

c0pperdragon commented 6 years ago

After having slept over the matter, I changed my mind just once again. Now I think it would be nice to unify the parameters and the local variables. So it would be possible to use the parameters exactly like local variables and even modify them like it is possible in most programming languages (java, javascript, c, etc.). So this would mean to provide getter and setter functions to do so instead of using assignments. Also it is not necessary to explicitly specify the types of the local variables (will do type inference for the brick compiler, for PC mode, no types are used). For the parameters and return values I still need types for the brick mode (for PC mode, the type information will be ignored).

And maybe using only a single letter for the command names is a bit too compact and unreadable. My proposal would now be (doing more unification of the syntax):

F.Sub = HANOI F.Function("HANOI", "FROM:STRING TO:STRING TMP:STRING DISKS:NUMBER", "NUMBER") Sub HANOI If F.Get("DISKS") <2 then TextWindow.WriteLine(F.Get("FROM") + " -> " + F.Get("TO")) F.Return(1) Else F.Set("V1", F.Call4("HANOI", F.Get("FROM"), F.Get("TMP"), F.Get("TO"), F.Get("DISKS")-1)) F.Set("V2", F.Call2("HANOI", F.Get("FROM"), F.Get("TO")) F.Set("V3", F.Call4("HANOI", F.Get("TMP"), F.Get("TO"), F.Get("FROM"), F.Get("DISKS")-1)) F.Return(F.Get("V1")+F.Get("V2")+F.Get("V3")) EndIf EndSub

Note, that it is here allowed to ommit parameter when calling a function. The missing parameters will be initialized to the default value "0" (or 0 in brick mode for a number typed parameter)

I am not sure if there will be much use of the feature, but I intend to make it possible for such a function to consist of multiple subroutines that can call each other via the normal small basic subroutine call. For the PC mode this comes without restrictions, for brick mode I will impose some compiler checks to make sure that all subroutines that call each other directly are indeed in the same function context.

This generic Get and Set functions can also be used on the top level outside of any function context if you need to have thread-local variables there. But these variables are then only visible on the top level and not inside any function context. For globally visible stuff, you still need the normal global variables that are shared by all functions and threads.

About timelines: I am not sure when I will have time to implement all this magic stuff. I guess I can come up with the implementation for PC mode quite soon, but the brick mode compiler will take longer. (did you notice, that I have not given you any date here? ;-) )

c0pperdragon commented 6 years ago

As a proof of concept, I have patched together a quick implementation of the "Byte" and "F" library for use in PC mode. No documentation for the functions and of course no support for the brick mode compilation yet. You can fetch this experimental version from:

https://1drv.ms/f/s!AuAO_Q-atz3PaVbs7BahvHZtApQ

The version number is still just 1.2.0 - when I have a stable and correctly working version, this will then become 1.2.1. Please use this installation file only now for experimenting with the feature and don't pass it to other persion..

LaiYanKai commented 6 years ago

Haha, I asked you about the time because the WRO challenge will come out by 15 Jan, but really the time is only at the back of the mind. That's because I have to create or improve lesson plans of which the bulk of it would be mechanical, and it takes only a short amount of time to prepare the plans for programming. And I would have until around the middle of March to prepare the stuff, which is quite a lot of time. So honestly, no rush! Besides, there would still be enough time for other organisations interested in EV3 basic to learn and use it for WRO. I'm based in Singapore, in fact, I am currently interning at a company (Duck Learning, LEGO Education sole distributor in Sg) that organises the national-level WRO competition in Sg. I'm looking into ways to improve the competitiveness (scores) of teams in Sg and more importantly my high school. I feel that EV3 Basic could be the next step, hence all these threads. And YES, good magical stuff are worth the wait!!!

Note, that it is here allowed to ommit parameter when calling a function. The missing parameters will be initialized to the default value "0" (or 0 in brick mode for a number typed parameter)

I recommend using "-0.1" or -0.1, that is because usually 0 is a valid data point for many sensors, including colour sensors, while negative, non-integer numbers are not. And since input can come from either raw or manipulated sensor data, it will become a lot easier for users to differentiate a valid data point from a "NULL" data point.

Hmmm... Is there a way to assign the default values? like F.Function("HANOI", "FROM:STRING:A TO:STRING:B TMP:STRING:C DISKS:NUMBER:10", "NUMBER")

I am not sure if there will be much use of the feature,

When programming the robots, I routinely run into functions which can be shortened. For example for PID Line tracing or comparing the RGB values from the colour sensor, the "PORT" argument is almost always the same, except for a few instances. So YES THERE WILL BE OF MUCH USE TO THIS FEATURE.

LaiYanKai commented 6 years ago

Hey, I have looked through the Byte class. I played with negative numbers and they work absolutely fine. It is currently limited to 8 bits, but could it be possible to extend beyond that (just in case, if the robots need it)? So probably byte can be phased out because it is actually a generic form? I propose:

The following have the same methods and parameters (probably not the NOT method)

Bin.And(Num1, Num2)
Bin.Or(Num1, Num2)
Bin.Not(Num1)
Bin.Xor(Num1, Num2)
Bin.Shl(Num1, NumShifts)
Bin.Shr(Num1, NumShifts)
Bin.Bit(Num1, Index)

For the FromBinary and ToBinary etc. methods, I propose renaming them, because I found them confusing...

Bin.Str2Bin(Str1) instead of Bin.FromBinary(Str1)
Bin.Bin2Str(Num1) instead of Bin.ToBinary(Num1)
Bin.Str2Hex(Str1) instead of Bin.FromHex(Str1)
Bin.Hex2Str(Num1) instead of Bin.ToHex(Num1)

--or---

Bin.Str2Num(Str1, Base) 
Bin.Num2Str(Num1, Base)

Where Base is either "BIN" or "HEX"

BTW, there is only up to a call5 method so far for the Function class, it is still a work in progress right, because there should be at least a call10 method (hehe, bcos EV3G can support up to 10 parameters)?

c0pperdragon commented 6 years ago

I was considering having more bits in a number. But this would have been limited to 23 bit because of the used numerical precission on the brick. I could support 16-bit words to make it look less silly, but that would stilll mean not much to a novice in computing. But a "byte" is really something you need to know about. And having bytes that actuallly consist of bits makes really sense from an educational point of view. One thing I can not solve is the fact than AND and OR are reserved words in Small Basic, so I have called the functions AND and OR

c0pperdragon commented 6 years ago

Your first proposal to rename the conversion methods is even more confusing. Str2hex would suggest that you convert a string to a hex representation, which is also a string. The second proposal is cumbersome to use and hard to implement.

My Byte.ToHex and Byte.ToBinary is perfectly clear in this respect. I must admit that the FromHex and FromBinary are a bit confusing. Maybe you have a better idea?

c0pperdragon commented 6 years ago

The whole F library is not finished. I will support 20 or so parameters,

About default values: The parameter specifier will look like "X:0 Y:1 MSG:HELLO" which will specify default values as well as implicitely the types. Same thing for the return type specifier which also contain a default return value (for the case that the function does not call the Return command). This would eliminate the need for the NUMBER and STRING keyword inside the specifier strings.

LaiYanKai commented 6 years ago

I'm okay with limiting it to 8 bits from an educational point of view, two's complement, size of bytes, bitwise operations etc. Besides, if we really want to expand the functionality, we can always use the powerful F library. Do you think, in the context of using Bytes, that a rotate left or right function would be good? I saw the amount of work you did on the Byte.txt, it's crazy, so I'm perfectly alright if that isn't implemented.

Yep I agree with the Str2Hex and Str2Bin. That's why I called it Str2Num, because it is about converting a string representation into a numeric data type, so that it is easier for beginners to use them and fit into the parameters. Personally i saw Hex and Bin as numbers (probably other novices would), instead of strings, and that's where the confusion arose, so I thought maybe it could be clearer to use Str2Num. If I persist on this current proposal, I would modify to HexStr2Num(Str1) and BinStr2Num(Str1). But really, we can always look into the documentation to know how to use it. In that regard, I'm fine with the current proposal. I can't ask too much because that will be too much work for you!

The parameter specifier is great, never would have thought of it. I could sense the potential problem of passing numbers if they are meant to be strings, but since this is small basic, the primitive doesn't matter. Thanks!

c0pperdragon commented 6 years ago

Hi! It was quite some difficult stuff, but I finally managed to make my basic compiler to support the function call framework. You can get a preview again at: https://1drv.ms/f/s!AuAO_Q-atz3PaVbs7BahvHZtApQ

I had to redo some of the commands, mainly the way how the function return type is handled. Also there is now no specific support for local variables. Since the call parameters can be written to, and the caller does not need to provide values for all parametes, this mechanism can be easily "abused" for local storage. Recursion also works, but comes with some performance penalty when doing the calls (I had to manually implement a stack for the locals which is probably pretty slow).

The Byte library had minor changes too. I will provide the commands to convert from string to numbers as Byte.H and Byte.B to make it short and easier usable when you want to just include some numeric values in the code.

This version is not finished, because I expect that there are some bugs. And documentation is pretty thin and not translated in any language. I will continue on these some time sooner or later

It would be nice if you could test the new functionality (see examples "Function", "Recursion", "Concurrency" for more details).

LaiYanKai commented 6 years ago

Hey!

Thank you so much! I will test and reply by Sunday 1600 GMT lolol.

LaiYanKai commented 6 years ago

Hi! I have not been able to test the code on the EV3 yet (I'll do that in the coming week). But I managed to code some of my functions. Here are some of my suggestions:

1) Documentation: It could probably be useful to note that function names and parameters are case-insensitive, particularly because the string literals will be uppercased after getting parsed by the F.Function.

2) The Byte library would be more useful if there is a helper function to convert "True" and "False" to 1 and 0 respectively. Like Byte.L("True") and Byte.ToLogic(1) or something like that?

3) In order to write more complex logic, it could be useful to store the result of a comparison (which EV3 G is able to do without the if else overhead):

So instead of the silly if else overhead in small basic

If a >b Then
  c = "True"
Else
  c = "False"
EndIf

It could be better to make do with a function like SomeClass.MoreThan(a, b). If this could be expanded, the following methods could be implemented:

'The following emulates the compare block for EV3G
'a and b are numeric inputs
.MoreThan(a,b)
.LessThan(a,b)
.MoreThanEquals(a,b)
.LessThanEquals(a,b)
.Equals(a,b)
.NotEquals(a,b)

'Otherwise, a more compact way of doing it:
'comp is the string representation of the operator in small basic
.Compare(a, comp, b)
'e.g.
.Compare(a, "<>", b)

A similar thing can be done for logic input:

'The following emulates the logic block for EV3G
'a and b are logic (string literals "True" "False") inputs
.Xor(a,b)
.And_(a,b)
.Or_(a,b)
.Not(a,b)

4) I feel that a split function could be written in the F library so that if there is more than 1 output, the function can return a string of data delimited by a delimiter of choice (by concatenating the data with delimiters). In the main thread, the data can then be constructed into an array using the split function: F.Start = Data F.Function("DATA", "DEL:[ A:1 B:2 C:3") Sub Data F.ReturnText(F.Get("A") + F.Get("DEL") + F.Get("B") + F.Get("DEL") + F.Get("C")) EndSub F.Call0("DATA") a = F.Split("A,B,C,D", "[")

5) Is there a way to escape the space and colon characters in F.Function?

6) Actually, isn't it possible with what you did so far to create a method to write and return arrays on a single line?

Thanks!

c0pperdragon commented 6 years ago

Hi!

  1. You are right, the documentation needs much more work and explanations. I will continue on this when the features for the version are done.

  2. This is no problem. I can easily just add what you proposed.

  3. Here you are probably out of luck. For some reasons Basic in all its dialects that I know of had always this limitation that logic operations are only possible in the appropriate statements ('if', 'while' etc.). Also it normally only had the barest minimum of logic operators ("and","or"). I guess this was done to make the language as small as possible. Indeed you can formulate all logic operations with just these two operators when you chose the right compare operations (using De Morgan's laws to rearrange the expression to eliminate all NOTs). For the sake of simplicity, I don't want to introduce a whole parallel universe of logic operators. (And also to avoid the performance overhead for function calls, string manipulation, data conversion and the fact that I could not do short-circuit evaluation)

  4. Returning multiple values from a function mangled into a string is probably not very performance efficient and should normally be avoided (using a global array would probably do this). But when there is a legitimate use case and performance is not such an issue there, this could make sense. I will make such a split command that will extract a series of numbers from a comma-separated string. This will naturally fit into the EV3File library where it can also be used to parse numbers from data files. EV3File.ParseNumbers(line) that returns an array of numbers would be a nice choice.

  5. There is no way of escaping string default values there. I decided against it to keep the library as simple as possible (which is already not simple at all). I can not think of many cases where it would make sense to have a string parameter defaulting to a decimal number. Even then you can easily work around this in the code of the function.

  6. No. No passing of arrays in and out of functions. Only global variables can hold arrays. This limitation comes from the way my compiler works and from the functionality the EV3 virtual machine provides. While I could in theory somehow overcome this limitation, this would come at a huge cost in performance and complexity.

c0pperdragon commented 6 years ago

Adding to point 5: The colon character does not need escaping. Only the first colon will be used as delimiter, so you can easily use "X::" to make a X defaulting to ":" (at least this I intended - if it does not work this would be a bug to be fixed).
Having spaces in the default value is currently not possible. I don't want to introduce this complex handling of edge cases in a beginner level language for no real purpose.

LaiYanKai commented 6 years ago

Hello! Thanks for implementing the suggestions!

Regarding (3), yes if there is plenty of overhead on the compiler side, then forget it. I'm trying to see if there is an efficient way (particularly short-circuiting, and less overhead with compiler) of doing this. Since it is not possible, then the if else will be good to work with. The judges crave some complexity, but this reason you provided is even better.

I stated (4) primarily because it could be faster to read multiple data at once from the sensors (like reading all the color, R, G, B and white values for HiTechnic sensor V2 and the 2 raw reflected light intensity values for the color sensor) and returning their values. But come to think of it, if there is no efficient way of doing it on the compiler side, then we can always read values one at a time from the sensor, which should be as fast as reading multiple values...

I thought of the file thing too lol, that would be a great idea!

Thanks for the tip on (5) for colons. In fact my concern was if spaces were allowed in the default. But since it's just a short line of code to change the default to spaces after calling the function, it really doesn't matter.

Noted for (6)!

I'll keep in touch if there are any bugs. It will take some time. The first time I could fully commit to testing a robot on the WRO map is coming Saturday. I would do some quick testing before that. Actually so far I find the libraries stable (I haven't got to the level of concurrency and recursion as that of your examples, and I probably wouldn't. However, I will be testing the libraries with the motors, sensors and ev3 features soon).

c0pperdragon commented 6 years ago

One more thought about (4): Your use case for reading multiple sensor values is actually a very good reason to use only a single function call. There is a massive time overhead for each individual I2C communication (in the order of multiple milliseconds), and the cost of string concatenation and parsing is probably much less (but I would need to actually benchmark this). So something like

RGB = EV3File.ParseNumbers(F.Call("READCOLORS", 1))

could perfectly make sense.

c0pperdragon commented 6 years ago

Hello again! I have thought about (3) a bit more recently, and there could indeed be a very funky way to achieve your goals without adding anything more to the EV3 libraries.

The core part will be the exact behaviour of the new Byte.ToLogic function: For every positive value it will return "True", for 0 or negative values it will return "False". Following this scheme, you could then also treat such numbers as logic values for the purpose of storing and computation.

Using just the standard Math library, you could implement every logic operator imaginable:

  A AND B                         Math.Min(A,B)
  A OR B                            Math.Max(A,B)
  NOT A                             (1-Math.Ceiling(A))

  X > Y                               (X-Y)
  X < Y                               (Y-X)
  X >= Y                            (1+Math.Floor(X-Y))      
  Y <= X                            (1+Math.Floor(Y-X))      
  X <> Y                            Math.Abs(X-Y)
  X = Y                               (1-Math.Ceiling(Math.Abs(X-Y))

Since all these functions from Math are inlined into the bytecode where used and the arithmetic operations are also quite light-weight, this approach would actually be quite fast. Of course it will quickly become a nightmare to read and understand, but it has a kind of puzzling elegance ;-) I guess I can also make the Byte.ToLogic function inlined too for best performance.

As a challenge your the reader: I am quite unhappy with bulky implementation of the = operator (just the <> and NOT stacked together). If you could come up with something more elegant, that would be nice.

For special cases where X and Y are guaranteed to be integer values, some compare operators could be written shorter:

  X >= Y                            (X+0.5-Y)      
  Y <= X                            (Y+0.5-X)      
  X = Y                               (1-(Math.Abs(X-Y))
c0pperdragon commented 6 years ago

Maybe the previous post needs a tiny bit of clarification. Imagine you want to write something like:

X = A < B AND C < D

which is not possible in SmallBasic, you could instead write

X = Byte.ToLogic(Math.Min(B-A,D-C))

in just one line.

LaiYanKai commented 6 years ago

Hey! Sorry for the late reply, quite busy recently...

That's a nice idea for the logic and ToLogic, I never thought that math can be used to replace the logic functions! With these ideas and the F class it is possible to achieve the output. It will also be a nice exercise for advanced students to understand more.

I am honestly very very happy with the capabilities you have designed so far. As for the bugs and issues, it could be a course of two or three months before my students and I can identify them...

c0pperdragon commented 6 years ago

So, I finally implemented the two commands new for Byte and created the Vector.Data command to split a string into a number array. Instead of putting this into the EV3File object, I think it makes more sense to add it to the Vector object. I have placed an updated build package at

https://1drv.ms/f/s!AuAO_Q-atz3PaVbs7BahvHZtApQ

LaiYanKai commented 6 years ago

Hey man, thanks! My students / juniors and I have been writing out some functionalities with the experimental program, I will get back to you in about 2 to 3 weeks if we spot any bugs!

LaiYanKai commented 6 years ago

Hi! One of my students have been using this software.

However, is it possible if we could make a function call non-blocking (call on a new thread): We have F.Call1 etc. but I suggest adding new methods like F.TCall1 to make that particular call non-blocking like the motor methods? For example, we need to line-trace (one function) and color sense on another sensor (another function) at the same time...

We can technically use Sub, call the function in it, and then use a thread.run, but this means that we can't use the parameters.