How can I simulate macros in JavaScript?
Asked Answered
R

8

31

I know that JavaScript doesn't support macros (Lisp-style ones) but I was wondering if anyone had a solution to maybe simulate macros? I Googled it, and one of the solutions suggested using eval(), but as he said, would be quite costly.

They don't really have to be very fancy. I just want to do simple stuff with them. And it shouldn't make debugging significantly harder :)

Recrystallize answered 11/10, 2008 at 1:15 Comment(0)
A
29

You could use parenscript. That'll give you macros for Javascript.

Acrobat answered 11/10, 2008 at 16:57 Comment(4)
-1 While parenscript was ok in 2008 you should really be using ClojureScript now.Burghley
Different solutions... Parenscript is a rather thin layer, unlike ClojureScript.Appendix
Not just different solutions, different languages tooGoatskin
@CesarCanassa parenscript is a Common Lisp library which works with most (maybe all) major implementations (I've tried it myself). Clojurescript is for a more niche market, and suitable for different audience.Icky
O
28

A library by Mozilla (called SweetJS) is designed to simulate macros in JavaScript. For example, you can use SweetJS to replace the function keyword with def.

Oxidate answered 5/1, 2013 at 1:43 Comment(1)
SweetJS hasn't been updated since 2017. Do you know what's the context of SweetJS in 2022 ? Beside mozilla, I haven't heard/seen other people/company using macros in JS. Do you have personal experience with this ? ThanksAnaphylaxis
R
12

One can also now use ClojureScript to compile clojure to javascript and get macros that way. Note ClojureScript uses Google Closure.

Recrystallize answered 21/7, 2011 at 12:28 Comment(0)
S
8

I've written a gameboy emulator in javascript and I simulate macros for cpu emulation this way:

macro code (the function returns a string with the macro code):

function CPU_CP_A(R,C) { // this function simulates the CP instruction, 
  return ''+             // sets CPU flags and stores in CCC the number
  'FZ=(RA=='+R+');'+     // of cpu cycles needed
  'FN=1;'+
  'FC=RA<'+R+';'+
  'FH=(RA&0x0F)<('+R+'&0x0F);'+
  'ICC='+C+';';
}

Using the "macro", so the code is generated "on the fly" and we don't need to make function calls to it or write lots of repeated code for each istruction...

OP[0xB8]=new Function(CPU_CP_A('RB',4)); // CP B
OP[0xB9]=new Function(CPU_CP_A('RC',4)); // CP C
OP[0xBA]=new Function(CPU_CP_A('RD',4)); // CP D
OP[0xBB]=new Function(CPU_CP_A('RE',4)); // CP E
OP[0xBC]=new Function('T1=HL>>8;'+CPU_CP_A('T1',4)); // CP H
OP[0xBD]=new Function('T1=HL&0xFF;'+CPU_CP_A('T1',4)); // CP L
OP[0xBE]=new Function('T1=MEM[HL];'+CPU_CP_A('T1',8)); // CP (HL)
OP[0xBF]=new Function(CPU_CP_A('RA',4)); // CP A

Now we can execute emulated code like this:

OP[MEM[PC]](); // MEM is an array of bytes and PC the program counter

Hope it helps...

Shibboleth answered 7/9, 2009 at 19:27 Comment(3)
new Function(string) is an eval() context, and as such, has the same performance characteristics of eval()Holocene
I do not think that is true. It may be an eval, but the eval only occurs once. Once it is a function, it should execute any number of times at normal function speed.Gardenia
IIRC, it used to be the case that Function objects were not optimized by most JS engines, unlike "plain" JavaScript code. However, it was years ago when I read this, so I wonder how well the average JS engine supports them now. Especially with all the effort going into closure optimization.Estop
E
6
function unless(condition,body) {
    return 'if(! '+condition.toSource()+'() ) {' + body.toSource()+'(); }';
}


eval(unless( function() {
    return false;
  }, function() {
    alert("OK");
}));
Ens answered 26/10, 2009 at 10:53 Comment(5)
Not a bad idea, but sadly the solution adds a eval and 2x function definitions too much. +1 for trying though.Recrystallize
Macroses are expanded at COMPILE time, so we need to add compilation stage to JavaScript or forget about macroses. We can compile JavaScript by call to eval() function only, so we need eval() anyway.Ens
macro are syntaxic sugar for more consise and expressive code. They do not increase theorically the type of thing you can do or not. They are expended one time at compile time, making the runtime cost to zero. Example here fail: eval will be called each time and the code is more verbose and less lisible than if one directly wrote the equivalent javascript. For the macros to be usefull you should be able to use it with a syntax like unless('false','alert("OK")');Didi
@NicolasBousquet Macros give you the ability to manipulate code before it gets executed (and before it gets compiled, macro expansion time). You cannot do that without macros.Icky
In the end, the code generated by macros is as powerful as the code written by hand, but any turing-complete language would give you that. What matters is that they increase what you can do.Icky
J
4

LispyScript is the latest language that compiles to Javascript, that supports macros. It has a Lisp like tree syntax, but also maintains the same Javascript semantics. Disclaimer: I am the author of LispyScript.

Jessiejessika answered 6/8, 2012 at 10:5 Comment(5)
Is LispyScript a homoiconic language (like Scheme and Common Lisp)?Oxidate
Is LispyScript related to ParenScript in any way? They look very similar to me, since they are both Lisp dialects that compile to JavaScript. common-lisp.net/project/parenscriptOxidate
@AndersonGreen I thought that Santosh's claim is that LispyScript is lisp like but not a lisp dialect.Biblical
No longer maintained?Aerobiosis
LispyScript is no longer maintained and should not be used.Impeditive
G
0

Check out the Linux/Unix/GNU M4 processor. It is a generic and powerful macro processor for any language. It is especially oriented towards Algol-style languages of which JavaScript is a member.

Gardenia answered 9/5, 2020 at 18:53 Comment(0)
P
-1

Javascript is interpreted. Eval isn't any more costly that anything else in Javascript.

Panga answered 11/10, 2008 at 1:50 Comment(5)
Wrong! James, please read up on this question:#87013 and validate your opinions before making misleading statements. The string passed to an eval must be parsed/interpreted every time the eval is called!Venessavenetia
Yes, the string passed to eval must parsed every time eval is called -- but so must every other line of javascript. that's how interpreters works. As for the answer you linked to, he never mentions speed, just "much easier to read as well as less potentially buggy"Panga
I'll grant that this possibly may have been the case with Javascript in browsers say around 2000, but today there are serious optimisations being applied to plain Javascript code (ie non-eval'd) and this will only continue. Code in a string in any language cannot be optimised anywhere near as well.Venessavenetia
Ash, the point James is trying to make, I believe, is that for a one time deal, eval works as quickly as literal JavaScript because either way the interpreter is doing the same thing - taking a string and executing it. What do you think literal JavaScript is? It is a string in an HTML file.Rotatory
Measured loop with million iterations: Regular loop: 4.099999904632568 milliseconds Loop entirely within eval: 18.300000190734863 milliseconds Loop with million evals: 115 millisecondsYakut

© 2022 - 2024 — McMap. All rights reserved.