Where it shines
FreezeFlame shines in readibility while still maintaining most of the C-like syntax. It also implements a few (Planning on several) operators that are still in testing for other languages.
Reason / TL;DR
I decided to write a new language, partially for the challenge and for the following reasons:
- I wanted to implement the pipe operator:
|>
, the lambda operator:::
and the hash operator:#
. - JavaScript comments were rather jagged, so I replaced
/*
with@@@
- Accessing prototypes was a pain to type if you used it commonly, so
.prototype.
got replaced with::
..prototype.
will still work. I named it FreezeFlame because it implements two languages and styles.
Explanation
The language takes after both JavaScript and CoffeeScript in its syntax. A few other things have also been added. The following examples are before and after:
Live demo
Go to https://nolanlawson.github.io/jison-debugger/ and put the below grammar in the grammar section. Then, put in any of the tests. I chose that tester over https://zaa.ch/jison/try/ because it showed me the node output.
Array
Before:
[1,2,3]
After:
[1,2,3]
Switch
Before:
switch day {
when 'tuesday' then {
'tuesday' |> console.log;
1;
}
else {
return 2;
}
}
After:
switch(day) {
case 'tuesday':
console.log('tuesday');
return 1;
default:
return 2;
}
Object
Objects remain untouched from JS syntax.
String
Strings can be both multi line and single line. Before:
"""
#{ false }
"""
After:
`
${ false }
`
Before:
" #{ true } "
After:
${ true }
Pipe
Increase readability for a nested function calls. Before:
a |> a |> #a
After:
this.a(a(a));
Function definition and call
Before:
() ~> {
console.log(1)
}
After:
function() {
return console.log(1);
};
Lambda
Access the prototype quickly. Before:
() -> {
return String::split;
}
After:
()=> {
return String.prototype.split;
};
#
#
is equivalent to this
or this.
Before:
() -> {
#;
}
After:
()=> {
return this;
};
Any requests can be put in comments. The following are proposed features:
<=>
: Spaceship operator. Alias:⇔
↔
: Alias for the swap operator→
: Alias for the function operator (Bound).↝
: Alias for the function operator (Un-bound).
The following have been implemented:
<->
: Swap operator.
The code (grammar.jison):
%lex
%%
'@@@'(\s*(?:(?!\@){3,}.)*\s*)'@@@'
{
return 'COMMENT2'
}
'@'([^\n]*)
{
return 'COMMENT1'
}
'"""'(\s*(?:[^\\]|(\\.)|(?!\'){3,})*\s*)'"""'
{
s = this.matches[1];
yytext = s
return 'STRING2'
}
"'''"(\s*(?:[^\\]|(\\.)|(?!\'){3,})*\s*)"'''"
{
s = this.matches[1];
yytext = s
return 'STRING2'
}
'"'((\\.)|(\\\n)|[^\"\n])*'"'
{
s = this.matches[1];
yytext = s;
return 'STRING1';
}
"'"((\\.)|(\\\n)|[^\'\n]*)*"'"
{
s = this.matches[1];
yytext = s;
return 'STRING1'
}
\n+ return 'NL'
([^\S\n]{3})+ return 'INDENT'
\t+ return 'INDENT'
[^\S\n]+ /* skip whitespace */
[0-9]+ return 'number'
'on' return 'true'
'off' return 'false'
'yes' return 'true'
'class' return 'class'
'no' return 'false'
'true' return 'true'
'false' return 'false'
(return\s+) return 'return1'
'null' return 'null'
(return) return 'return2'
'finally' return 'finally'
'switch' return 'switch'
'catch' return 'catch'
'when' return 'when'
'else' return 'else'
'then' return 'then'
'for' return 'for'
'var' return 'var'
'try' return 'try'
'if' return 'if'
'isnt' return '!=='
'this' return 'THIS'
([a-zA-Z_][a-zA-Z_0-9]*) return 'name'
"|>" return '|>'
[-~]\> return 'Glyph'
'@@@' return '@@@'
'...' return '...'
'>>>=' return '>>>='
'<<<=' return '<<<='
'>>>' return '>>>'
'<<<' return '<<<'
'===' return '==='
'!==' return '!=='
'<->' return '<->'
'||' return '||'
'!=' return '!='
'==' return '=='
'>>' return '>>'
'<<' return '<<'
'&&' return '&&'
'**' return '**'
'::' return '::'
'|=' return '|='
'+=' return '+='
'/=' return '/='
'-=' return '-='
'&=' return '&='
'++' return '++'
'%=' return '%='
'#{' return '#{'
'#' return '#'
"'" return "'"
'"' return '"'
';' return ';'
'>' return '>'
'<' return '<'
'^' return '^'
',' return ','
'(' return '('
')' return ')'
'[' return '['
']' return ']'
'.' return '.'
'`' return '`'
'%' return '%'
':' return ':'
'&' return '&'
'@' return '@'
'=' return '='
'|' return '|'
'+' return '+'
'-' return '-'
'/' return '/'
'*' return '*'
'{' return '{'
'}' return '}'
'?' return '?'
<<EOF>> return 'EOF'
/lex
%right '='
%left '+' '-'
%left '*' '/'
%start root
%%
root
: Body EOF
%{
return $1
}%
;
Body
: Switch NL "Body"
{ $$ = $1 + $2 + $3 }
| Switch
| Expression NL OptIndent Body
{ $$ = $1 + ';' + $2 + $3 + $4 }
| Expression
| Try NL "Body"
{ $$ = $1 + $2 + $3 }
| Try
| For NL Body
{ $$ = $1 + $2 + $3 }
| For
| Function OptNewLine Body
{ $$ = $1 + $2 + $3 }
| Function
| Object NL Body
{ $$ = $1 + $2 + $3 }
| Object
| Class NL Body
{ $$ = $1 + $2 + $3 }
| Class
| MultiLineString NL Body
{ $$ = $1 + $2 + $3 }
| MultiLineString
| Line NL OptIndent Body
{ $$ = $1 + $2 + $3 + $4 }
| BodyEnd OptNewLine -> $1 + $2
| BodyEnd
|
{ $$ = '' }
;
Return
: OptIndent return1 Expression OptSemiColon
{ $$ = $1 + $2 + ' ' + $3 + ';' }
| OptIndent return2 OptSemiColon
{ $$ = $1 + $2 + ';' }
| OptIndent Expression OptSemiColon
{ $$ = $1 + 'return ' + $2 + ';' }
;
Class
: class Name OptIndent '{' NL INDENT ClassBody OptIndent '}' OptSemiColon
{ $$ = 'class ' + $2 + ' {\n' + $6 + $7 + $8 + '\n};' }
;
ClassBody
: name ParamDef OptIndent '{' NL INDENT Body OptIndent '}' NL INDENT ClassBody
{ $$ = $1 + $2.split('this.').join`` + ' {' + $2.replace(/^\(/,'').replace(/\)$/,'').split(',').filter(a=>a.includes('this.')).map(a=>a.split(/\s*=\s*/)[0]).map(a=>a+ ' = ' + a.replace(/\s*this\./,'') + ';').join`\n` + $6 + $Body + $8 + '}\n' + $11 + $12 }
| name ParamDef OptIndent '{' NL INDENT Body OptIndent '}' NL
{ $$ = $1 + $2.split('this.').join`` + ' {' + $2.replace(/^\(/,'').replace(/\)$/,'').split(',').filter(a=>a.includes('this.')).map(a=>a.split(/\s*=\s*/)[0]).map(a=>a+ ' = ' + a.replace(/\s*this\./,'') + ';').join`\n` + $6 + $Body + $8 + '}\n' }
;
Line
: OptIndent Expression OptSemiColon
{ $$ = $1 + $2 + ';' }
;
BodyEnd
: Return
| INDENT Expression OptSemiColon
{ $$ = $1 + $2 + ';' }
| Expression OptSemiColon -> $1 + ';'
;
OptSemiColon
: OptSemiColon OptSemiColon
| ';'
|
{ $$ = '' }
;
Ternary
: 'if' Expression 'then' Expression 'else' Expression
{ $$ = $2 + ' ? ' + $4 + ' : ' + $6 }
;
OptNewLine
: NL
|
{ $$ = '' }
;
OptIndent
: INDENT
|
{ $$ = '' }
;
OptComma
: ','
|
{ $$ = '' }
;
Function
: FunctionStart '{' OptNewLine Body OptNewLine OptIndent '}' OptSemiColon
{ $$ = $1 + ' {\n' + $Body + '\n}' }
| FunctionStart '{ OptNewLine }' OptSemiColon
{ $$ = $1 + '{' + $3 + '}' }
;
FunctionStart
: ParamDef Glyph
%{
switch($2) {
case '~>':
$$ = 'function' + $1;
break;
case '->':
$$ = $1 + ' =>';
break;
}
}%
;
Boolean
: 'true' -> 'true'
| 'false' -> 'false'
;
Param
: '( )' -> '()'
| '(' List ')' -> '(' + $List + ')'
;
List
: Value ',' List
{ $$ = $1 + ',' + $3 }
| Name ',' List
{ $$ = $1 + ',' + $3 }
| '...' Value ',' List
{$$ = '...' + $Value + ',' + $List }
| '...' Value
{ $$ = '...' + $3 }
| '...' Name
{ $$ = '...' + $2 }
| Value
| Name
| Expression
{$$ = $1}
;
ParamDef
: '( )' -> '()'
| '(' ParamDefList ')' -> $1 + $2 + $3
;
ParamDefList
: Name '... ,' ParamDefList
{ $$ = $1 + $2 + $3 + $4 }
| Name '=' Expression ',' ParamDefList
{ $$ = $1 + ' = ' + $3 + ', ' + $5 }
| '(' Expression ')'
| Name ',' ParamDefList
{ $$ = $1 + $2 + $3 }
| Name '...'
{ $$ = $1 + $2 }
| Name '=' Expression
{ $$ = $1 + ' = ' + $3 }
| Name
{ $$ = $1 }
;
Expression
: ERRORS
| Name '<->' Name
{ $$ = `[${$1}, ${$3}] = [${$3}, ${$1}]` }
| Name '=' Switch
{ $$ = $1 + $2 + '(function(){\n ' + $3 + '\n' + '})()' }
| OptVar Name '=' Expression
{$$ = `${$1.length?$1+' ':''}${$2} = ${$4}` }
| Name '=' Expression
{ $$ = $1 + $2 + $3 }
| Name ComboMath Expression
{ $$ = $1 + $2 + $3 }
| Expression Math Expression
{ $$ = $1 + $2 + $3 }
| Expression Math Expression
{ $$ = $1 + $2 + $3 }
| Expression Operator Expression
{ $$ = $1 + $2 + $3 }
| Name '++'
{ $$ = $1 + $2 }
| Name List
{$$ = `${$1}(${$2})`}
| Name Param
{$$ = $1 + $2}
| Value 'is' Name
{$$ = `${$1} instanceof ${$3}`}
| Ternary
| Function
| '(' Expression ')'
{ $$ = $1 + $2 + $3 }
| For
| Switch
| Value
| Name
| Comment
| Pipe
;
Comment
: COMMENT1
{ $$ = $1.replace(/^@/, '//') }
| COMMENT2
{ $$ = $1.replace(/^@@@/, '/*').replace(/@@@/g, '*/'); }-
;
Operator
: '&&'
| '||'
| '!==' -> '!=='
| '===' -> '==='
| '=='
| '>='
| '<='
| '!='
;
ComboMath
: '|='
| '*='
| '/='
| '%='
| '+='
| '-='
| '&='
| '>>>='
| '>>='
| '<<='
;
Math
: '>>>'
| '**'
| '<<'
| '>>'
| '&'
| '^'
| '|'
| '+'
| '*'
| '+'
| '-'
| '/'
| '<'
| '>'
| '%'
;
OptVar
: 'var'
|
{ $$ = '' }
;
Value
: Function
| Boolean
| Number
| Pipe
| String
| Array
| Null
;
For
: for '(' Expression ';' Expression ';' Expression ')' '{' OptNewLine Body OptNewLine OptIndent '}' OptSemiColon
{ $$ = $1 + '(' + $3 + $4 + $5 + $6 + $7 + $8 + $9 + $10 + $11 + $12 + $13 + $14 }
;
LineList
: OptIndent Expression ',' OptNewLine LineList
{ $$ = $1 + $2 + ',\n' + $LineList }
| OptIndent Expression
{ $$ = $1 + $2 }
;
Array
: '[' OptNewLine ArrList OptNewLine ']'
{ $$ = $1 + '\n' + $3 + '\n' + $5 }
| '[' NL LineList NL ']'
{ $$ = $1 + '\n' + $3 + '\n' + $5 }
| '[ ]'
{ $$ = '[]' }
;
KeyList
: Key
{ $$ = $1 + ': ' + $1 }
| Value
{ $$ = "'" + $1 + "'" + ': ' + $1 }
;
Switch
: switch Value OptIndent '{' OptIndent NL WhenList OptNewLine OptIndent '}' OptIndent OptSemiColon
{ $$ = $switch + '(' + $Value + ') {\n' + $WhenList + '};' }
| switch Name '{' NL WhenList OptNewLine OptIndent '}' OptSemiColon
{ $$ = $switch + '(' + $Name + ') {\n' + $WhenList + '};' }
;
WhenList
: OptIndent when WhenArr then '{' OptNewLine Body OptNewLine OptIndent '}' OptNewLine WhenList
{ $$ = $OptIndent + ' ' + $WhenArr + ' ' + $Body + '\n' + $WhenList }
| OptIndent when WhenArr then '{' OptNewLine Body OptNewLine OptIndent '}' OptNewLine
{ $$ = $OptIndent + ' ' + $WhenArr + ' ' + $Body + '\n' + $OptIndent + ' break;' }
| OptIndent else '{' OptNewLine Body OptNewLine OptIndent '}'
{ $$ = $OptIndent + 'default: \n' + $Body }
;
WhenArr
: Expression ',' WhenArr
{ $$ = 'case ' + $1 + ': \n' + $3 }
| Expression
{ $$ = 'case ' + $1 + ': \n' }
;
Object
: '{' OptNewLine OptIndent AssignList OptNewLine '}'
{ $$ = $1 + '\n' + $3 + $4 + '\n' + $6 }
| '{' KeyList '}'
{ $$ = $1 + '\n' + $2 + '\n}' }
| NL NL AssignList NL
{ $$ = '{\n' + $3 + '\n}' }
| NL AssignList
{ $$ = '{\n' + $2 + '\n}' }
| AssignList
{ $$ = '{\n' + $1 + '\n}' }
| '{ }'
{ $$ = '{}' }
;
AssignList
: OptNewLine OptIndent Key ':' Expression ',' AssignList
{ $$ = $1 + $2 + $3 + ': ' + $5 + ',' + $7 }
| OptNewLine OptIndent Key ':' Expression
{ $$ = '\n' + $2 + $3 + ': ' + $5 }
;
Try
: OptIndent try '{' OptNewLine Body OptNewLine OptIndent '}' catch ParamDef '{' OptNewLine Body OptNewLine OptIndent '}' finally '{' OptNewLine Body OptNewLine OptIndent '}' OptSemiColon
{ $$ = $1 + $try + ' {\n' + $1 + ' ' + $5 + '\n' + $1 + '} catch' + $ParamDef + ' {\n' + $1 + ' ' + $31 + '\n' + $1 + '} finally {\n' + $1 + ' ' + $20 + '\n' + $1 + '}' }
| OptIndent try '{' OptNewLine Body OptNewLine OptIndent '}' catch ParamDef '{' OptNewLine Body OptNewLine OptIndent '}' OptSemiColon
{ $$ = $1 + $try + ' {\n' + $1 + ' ' + $6 + '\n' + $1 + '} catch' + $ParamDef + ' {\n' + $1 + ' ' + $13 + $1 + '\n};' }
;
MultiLineString
: STRING2
{
function interpolate(str) {
let current = str;
str = str.replace(/\#\{(.*)\}/, function(_,match2) { return '${' + match2 + '}'});
while(str != current) {
current = str;
str = str.replace(/\#\{(.*)\}/, function(_,match2) { return '${' + match2 + '}'});
}
return str;
}
$$ = "`" + interpolate($1.replace(/((^''')(\s*(?:(\\.)|(?!\'){3,}.)+\s*)('''$)|(^""")(\s*(?:(?!\'){3,}.)+\s*)("""$))/, function(_,Match1, Match2) { return Match1||Match2 })) + "`" }
;
String
: STRING1
{
function interpolate(str) {
let current = str;
str = str.replace(/\#\{(.*)\}/, function(_,match2) { return '${' + match2 + '}'});
while(str != current) {
current = str;
str = str.replace(/\#\{(.*)\}/, function(_,match2) { return '${' + match2 + '}'});
}
return str;
}
$$ = "'" + interpolate($1) + "'" }
;
Key
: String
| Name
| Number
| '[' Name ']'
{ $$ = $1 + $2 + $3 }
| '[' Primitive ']'
{ $$ = $1 + $2 + $3 }
;
Primitive
: Number
| String
| Boolean
;
Access
: '[' String ']'
{ $$ = $1 + $2 + $3 }
| '[' Name ']'
{ $$ = $1 + $2 + $3 }
| '.' Name
{ $$ = $1 + $2 }
;
Name
: name Access -> $1 + $2
| name
| '#' name -> 'this.' + $2
| '#'
{ $$ = 'this' }
| Name '::' name
{$$ = $1 + '.prototype.' + $3}
| Name '::'
{ $$ = $1 + '.prototype' }
;
Number
: number
;
Pipe
: Expression '|>' Name
{$$ = $Name + '(' + $Expression + ')' }
| Expression '|>' '#' Name
{$$ = 'this.' + $Name + '(' + $Expression + ')' }
| Pipe '|>' Name
{$$ = $3 + '(' + $1 + ')' }
| Pipe '|>' '#' Name
{$$ = 'this.' + $4 + '(' + $1 + ')' }
;
@@@
as"//@@"
. \$\endgroup\$@@@comment start\na=1\ncomment end@@@
, expecting it to be transformed into/*comment start\na=1\ncomment end*/
. However, I got//@@comment start;\na=1;\ncomment(end(//@@))
, so it seems that multiline comments aren't supported and that normal end-of-line comments have transformation issues (getting placed as argument to function call?). \$\endgroup\$@comment start\na = 1\ncomment end@
gets transformed to//comment start;\na=1;\ncomment(end(//))
. Notice the semicolon after start in the first line, and the double slash inside the parentheses on the last line! \$\endgroup\$