Παρασκευή 26 Δεκεμβρίου 2025

Nested Functions

The M2000 programming language has two types of inner (nested) functions (and some variations for the first type).

Normal Function: 

- We can nest normal functions:

Here we have two functions beta(), but only one inner (nested) in afa() function. A normal function may have modules and functions. Each inner function has own scope, so we can't call something out of block of code of function (except the sane name, as a recursion call)

function beta(y) {
=y/2
}
function alfa(x) {
function beta(y) {
=y*100
}
=x*beta(x)
}
print alfa(20)=40000
print beta(40000)=20000


- Using lambda functions:

A lambda function is an object which combines a list of variables and code for normal function. We can place a lambda function as variable in the list of variables of lambda. The first part of the next example has alfa function which define a nested function beta(). The second part, has a alfa function which have a closure beta which is lambda function, so this definition of alfa has beta all the time (but the first definition has beta at the moment we defined it - so we have some space before which beta not exist).

beta=lambda (y)-> {
=y/2
}
alfa=lambda (x) ->{
function beta(y) {
=y*100
}
=x*beta(x)
}
print alfa(20)=40000
print beta(40000)=20000


beta=lambda (y)-> {
=y/2
}
alfa=lambda (y)->y*100
alfa=lambda beta=alfa (x) ->{
=x*beta(x)
}
print alfa(20)=40000
print beta(40000)=20000


The alfa lambda function can be written like these too:

alfa=lambda
beta=lambda (y)
->{
=y*100
}
(x)
->{
=x*beta(x)
}
print alfa(20)=40000


alfa=lambda
beta=lambda (y)->y*100
(x)
->{
=x*beta(x)
}
print alfa(20)=40000

alfa=lambda
beta=lambda (y)->{
=y*100
}
(x) ->{
=x*beta(x)
}


alfa=lambda
beta=lambda (y) ->y*100

(x) ->{
=x*beta(x)
}


- We can use normal functions as object functions, so we can use siblings functions (as objects functions) as well as inner functions. Using object we gain a state part which we can study or display later:

group delta {
long counter
function beta(y) {
// increment this.counter
.counter++
=y*10
}
function alfa(x) {
function beta(y) {
=y*100
}
=x*.beta(x)+beta(x/2)
}
function alfa2(x) {
function beta(y) {
=y*500
}
// call this.beta() and local beta()
=x*.beta(x)+beta(x/2)
}
}
print delta.beta(10)=100
print delta.alfa(20)=5000
print delta.alfa2(20)=9000
print delta.counter=3


We can't call delta.alfa2().beta() becauses beta() not exist at the level of delta. When we call the delta.alfa2() then the Function statement define the function (writing in a list of functions), and at the exit from delta.alfa2() the inner function deleted. From delta.alfa2() we can call the delta.beta() because it is a member of delta, but without using the delta (because this is something not known for the context of delta.alfa2()). We use the THIS object as this.beta() or .beta() which is the same.

We can use Class statement to make one or more objects:

class delta {
long counter
function beta(y) {
// increment this.counter
.counter++
=y*10
}
function alfa(x) {
function beta(y) {
=y*100
}
=x*.beta(x)+beta(x/2)
}
function alfa2(x) {
function beta(y) {
=y*500
}
// call this.beta() and local beta()
=x*.beta(x)+beta(x/2)
}
}
delta=delta()
print delta.beta(10)=100
print delta.alfa(20)=5000
print delta.alfa2(20)=9000
print delta.counter=3

We can make pointers to objects too. An object created as a pointer to object deleted after last pointer deleted, but an object like delta which created as "named object" or group, deleted same time as all variabled deleted in a module or function, at the end of call, or in the For object { } block at the exit of block. See the => used instead of dot for addressing a member through a pointer of object (group).

// same as delta=pointer(delta())
delta1->delta()
print delta1=>beta(10)=100
print delta1=>alfa(20)=5000
print delta1=>alfa2(20)=9000
print delta1=>counter=3


Simple Function:

This example is like the one with an object (group delta), but now we use simple functions. We can define functions in a simple function using @ (which call always the normal function constructor). Without the @ workaround the search for a simple function return error. Simple functions are not listed in the list of functions, but instead first time searched from the end and then listed as part of code of current scope. A simple function has no own scope (use the caller scope, so in the example counter is on scope in any simple function) but anything new defined (using Local statement to hide a previous defined identifier) would erased at the end of call. Also a simple function use same stack of values from the caller (this speed up the call). The function new variation make a new function always (without new the new definition replace an old one if the name exist).

long counter


print @beta(10)=100
print @alfa(20)=5000
print @alfa2(20)=9000
print counter=3


function beta(y)
counter++
=y*10
end function
function alfa(x)
@function new beta(y) {
=y*100
}
=x*@beta(x)+beta(x/2)
end function
function alfa2(x)
@function new beta(y) {
=y*500
}
=x*@beta(x)+beta(x/2)
end function



Τρίτη 16 Δεκεμβρίου 2025

Search Inkscape.exe (or any exe file) Updated

This is the new solution using ItemUrl which return file://path using / as URL. Also there is a way to read the path from link. Path from link maybe is ANSI encoded for language of OS system (we have to use the proper locale id (using Locale 1032 for greek names), or UTF16LE. So if you make a text file والكليات.txt in Documents and make a lnk file and then move this file to Desktop folder (and rename the link as والكليات ) then the inner function findlnk find the unicode name. All names have 0 at the end (like all C strings).

When we load the lnk file in a buffer we get offsets from base 0 (not base 1 for file). 


function findExefile(this$, typ$="exe") {
function findlnk(linkfile$) {
function ReadSingleString(b as buffer, offset) {
=leftpart$(chr$(eval$(b, offset, len(b)-offset)), chr$(0))
}
function ReadDoubleString(b as buffer, offset) {
=leftpart$(eval$(b, offset, len(b)-offset), chr$(0))
}
=""
let a=buffer(linkfile$), offset=0
if eval(a, offset as long)=76 then
ShortcutFilename=""
Long offset=20
long Flags=a[offset]
if binary.and(Flags,2)>1 then
offset= eval(a, 76 as integer)+76+2
end if
if binary.and(Flags,2)>0 then
TotalStructLength=eval(a, offset as long)
PtrBasePath=eval(a, offset+16 as long)
PtrNetworkVolumeInfo=eval(a, offset+20 as long)
PtrFilename=eval(a, offset+24 as long)
If PtrBasePath Then
ShortcutFilename = ReadSingleString(a, offset + PtrBasePath)
if len(ShortcutFilename)<TotalStructLength-PtrBasePath-2 then
ShortcutFilename=ReadDoubleString(a, offset + PtrBasePath+len(ShortcutFilename)+1)
end if
Else.If PtrNetworkVolumeInfo Then
ShortcutFilename = ReadSingleString(a, offset + PtrNetworkVolumeInfo + &H14)
End If
If PtrFilename Then
Str = ReadSingleString(a, offset + PtrFilename)
If Str <> "" and false Then
If Right$(ShortcutFilename, 1) <> "\" Then
ShortcutFilename += "\"
End If
ShortcutFilename += Str
End If
End If
= ShortcutFilename
end if
end if
}
ShortcutFilename=""
declare cn "ADODB.Connection"
method cn, "open", "Provider=Search.CollatorDSO;Extended Properties='Application=Windows';"
search$={SELECT System.ItemUrl
FROM SystemIndex
WHERE System.FileName = '@@@.###'
}
if typ$="" else
method cn, "execute", replace$("@@@", this$, replace$("###",typ$,search$)) as rs
with rs, "eof" as rs.EOF, "fields" as field()
while not rs.EOF {
ShortcutFilename=field(0)
method rs, "movenext"
}
end if
if ShortcutFilename="" else =replace$("/","\",rightpart$(ShortcutFilename, ":")): exit
method cn, "execute", replace$("@@@", this$, replace$("###","lnk",search$)) as rs
if Valid(rs.EOF) Else with rs, "eof" as rs.EOF, "fields" as field()
while not rs.EOF {
ShortcutFilename=field(0)
method rs, "movenext"
}
if ShortcutFilename="" then exit
=findlnk(replace$("/","\",rightpart$(ShortcutFilename, ":")))


}
Print findExefile("Inkscape")
Print findExefile("والكليات", "txt")

This is from my example, there are two links in desktop folder which the Search.CollatorDSO provider can find (We can change the folder list from windows indexing options)

C:\Program Files\Inkscape\bin\inkscape.exe  (was ANSI)

C:\Users\fotod\OneDrive\Έγγραφα\والكليات.txt (was two parts: the ansi but with three languages English, Greek, Arabian, was impossible to read, and the UTF16LE which is ok)


OLD: works if we have English language for OS, because the ItemPathDisplay return names like folder USERS using the system language of Windows. 

Using simple function:

Print @findExe("Inkscape")


function findExe(this$)
declare local cn "ADODB.Connection"
method cn, "open", "Provider=Search.CollatorDSO;Extended Properties='Application=Windows';"
local search$={SELECT System.ItemPathDisplay
FROM SystemIndex
WHERE System.FileName = '@@@.exe'
}
method cn, "execute", replace$("@@@", this$, search$) as new rs
with rs, "eof" as new rs.EOF, "fields" as new field()
=""
if not rs.EOF then =field(0)
end function


Using function (can be member of a class too)

function findExe(this$) {
declare cn "ADODB.Connection"
method cn, "open", "Provider=Search.CollatorDSO;Extended Properties='Application=Windows';"
search$={SELECT System.ItemPathDisplay
FROM SystemIndex
WHERE System.FileName = '@@@.exe'
}
method cn, "execute", replace$("@@@", this$, search$) as rs
with rs, "eof" as rs.EOF, "fields" as field()
=""
if not rs.EOF then =field(0)
}
Print findExe("Inkscape")


This is the simple version which find inkscape exe and lnk files:

declare cn "ADODB.Connection"
method cn, "open", "Provider=Search.CollatorDSO;Extended Properties='Application=Windows';"
search$={SELECT System.ItemNameDisplay, System.ItemPathDisplay
FROM SystemIndex
WHERE System.FileName = 'Inkscape.exe'
OR (System.FileExtension = '.lnk' AND System.ItemNameDisplay LIKE '%Inkscape%')
}
method cn, "execute", search$ as rs
with rs, "eof" as rs.EOF, "fields" as field()
while not rs.EOF
Print field(0);" -> ";field(1)
method rs, "MoveNext"
end while


Κυριακή 29 Ιουνίου 2025

PRINT USING

 A function which convert BASIC Print Using to M2000 format$()


FUNCTION USING(A$) {
VAR P,B$,B1$,C$, ALL=STACK.SIZE
REPEAT
B1$=LEFTPART$(A$,"#")+"{"+P
B$+=B1$
A$=MID$(A$,LEN(B1$))
INTEGER D2=0, D1=0, I=1
WHILE MID$(A$,I,1)="#"
I++
END WHILE
D2=I
IF MID$(A$,I,1)="." THEN
D2++:I++
D1=0
WHILE MID$(A$,I,1)="#"
D1++
I++
END WHILE
D2+=D1
END IF
A$=MID$(A$,D2)
C$=LEFTPART$(A$,"#")
IF LEN(C$)=0 THEN C$=A$:A$=""
B$+=":"+D1+":-"+D2+"}"
P++
WHEN A$<>""
TRY OK {
INLINE "=FORMAT$("+QUOTE$(B$+C$)+STRING$(", NUMBER", P)+")"
}
IF ERROR OR NOT OK THEN C$=ERROR$: ERROR "Problem on parameter #"+(ALL-STACK.SIZE+1)
}
PRINT USING("A=#####.##ms B=##.##sec c=###%", 1, 1.3, 12.4)
PRINT FORMAT$("A={0:2:-8}ms B={1:2:-5}sec c={2:0:-3}%",1, 1.3, 12.4)

Τετάρτη 11 Ιουνίου 2025

Factorial up to 1000!

 We use biginteger, a type for real big numbers.





Font "Verdana"
form 80, 48
Cls , 0 ' 0 for non split display, eg 3 means we preserve the 3 top lines from scrolling/cla
Report {
Factorial Task
Definitions
               • The factorial of   0   (zero)   is defined as being   1   (unity).
               • The   Factorial Function   of a positive integer,   n,   is defined as the product of the sequence:
                                                                      n,   n-1,   n-2,   ...   1
                                                                      
}
Cls, row ' now we preserve some lines (as row number return here)
Module CheckIt {
m=1u ' 1u is biginteger
k=width-tab
For i=1 to 1000
if pos>tab then print
m*=i
Print @(0), format$("{0::-4} :", i);
Report ltrim$(str$(m)), k
' Report accumulate lines and stop at 3/4 of the screen (but not on printer)
' so we can break this using this line:
while inkey$<>"": wait 1:end while: keyboard "         "
Next i
}
Checkit
while inkey$<>"": wait 1:end while
push key$
drop

Κυριακή 8 Ιουνίου 2025

Export help entries to a doc file (simple text file)

 Loading the INFO file we find the EXPORTHELP module

Although this module written to be used with the application of m2000.exe and m2000.dll as source code, we can use it  downloading the file and we need two lines, at top in EXPORTHELP:

DB.PROVIDER "Microsoft.Jet.OLEDB.4.0"
appdir$=dir$

and a file help2000.mdb at User directory (although the appdir$ is the application directory, we just change it for the module level, to dir$ using appdir$=dir$ which make a local variable appdir$ which shadow the read only variable (which we can use at this level using @appdir$).

The DB.PROVIDER need to be 4 because by default it is 12, so we can use DB.PROVIDER as the last statement without arguments to restore to 12.

To download the help2000.mdb we have to get it from 

https://github.com/M2000Interpreter/Environment/blob/main/help2000.mdb

using your browser.

to open explorer at dir$ use this from M2000:

Win Dir$ 

To copy to clipboard the path:

Clipboard Dir$

So if all is ok you can execute EXPORTHELP and choose E for the English help or G for the Greek help.

The General.doc has 265 (Writer)/299 (Word) A4 pages and the Γενικά.doc has 225 (Writer)/255 (Word) A4 pages. The format of these files are UTF8 text.

If you don't want to make the latest, you can read these (although from M2000 we can use HELP word , or HELP ALL and then click on any word to go further):

June 8, 2025: 

Greek - Γενικά.doc

English - General.doc

Παρασκευή 6 Ιουνίου 2025

Talmudic bankruptcy problem

 I found an interesting problem from here Mathologer - YouTube 

So I decide to write a program to calculate the values based on the graphic solution provided by Mathologer. There was no algorithm in the video, so I have to invent one. After thtee fault solution I find how to "fill" like water the vessels of "claims", or pools (as objects)

I found that the objects has to be in ascending order, from smaller to higher claim, so the  "water" split can be done faultless.




cls
Print "Talmudic bankruptcy problem"
report {
The Problem:
A man dies, leaving three creditors with claims of 100, 200, and 300. The estate's value is not specified, but the Talmud provides solutions for when the estate is worth 100, 200, and 300.
The Talmudic Solutions:
Estate Value of 100: The estate is divided equally, giving each creditor 33 1/3.
Estate Value of 200: The creditors receive 50, 75, and 75.
Estate Value of 300: The creditors receive 50, 100, and 150, proportional to their claims.
}
class pool {
claim as double
lim1 as value, total as value
function level(per_cent as double) {
if per_cent<.lim1 then
w=per_cent*.total
if w>=.claim/2 then
=.claim/2
else
=w
end if
else.if per_cent>1-.lim1 then
=.claim-per_cent*.total
else
=.claim
end if
}
module fix (t) {
.total<=t
.lim1<=.claim*2/t
}
class:
module pool (.claim) {
}
}
function total {
var tot as double
while not empty
read t as *pool
tot+=t=>claim
end while
=tot
}
function process(tot, estate) {
m=array([])
many=len(m)
k=each(m)
p=estate>tot/2
if p then estate=tot-estate
while k
t=array(k)
t=>fix tot
per_cent=estate/tot/many
z=t=>level(per_cent)
estate-=z
many--
if p then
data -round(z,2)+t=>claim
else
data round(z,2)
end if
end while
=array([])
}
a->pool(100)
b->pool(200)
c->pool(300)
total=total(a,b,c)
flush
data 50, 100, 200, 250, 275, 300, 400, 450, 500, 600
Pen 15 {Print "", "       A", "      B", "      C", "    SUM"}
while not empty
read that
rep=process(total, that, a, b, c)
Pen 15 {Print "estate = ";that,}
Print rep,
Pen 15 {Print round(rep#sum(),0)}
end while
total=total(a,b)
flush
data 50, 100, 200, 250, 275, 300
Pen 15{Print "", "       A", "      B","", "    SUM"}
while not empty
read that
rep=process(total, that, a, b)
Pen 15 {Print "estate = ";that,}
Print rep,"      ....",
Pen 15 {Print round(rep#sum(),0)}
end while


Κυριακή 1 Ιουνίου 2025

Polyspiral

 





module polyspiral {
smooth off
form 60, 25
cls #220055, 0
var inc = 0, x=scale.x / 2, y=scale.y / 2
var length, angle
refresh 1000
gradient 0, #330066
pen 15 {
italic 1
pen 14 {report "M2000 Interpreter"}
italic 0
report "POLYSPIRAL ANIMATION"
}
hold
every 1000/30 {
release
inc+=0.00248
move x, y
length = TWIPSx*20
angle = inc
for ν=1 to 150
pen #FF0055
width 2 {draw angle angle, length}
length+=TWIPSx*5
angle+=inc
next
if keyPress(32) then exit
refresh 1000
}
refresh 25
}
polyspiral
pen 14