Show HN: Fe – A tiny, embeddable language implemented in ANSI C
github.com
github.com
Something I wish project docs would always include is …why… and what. Why does fe exist? Why did the orignal author spend a span of 6mo on it? What can it do and what can't it do? Don't make people read between the lines. It can be just to learn and have fun! But please don't make me read between the lines.
The overview is interesting:
* Supports numbers, symbols, strings, pairs, lambdas, macros
* Lexically scoped variables, closures
* Small memory usage within a fixed-sized memory region — no mallocs
* Simple mark and sweep garbage collector
* Easy to use C API
* Portable ANSI C — works on 32 and 64bit
* Concise — less than 800 sloc
Since I work in embedded Linux systems I always like looking at small languages, using Lua at the moment. So it struck my interest, oooh this is interesting.
The single fixed allocation is interesting, and odd. It then has gc...so why On the not just use malloc the manage the pool? I wonder if the performance is that much better?
But beyond print there's no IO library, so I guess it's a "toy" language, maybe somewhere to learn a lisp flavor?
Reply to one of the GitHub issues discusses it some[1].
>> What was the motivation to create fe if you already made aria?
> The same motivations I had writing aria: for the fun of programming it; trying to make something terse but still practical. Every line of code has a weight to it with consideration that would be unreasonable outside this kind of project: a project that exists just to exist. Each time you write something it becomes — or at least should become — more clear and more concise than the previous time, and if you repeat this, the original version is by comparison an over-engineered bloated mess. fe is a sequel to aria, and as such ended up smaller, simpler and faster
Ever look at Chicken Scheme? It uses the stack as a heap, and its functions never unwind the stack to return. Hitting the stack size limit triggers a copy garbage collection that resets the stack.
I imagine Fe has a similar design.
Allocation is super fast in the Chicken Scheme way, and GC is, well, GC, and you don't have fragmentation to worry about.
I have made a fair number of decisions against ergonomics in favor of less objectively important ideals because I don't plan to support people working on real problems. However, I do plan to make a self-hosting compiler though so I will have to deal with any issues I do make.
> that authors of such projects seem unable to resist
It definitely is very tempting to add things beyond the scope of a useful language (at a toy scale). I restricted myself from most such things by requiring strict C compatibility. You can't have a fancy type system and interop with C strongly.
ATS would disagree with this statement, strongly.
My idea of strong interop is not having to write any glue code at all and being able to call any function. I assume there are some types that don't interface with C well.
That said, thanks for mentioning it. I should keep it in mind as it does do some of the stuff I want my language to.
typedef
struct {
int atslab__0; void *atslab__1;
} int_and_string ;
very strong typing right there...I was speaking about interfacing in the opposite direction.
No need to apologize. I also should have been more clear about what I meant by strong interoperability.
> I was speaking about interfacing in the opposite direction.
I think that having really strong interoperability requires both directions, but the being called from C is the one that is more restrictive on fancy types.
It all starts to look like COM, lol. Take care man.
I couldn't see (other than reading the code) if you have an expression (foo bar quux) if foo is first evaluated before calling the result. I seem to recall Scheme follows this approach.
Alternately, the name foo could be looked up in a global symbol table and the result (presumably a lambda expression) is applied to the list of evaluated arguments (bar quux). I associate this approach with Lisp.
I didn't see any error trapping mentioned in the documentation, so I assume if you try to do something like divide by zero, that Fe will crash. How much work does it take to implement a simple error trap strategy ?
I didn't see if the language supports a fluid let (any variable name is looked up at run time when the reference is encountered thru the stack levels until the name is found. Presumably each time new variables are created with let, they would be added at run-time to the symbol table.
The other choice would be that the variables are lexically scoped. Thus when reading in the code, variables are "found" in the order the program is written on the page, and there is no searching of the run time stack frames to find the "innermost" version of the variable name.
From what I can see, Fe is a good language to use if you are happy with the capabilities the implementor defined initially. There is a set of functions, named after mathematical operators, like "+", "-", "*" etc. and logical operators like "AND" "NOT" etc. The doc says these functions take a variable number of arguments, evaluate them, and then the operator takes each of the results, and does some operation using them, such as adding them together, or returning false if any of them are false (AND)
I see nothing in the docs suggesting how a programmer would create his own functions that function in this way.
It's not clear if the logical operators "short cut" on evaluation of the arguments. ie: with (or bar quux qide) does the value of quux even get evaluated if bar is true ?
Is there a short and sweet checklist to help describe a programming language you have never seen so you know how these types of issues are addressed in the implementation?
Thanks for sharing your code. I always learn when I read other people's work.
These are called dynamic variables in Lisp. They're only really useful as a way of doing things like I/O redirection, and you can just shallow-bind them to make them fast (but then they need to be per-thread as well, and pushing a binding requires unwind-protect to pop on unwind).
> The other choice would be that the variables are lexically scoped. [...]
That's what the README says Fe does.
SRFI-15: https://srfi.schemers.org/srfi-15/srfi-15.html [1999]