uLisp as a Library + Outside of the Arduino IDE (PlatformIO, etc)


#1

Hi @technoblogy,
I’m really fond of uLisp, and I’ve used it in my business for several clients now. (Not in the end product, but it has proved to be extremely useful to me during development.)

Each time, I go though a somewhat tedious process of separating out uLisp into .hpp and .cpp files, so that:
1). It compiles outside of the Arduino build system (Arduino automatically adds function/variable declarations when needed).
2). It can be used as a library.

I’d like to upstream these changes so that:
1). I don’t have to do it with each new version/platform I work with.
2). Others can benefit as well. :)

Is this something you would be interested in? I presume it would be done inside of uLisp builder, ideally, so that it’s easier to maintain. I also would likely need some assistance for the best way to go about it.


#2

Good to hear that you’re finding uLisp useful!

It does sound as if the uLisp Builder would be the best way to do this. Each time I build a new version of uLisp I could also build one in the “library” format that you need.

Can you describe a checklist for what would need to be done to make the version you require?

Alternatively I’m happy to give assistance to enable you to do it, although I appreciate that the uLisp Builder isn’t very easy to use as I didn’t really expect anyone else to want to use it.


#3

Yes, it’s been delightful. :D Thank you so much for making it!

what’s strictly needed may be a bit less than what I actually do, but here is the process as well as I remember it:

  • Rename ulisp.ino to ulisp.cpp.
  • Run makeheaders on it to do most of the heavy lifting.
  • Trim out all the inline definition bodies out of the header. (This is what I have done, and seems to work, but I think the proper solution is to actually remove the entire definition from ulisp.cpp instead.)

_

  • Cut up to // global variables from ulisp.cpp into the top of ulisp.hpp.
  • (There may have been another step here to remove anything makeheaders generated up to the // global variables point).
  • manually extern this bit: (this might actually be done correctly by makeheaders, but I probably deleted it at first when I was trying to make it compile by trial and error. Only to later add it back in when I got to the point of wanting to use uLisp as a library instead of the main program)
extern uint8_t FLAG __attribute__ ((section (".noinit")));

extern object Workspace[WORKSPACESIZE] OBJECTALIGNED;

extern jmp_buf exception;
extern unsigned int Freespace;
extern object* Freelist;
extern unsigned int I2Ccount;
extern unsigned int TraceFn[TRACEMAX];
extern unsigned int TraceDepth[TRACEMAX];
extern builtin_t Context;

extern object* GlobalEnv;
extern object* GCStack;
extern object* GlobalString;
extern object* GlobalStringTail;
extern int GlobalStringIndex;
extern uint8_t PrintCount;
extern uint8_t BreakLevel;
extern char LastChar;
extern char LastPrint;
extern uint16_t RandomSeed;

I think that is the bulk of it, although there may be some steps I’m forgetting. When I wrap up my current project I can repeat the conversion fresh and get you a better list.


#4

Makeheaders is the bit that is probably overkill. It is convenient, but it also put’s everything into the header. In theory it might make sense to trim out private vars to ease potential namespace pollution.

On the other hand, cpp has namespaces, it may be possible to wrap everything in a namespace at the header level.


#5

Oh, another simple thing: #include "Arduino.h" needs to be added to the top of both files, and #include "ulisp.hpp" to the top of ulisp.cpp.


#6

I took a quick look at the source for ulisp-builder, it doesn’t look too hard to follow. :)


#7

It seems a pity to have to rely on makeheaders – otherwise the whole process could be done by the uLisp Builder.

I took a quick look at the source for ulisp-builder, it doesn’t look too hard to follow. :)

If you know Lisp it shouldn’t be hard. My problem is that I don’t really understand the requirements of C++ libraries. Feel free to ask if you get stuck.


#8

Some alternatives:

  • Parse the cpp via treesitter. CL bindings here: https://github.com/death/cl-tree-sitter
    This would be robust and hassel-free long-term, and the result would be extremely useful in general as a standalone hpp generator. (makeheaders is old and pretty limited).

  • Make a “good-enough” finicky parser via regex matching. There probably would be a lot of unexpected gotchas with this one.

  • Define the ulisp cpp code in smaller chunks in the ulisp builder, so as to be able to manually delineate between declaration and implementation.

Other notes: (subject to fuzzy recollection/understanding)

  • .hpp can have everything moved from the .cpp that is a macro, a constant, or a declaration.
  • every function can be declared in the header and implemented in the .cpp.
  • same for every variable, but must be prefixed with extern.

#9

But is that meant it still depends upon Arduino ?


#10

@Ngcchk As is, yes, it still depends on Arduino framework. (But not the Arduino IDE). If you are looking to use uLisp without it, I don’t think it would be too hard to trim out the arduino specific bits.

I think @hyj0 has done this for the ESP platform for the purpose of running uLisp on the desktop for easier debugging of non platform-specific code. See https://github.com/hyj0/ulisp-esp/tree/hyj.


#11

See also[What would you like to see in uLisp in 2023?](this comment) and the uLisp reference for which fns are Arduino specific.