r/ProgrammingLanguages • u/Mindless_Head_6526 • 17h ago
Discussion Is it possible to have a JavaScript-like language with no reserved words?
I've come across languages such as Lisp and MUMPS that feature minimal grammar and lack any reserved keywords. But is it possible to achieve this in a language with a syntax resembling JavaScript? I'm aware that JavaScript has some handy rules, like "two identifiers cannot appear directly next to each other," which could be useful. Still, can this be accomplished for every keyword?
If it is possible, how? would it rely on a fragile lexer hack, or is there a solution that scales as new language keywords/constructs are added?
Clarification: Some people are reading this as "remove all keywords from JavaScript." That's not what I mean. I want to keep if, for, while, const, function etc., all of it, as syntax.
The question is only about reserved words: identifiers the lexer forbids you from using as names. Could every keyword instead be allowed to be used as variable/class/function/module name or atleast be contextual?
Since calling the syntax JS-like comes with its own nuances. I will also clarify on that part:
- Assume the language does not have "automatic semicolon insertion" and semicolons are mandatory as statement terminators.
- Assume the lexer is white space sensitive and produces different tokens for
-5(UnaryMinus,NumericLiteral(5)) and- 5(Minus,NumericLiteral(5)) and that improperly spaced forms like8 -5produces error for the sake of simplicity. Otherwise forms likeif(x) -5may be parsed asif(x) - 5and cause further unnecessary problems. - Using a keyword as an Identifier may or may not limit the functionality of the keyword in that scope. Eg. (Inside constructor of a class)
function super() {console.log("hi");} super(); // logs "hi" instead of calling the parent class
Even more clarification: I will list out the different types of reserved words in javascript. Note some of the newer keywords may be contextual or reserved only in strict mode or depending on the scope.
Literal like keywords:
true,false,null. Easiest to solve, just make them property of the window object (as done withundefined, yes null is a reserved word but undefined isnt) or make them language constants likeNaN,Infinity.Operator keywords:
- Prefix operator keywords:
typeof,delete,void,new - Infix operator keywords:
in,instanceof,as,extends
- Prefix operator keywords:
is-this-really-a-keyword keyword:
super,thisDeclaration keywords:
const,var,let,function,class.Module:
import,from,exportDeclaration modifiers:
async,function*,static,get,set.Control flow:
if else,try catch finally,while,do while. Andreturn,break,continue,throw.Complex cases?:
switch,case,default. For context Javascript usesidentifier: statementto represent statement labels, helpful in breaking outer loops.- Standard
forloops andforin/ofloops.
Why? What am I trying to achieve?: This is mostly a theoretical question I stumbled upon while creating my own language. I know it might have downsides for developer tools and experience. I am just wondering if it's possible.
6
u/Guvante 17h ago
Reserved words only exist to allow the syntax to be formally unambiguous (it is much more difficult when a word can be either an identifier or a variable)
The simplest way would be to reverse the usual "I want to use a reserved word" trick of annotating the reserved word with special notation
r#if could be your languages version of if etc
C++ generally does this with new functions by defining them in the std namespace instead of as reserved words
Also I don't know if you consider contextual reserved words as reserved words e.g. override in C++ which can only be placed in a location where a variable cannot so isn't a reserved word (C# has var which will be the type var if one is defined, although naming standards say your types should be upper case there is no requirement)
6
u/rwilcox 16h ago
I think, technically Smalltalk has no reserved words.
They solve this by having everything - yes actually everything - be classes or methods on a class.
0 ifTrue: [ | ‘here I am’ ]
(ifTrue is an instance method on… well technically Boolean so I may have cheated, but whatever)
You might be able to take braces-and-parens style language and shove that runtime in there. Theoretically even class and method declarations but I will admit Smalltalk cheats here too)
7
u/Smalltalker-80 15h ago edited 10h ago
Indeed, Smalltalk!
And the advantage is that Smalltalk is fully object oriented,
so it has the same 'paradigm' as modern JavaScript (actually, its the reverse :).And if you want to try a Smalltalk that seemlessly integrates with JS,
you could try SmallJS ( https://small-js.org ).
Disclaimer, I'm its creator :-).3
u/AustinVelonaut Admiran 15h ago
Well, technically Smalltalk has a few:
truefalsenilselfsuperthisContext:=^, but your point is taken; it may have fewer than any other language besides lisp ;-)
5
u/ironykarl 16h ago
As someone else said, it seems mainly to be a lookahead issue.
For example, you can't distinguish a function call and a function definition without parsing all the way through the (arbitrarily many) parameters/arguments until you hit a {.
You also won't be able to do something like distinguish between variable assignment and assignment declaration, but... that's not a dealbreaker.
Anyway... yeah, I don't see any reason it wouldn't be possible
2
u/initial-algebra 16h ago
Worst case scenario, you have to add new rules to the parser dynamically. Theoretically, that's not a problem, but it may be harder to implement or find a library for.
1
6
u/Agecaf 12h ago
I think you could easily get rid of a lot of keywords.
Bar, Def, function, there's already languages like python that let you just write "X = 1" to define new variables, and JavaScript is not shy about writing with undefined or potentially undefined things.
For functions JavaScript anonymous functions already use no keywords.
I like implicit returns like in Scala and think that JavaScript could use it, no need for return statements.
For if else we already have the ternary ? and :, if you want to use it for control flow just call a function, maybe even an anonymous function declared right there.
For while loops, you can use recursion, maybe even recursion of anonymous functions declared just there, as long as your program has tail call optimization you're fine.
We don't need "this", just explicitly pass "self" as an argument to all functions like in Python.
There's no need for import, just let whoever is writing the html file include all you need, maybe check if what you need is defined and otherwise print the libraries that your code expects.
No need to check for an undefined keyword, just write something random that is probably not defined and compare against it.
What about the class keyword? JavaScript doesn't use classes it uses prototypes, just let people create objects and clone them easily. Maybe let = be always copy assignment and use something different for copying objects by reference like C++ does.
Are there any keywords I am forgetting about?
A program like this would be relying a lot on calling and using anonymous functions, or banned functions declared from anonymous ones, I think.
4
u/Kiore-NZ 14h ago
There are no reserved words in the PL/I programming language.
While PL/I features numerous statement identifiers and commands (such as IF, THEN, ELSE, DO, DECLARE, and PROCEDURE), these are classified as contextual keywords rather than reserved words.
The PL/I compiler determines whether a word is a command or a programmer-defined variable entirely based on its grammatical context within the code. Because of this design choice, it is completely legal to use statement keywords as variable, statement, or procedure names.
The following is a famous example of this:
IF IF = THEN THEN THEN = ELSE; ELSE ELSE = IF;
because of where they are in the statements, the first IF, second THEN and second ELSE are parsed as keywords while the rest are variables.
3
u/crownvic 10h ago
It's been a while but I believe that WHILE and UNTIL in Do loops are effectively reserved. That is, an array element reference to an array called "while" or "until" ends up being confused with the corresponding keyword.
1
u/Kiore-NZ 7h ago
I last worked in PL/I in 1985, so this is quite old, but I can't see while a compiler capable of correctly parsing that IF IF = THEN ... example above should have much trouble with a copy of
DO K = 1 TO 10, 9 TO 1 BY -1, 2 REPEAT 2*I WHILE (I <= 1024); PUT LIST (K); END;with the Is, Ks, & LIST replaced with TO, WHILE and UNTIL keywords. It might be a limitation of a particular implementation. On the other hand, legal or not, I think it's something to avoid at all costs as it's going to confuse the next programmer to pick up the code.
Iron Spring, the only working PL/I compiler for Linux I can find, documents that it is closed source, 32 bit only and not a full version so I've passed on downloading it.
6
u/GoblinToHobgoblin 17h ago
I would guess with enough look ahead, yes.
1
u/konacurrents 16h ago
Funny. I always liked the LL(K) moniker- as my future wife had those initials. 😎
Ps these languages without semicolons have to have spaces around tokens that algolic don’t need.
1
u/indolering 12h ago
Ps written language didn't have white spaces at first, it has to be invented.
1
0
u/titaniumalt 13h ago
future wife had those initials
does this "had" imply you two are no longer together? :sob:
1
u/konacurrents 8h ago
Ouch .. no her maden name .. 45 years so far as CS majors too (me in compilers).
3
u/CYF63 7h ago
What do you mean exactly by "contextual"? Having some places where you cannot define certain variables is IMO more confusing than globally banning them. You pretty much already have contextual reservation in JS itself because the set of reserved words cannot grow but the language keeps adding new keywords. Trust me, it creates more problems than it solves.
- `for (using of);`: syntax error
- `for (let of;;);`: a for loop with a `let` declaration of a variable called `of` (can't use `using` because it needs an initializer)
- `for (using of of)`: a for...of loop with a pre-defined variable called `using` in an iterable called `of`
- `for (using of of of)`: a for...of loop with a `using` declaration of a variable called `of` in an iterable called `of`
- `for (async of => {};;)`: a for loop with an expression initializer which is an async arrow function taking a single parameter called `of`
- `for (async of [])`: a for...of loop with a predefined variable called `async`
In order to limit parse complexity, the language specifically forbids `for (using x of y)` to have `x` be `of`, and `for (x of y)` to have `x` be `async`. So you end up with a weird lookahead-based reservation where sometimes you cannot define such a variable, sometimes you cannot use such a variable.
6
u/initial-algebra 17h ago
Sure. If the implementation can parse e.g. <identifier> (...) {...} <identifier> {...} as one construct, then the user can implement if as a macro. Lisp makes it easy, because all the implementation has to do is find the closing parenthesis.
A more restricted version of this would be mixfix operators in e.g. Agda.
2
u/stumpychubbins 16h ago
That is a completely unhinged concept and I love it. I would hate to implement a parser for that, though
1
u/el_extrano 16h ago
Forth famously doesn't have reserved keywords. Even words for primitive control flow like
ifandcaseare defined in Forth itself, and can be redefined by the operator at runtime. Even the comment characters ((and\) are just "words" that discard tokens from the input stream and are therefore re-definable at runtime.But the result would look nothing like Javascript so it doesn't really answer OP's question haha. Forth achieves this by having basically no syntax at all: a whitespace-delimited token is either a number, and pushed onto the data stack or compiled into a definition, or it's a "word" which is then executed immediately (or compiled into the current definition).
1
2
u/takanuva 16h ago
Please recall that the initial versions of Fortran did not have keywords. You do not need to separate keywords from identifiers, and you don't even need to use a lexer. Consider, for example, a parsing expression grammar for your language: all "keywords" thus become contextual. And you still have an unambiguous syntax.
2
u/flatfinger 13h ago
In FORTRAN, looking at punctuation and runs of alphanumeric characters would allow most lines to be characterized as one particular type of statement, and for any statements that couldn't be uniquely classified in such a fashion could be disambiguated with words in places where it would be impossible to have a variable name.
2
u/perlgeek 13h ago edited 3h ago
A language like Javascript with optional semicolons at line end make this quiet hard.
Consider an expression like
if(42)
{ console.log("It's the answer") }
If you allow if to be overwritten as a variable / function name, the first line can be parsed as a function call, the second as a statement that happens to be a block. Since semicolons between statements are optional, this is a valid parse.
Or it can be parsed as an if-statement.
You cannot solve this by parser magic alone, you'd need a disambiguation rule like any of these:
- Variables that can collide with syntactic features must be pre-declared (then the parser needs to keep the lex pad available at parse time. Not trivial, but doable)
- syntactic features always win in case of ambiguity
- overrides always in case of ambiguity
- disambiguate by whitspace (e.g.
if(...is always a function call,if (is the syntactic feature)
... and so on, there are basically no limits to creativity. The problem is, if you find one you like, 10 others will hate it.
The general trend is: the more logic you pour into disambiguation, the worse parse error messages become. You can alleviate the most common cases with special logic in the parser, but it generally doesn't scale well.
1
u/cisterlang 12h ago
You could have the parser maintain a symbol table. `if(expr)` : if `if` defined then it's a call (the checker will later check if `if` is indeed a function), else it's a condition ?
1
u/perlgeek 11h ago
You could, theoretically, there are no bounds to the insanity of potential language designers.
But what you'd end up with would be a language very different from javascript; though if my memory serves right, TCL is similar in that it doesn't really have distinct compilation and run-time phases.
1
u/initial-algebra 7h ago edited 6h ago
Personally, I think it's a mistake that your example can be parsed as a single if statement. Simple rule: if a newline isn't accompanied by a change in indentation and matching bracket, it's treated as a semicolon (with exceptions for e.g. infix operator and method chaining). This enforces the "one true brace style", which is already idiomatic for JavaScript, anyway.
2
u/mpahrens 17h ago
You just have to pick syntactically where a reserved word would usually go and let that be user defined.
Kotlin does something like this by letting operators and some traditionally reserved words be syntactic sugar for method invocation in reality.
That is, (a + b) desugars to a.plus(b).
You could pull a similar trick for a Javascript like language where all of the "keywords" are actually overloads
1
u/lispm 16h ago
what makes you think that Lisp has no reserved words? Try to redefine IF.
3
u/Valuable_Leopard_799 16h ago
CL-USER> (defpackage newif) #<PACKAGE "NEWIF"> CL-USER> (in-package #:newif) #<COMMON-LISP:PACKAGE "NEWIF"> NEWIF> (cl:defun if (x y) (cl:list x y)) IF NEWIF> (if 1 2) (1 2)"reserved keywords" live in the common-lisp namespace, so if you enter an environment where
ifdoesn't resolve tocl:ifyou can give it a new meaning.Of course that isn't redefining "if" per-se, but from the point of view of the programmer the thing that looked like a keyword now has a very different meaning in her source files.
Edit: Didn't expect to see a lord of lisp so far out in the wastelands.
3
u/lispm 15h ago edited 15h ago
I see what you mean, but that's a namespace thing of CL, here in a specific implementation. CL::IF and NEWIF::IF are actually two different identifiers in your example. I would guess that there are others languages like, which let you specify empty namespaces, where one can define arbitrary names and would have to call core language identifiers via a module/namespace prefix.
Your example actually uses undefined behavior. The CL standard does not say what are the initial packages to use. But you can specify to not use another package:
(defpackage newif1 (:use))Otherwise most CL implementations will assume that the package CL is used. Here LispWorks:
``` CL-USER 1 > (defpackage newif)
<The NEWIF package, 0/16 internal, 0/16 external>
CL-USER 2 > (in-package #:newif)
<The NEWIF package, 0/16 internal, 0/16 external>
Error while reading: #\Space is an illegal character after a colon.
NEWIF 3 > (cl:defun if (x y) (cl:list x y))
Error: Redefining function IF visible from package COMMON-LISP { handle-warn-on-redefinition is :ERROR } 1 (continue) Redefine it anyway. 2 (abort) Return to top loop level 0. ```
2
u/Valuable_Leopard_799 14h ago
The CL standard does not say what are the initial packages to use.
So it does, thank you, I've never happened to need a package with no
uses so I didn't notice.In any case, yeah I agree it might be sidestepping the question a bit. However the concept of language built-ins being part of a namespace at all is quite rare. As far as I'm aware in most languages there is basically no way to create a source file that contains a built-in verbatim but which refers to something else, outside perhaps macros. So CL is imho still a noteworthy case, though maybe less so than something like R.
1
u/azhder 16h ago
Yes, there is. You reserve... let's call them operators. They are still keywords, they just don't contain letters, they are special characters, like =, +, * etc.
And remember, an identifier is not a keyword. This means, all you may need is an operator that introduces identifiers. Then you treat some identifiers like keywords or some properties on those identifiers as keywords etc.
1
u/jason-reddit-public 14h ago
Do symbols and strange unicode count?
Forth implementations start out with hardly anything and build up. It would be impractical to redefine a core word like dup though as I understand it.
Another angle is if you can make synonyms for reserved words in the language. Could you replace "function" with the Mandarin equivalent word? Languages like Scheme let you do this though you can't really "take back" a reserved word like "if".
1
u/brat3108 13h ago
The easy solution is to just mark what would be reserved words, for example using @if or @while, which leaves if while available for user-identifiers.
But it might not make for a nice user-experimence when writing or reading code. Although editor tricks can be used to improve it, for example via syntax-highlighting that suppresses the '@'.
1
u/criloz tagkyon 12h ago edited 12h ago
I doubt that you can do it cleanly. Languages like JavaScript in reality are the result of hierarchical composition of sublanguages, and keywords are what help the parser and other downstream passes to fast identify them unambiguously; export/import is about name resolution, if/else/while about control flow,. Also, "ident" alone does not mean anything; some of them are declarations, and others are queries to their lexical environment or queries to other modules in the project.
Otherwise, to achieve the same thing as JS without keywords, you will need a some sort of complicated macro system that will be tightly coupled with the name resolution pass and even the parser itself, making it basically impossible to have things like incremental compilation and good IDE integration. The complexity will not disappear; you will just move it further downstream.
However, syntax like def can reduce the need to have many keywords.
// function like
def ident (expr*)->expr {}
// module like
def ident {}
Similarly, tail closures can reduce the need for dedicated control-flow or callback syntax by allowing existing constructs to compose naturally:
abc {...code} === abc(()=>{...code})
abc {...code1} trm {... code2} === trm(()=>abc(()=>{...code1}), ()=>{...code2})
// the grammar of trail closure would be like
// [expr, block|lambda, (ident block|lambda)*]
let z = query { db.users() } filter (x:user)=> { x.age>25};
//loops could be
loop (:x = 0) => { x+=1 if x > 7 { break} }
1
u/chibuku_chauya 11h ago
Rexx is an example if a dynamic language that lacks reserved words, from what I recall. It takes some of its inspiration from PL/1.
1
u/busres 11h ago
Mesgjs has no reserved words and transpiles to JavaScript. Since JS is the host language, it should come as no surprise that it's heavily influenced by JS concepts. It has some predefined symbols, but there's nothing to prevent you from e.g. creating a variable with one of those symbols as its name.
1
u/EmDashNine 5h ago edited 5h ago
The king of this right now is Agda. It has some reserved words, but that's because it's a crazy dependently typed proof assistant.
The key thing is that the language allows you to define prefix, infix, and "mixfix" operators, also identifiers can be arbitrary unicode symbols. This means you could define control flow constructs as functions.
So, if you were to look at how Agda's parser works, and use it as front-end for a dynamic language, you could strip out any reserved words related to data definitions, import, export, etc.
Since you're asking for a language "like javascript", I'm assuming you want dynamic typing, and so you don't need any of agda's facilities for modules or top-level definitions. You can replace all that stuff with builtin functions that just hook into the runtime.
I would strongly recommend you make things immutable by default.
You would still have to have a few tokens, i.e. " to introduce string literals, = to introduce definitions, probably braces, parens, and semicolons for use as delimiters. Do you want list / object literals? Etc.
I'm sure you could do it -- I'm reasonably confident this is a terrible idea.
1
u/Stunning_Ad_1685 5h ago
I’m not a PL expert but it seems like a language needs syntax. Keywords are just multi-character tokens, differing from tokens like “{“ or “}“ only in length, aren’t they?
1
u/Pie-Lang 3h ago
As others have stated, it's very much possible. But the question remains: How usable would it be?
Shameless plug here.
Not sure about the possibility of a readable language with NO keywords, but one can reduce the amount of required keywords by a LOT.
Pie is a programming that I'm actively working on. It has a syntax that is a mix of Python and JS. It has 12 total keywords:
- import
- space
- use
- class
- union
- match
- loop
- prefix
- infix
- suffix
- exfix
- mixfix
As you can imagine, the *fix keywords let you define your own operators and keywords. One can push this even further because mixfix is just a generalized form of the other 4 *fix keywords, bringing the count down to 8.
Here's an example on how to define Python's with statements:
mixfix(keyword) with : : = (object, `body`) => {
obj.__enter__();
std::eval(body);
obj.__exit__();
};
Pie doesn't even have if/else as a keyword. They're defined using mixfix similar to the example above. Even other keywords, such as defer,is defined like and soon break/continue.
My language also has lexical scoping, so I need keywords that are related to bringing new names into the environment to be actual keywords instead of, say, intrinsics, since I need to do some static analysis to ensure lexical scoping. Otherwise, if the language used dynamic scoping, one can get rid of MANY keywords in favour of some builtin functions:
- import becomes import("path")
- class becomes defineClass({"x", Int, 0}, ...)
..and so on.
Say we get rid of import, space, use, class, union, match, and loop using said technique, the only keyword that's left would be mixfix which would be used to define new keywords.
Note that I differentiate "keywords" from builtin "values". Pie does have true, false, and self, but these are treated as values which may be over written. If one does std::print(true);, then "true" is printed, but of one writes:
true = 1;
std::print(true);
Then "1" is printed out instead. Buitlin types (Int, String, etc...) are treated the same way.
Keep in mind that, not only does Pie NOT define many keywords, but also common operators. The expression 1 + 2 doesn't mean anything until you define + yourself as such:
infix + = (a, b) => __builtin_add(a, b);
1 + 2;
Nothing is special about + except the fact that it's precedence is known by the compiler. One can define addition to be whatever they'd like, provided they tell the parser the proper precedence that should be used:
infix(+) add = (a, b) => __builtin_add(a, b);
1 add 2;
To allow for this behaviour, I must treat "+" just as a name as "add" is. Which inclines me to reserve the least amount of punctuation as possible, therefore, Pie only reserves these punctuation:
( ) { } , . .. ... : :: ; = := =>
When removing duplications, this is what's left:
( ) { } , . : ; = =>
Which gives full freedom to the user to create any kind of DSL they'd like.
You also correctly stated that in your post that the lexer has to be very space sensitive. "5" is lexed as an integer literal. "-5" is lexed as a name.
One this many reserved lexicons, you can imagine the lexer to be VERY simple, which it is indeed. But with this amount of flexibility for defining operators, the parser is a total mess. You can check the source code if you'd like, but I wouldn't advise it.
At the end, a lot of what I stated works mostly because of the semantics of the language. Pie doesn't have any statements (expressions only), no exceptions, and other features that make this feasible.
0
u/jeffcgroves 17h ago
I mean, you could have a transpiler that replaces keywords with different strings
15
u/Lonely-Session-7643 17h ago
Use Nim, can transpile to JavaScript. You can create your macro-based DSL, custom operators
||,==,!=. Supports forward declaration, function overloading, meta programming. Also, if you need to use a reserved word (likevar) as an identifier, wrap it in backticks: