Skip to content

Actual interpreter for microcontroller? #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dragoncoder047 opened this issue Oct 18, 2022 · 17 comments
Open

Actual interpreter for microcontroller? #18

dragoncoder047 opened this issue Oct 18, 2022 · 17 comments

Comments

@dragoncoder047
Copy link
Contributor

From the readme:

CognaC (the Cognate Compiler) compiles Cognate sources directly into C. This produces very small and rather fast binaries, allowing Cognate to outperform most dynamic languages. This also makes Cognate a candidate for scripting in embedded environments, such as microcontrollers.

... but microcontrollers can't compile and run C. Is there a "pure interpreter" for a microcontroller somewhere that actually executes the code instead of transpiling it to C? If there isn't, how do I make one out of this code here?

@StavromulaBeta
Copy link
Collaborator

The intention is for code to be compiled for the microcontroller from C - that way small microcontrollers can be used without the overhead of an interpreter. Maybe the word scripting is misleading here(?). I'm planning to explore actually running Cognate on a microcontroller once I've got the new compiler feature complete and stable.

@dragoncoder047
Copy link
Contributor Author

dragoncoder047 commented Oct 18, 2022

I was worried of that. What I want to do is hook up the microcontroller directly to a storage device (flash drive, SD card, etc), have it read the .cog files and execute them onboard, without having to be connected to a regular computer to do the compiling. I'm going to be using an ESP32, which has megabytes of both RAM and flash, so memory consumption and speed are not concerns at least for me.

@StavromulaBeta
Copy link
Collaborator

Ah yeah that'd be a problem with the current implementation. I'll almost certainly ship a small interpreter with the new compiler once it's done, but CognaC in its current iteration won't be much help here, sorry!

@dragoncoder047
Copy link
Contributor Author

Hey Finn -- don't know if you're interested, but I have been starting work on a "pure" interpreter for Cognate that runs the code without having to transpile it to C first. I just put it on GitHub: https://github.com/dragoncoder047/cogni

In its present state it isn't able to run anything, but it is able to parse the prelude without errors. Hopefully I'll be able to get it to working status soon!

@StavromulaBeta
Copy link
Collaborator

Wow, that looks incredibly cool! The source looks a fair bit neater than CognaC's too lol. Please keep me updated :)))

@dragoncoder047
Copy link
Contributor Author

Please keep me updated :)))

It can now do this:

$ ./cogni -c "Print \"Hello, World\""
Hello, World

Slowly getting there...

@StavromulaBeta
Copy link
Collaborator

Nice!

@dragoncoder047
Copy link
Contributor Author

What did you intend for Begin to do? Is it just a general purpose nameable break statement? The way I implemented Begin in cogni is as a full blown call/cc where the continuations that Begin makes never expire even after the block returns -

$ cat loopy.cog

Let K be Box 0;
Begin (Set K);
Print "Looping!";
Do Unbox K;

$ cognac loopy.cog >/dev/null && ./loopy
Looping!


    Cannot resume expired continuation

$ ./cogni loopy.cog
Looping!
Looping!
Looping!
Looping!
Looping!
Looping!
Looping!
Looping!
Looping!
Looping!
Looping!
^C

Also, why is the function that handles invoking unexpired continuations called oh_no ?

@StavromulaBeta
Copy link
Collaborator

StavromulaBeta commented Jan 29, 2025

Call/cc is the ideal and what I was originally going to implement for Begin but I never got around to it (mostly because doing it performantly will be a pain), so what you've done is correct.

@StavromulaBeta
Copy link
Collaborator

Call/cc is the ideal and what I was originally going to implement for Begin but I never got around to it, so what you've done is correct. IIRC I called the function oh_no because I was thinking about using it for error handling when I first implemented it, obviously not the only use.

@dragoncoder047
Copy link
Contributor Author

Regarding tables / hash maps: It doesn't look like tables (or lists for that matter) are mutable, because both Push and Insert essentially return a new list or tree that only shares structure with the original to save memory. Is that the case why tables and lists are both considered hashable? I kind of thought of tables and lists as akin to Python's dict and list classes, which are mutable and unhashable, but are they closer to frozendict and tuple in terms of function?

@StavromulaBeta
Copy link
Collaborator

They're closer to tuple yeah. The only mutable structure in Cognate is Box by design.

@dragoncoder047
Copy link
Contributor Author

A monumental update: Cogni now passes all of the tests! (Or, all of the ones that don't depend on how something is printed or ordered.) Including the regex and IO functions!

It's still pretty slow, but I'm working to make it faster. Currently, the environment is using an assoc list instead of a table, so it's O(n) for lookups because an assoc list is effectively a BST that's always completely unbalanced. I tried swapping out the environment for tables (in another branch, not main), but now this happens:

cognate> Let X 1
    ...> 
Stack empty
cognate> Def Drop (Let X)
    ...> 
Stack empty
cognate> Filter
    ...> 
ERROR: undefined: Filter
Stack empty

I suspect a hash collision or something... will have to investigate further.

@StavromulaBeta
Copy link
Collaborator

StavromulaBeta commented Feb 4, 2025

Oh nice! I've just tested it on my machine and it works too (although cogni.c needed wctype.h). Tables definitely should be most suitable for implementing lookups so I agree that's the best way to go. What with all the allocations, another good optimisation might be a garbage collector that doesn't use malloc/free under the hood. Cognac's backend uses a generational copying gc which copes well with lots of small allocs.

Would you like me to add a link to Cogni on the Cognate website?

@dragoncoder047
Copy link
Contributor Author

What with all the allocations, another good optimisation might be a garbage collector that doesn't use malloc/free under the hood.

I considered that, but the GC I made serves two purposes: 1) it only uses malloc/free, so it can (in theory) run on microcontrollers like the esp32 that don't have mmap or sbrk, but do have ps_malloc to allocate a chunk on an external PSRAM chip - that way I can just redefine cog_malloc from malloc to ps_malloc and it "just works". 2) it's a pure mark-and-sweep, not a moving/copying GC, so once you have a pointer you can tell the GC to never collect it (by calling cog_make_immortal() on it) and then even if the pointer is stored someplace weird that maybe your GC can't see, the pointer will still be valid forever, or at least until you call cog_quit() that deallocates everything.

Would you like me to add a link to Cogni on the Cognate website?

Of course!

@StavromulaBeta
Copy link
Collaborator

Ohh those are some very good points about the GC. I'll add the link on the website now.

@StavromulaBeta
Copy link
Collaborator

StavromulaBeta commented Feb 4, 2025

I've added the link to the website and the github readme :)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants