Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions TestHScript.hx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ class TestHScript extends TestCase {
assertScript('var newMap = [{a:"a"}=>"foo", objKey=>"bar"]; newMap[objKey];', 'bar', vars);
}

function testStringInterpolation():Void {
assertScript("var a = 5; 'a is ${a}'", 'a is 5');
assertScript("var a = 5; 'a is ${a + 1}'", 'a is 6');
assertScript("var a = 5; 'a is ${if (a > 3) \"big\" else \"small\"}'", 'a is big');
assertScript("var a = 5; 'a is ${switch (a) { case 0: \"zero\"; case 5: \"five\"; default: \"other\"; }}'", 'a is five');
assertScript("'Hello, ${{var val = false; if (val) \"world\" else {var num = 5 + 3; 'userid_${num}';}}}!'", 'Hello, userid_8!');
}

static function main() {
#if ((haxe_ver < 4) && php)
// uncaught exception: The each() function is deprecated. This message will be suppressed on further calls (errno: 8192)
Expand Down
7 changes: 6 additions & 1 deletion hscript/Expr.hx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
*/
package hscript;

enum StringKind {
DoubleQuotes;
SingleQuotes;
}

enum Const {
CInt( v : Int );
CFloat( f : Float );
CString( s : String );
CString( s : String , ?kind : StringKind);
}

#if hscriptPos
Expand Down
188 changes: 186 additions & 2 deletions hscript/Parser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,19 @@ class Parser {
e = mk(EIdent(id));
return isBlock(e) ? e : parseExprNext(e);
case TConst(c):
if (c.match(CString(_, SingleQuotes)))
{
var ex = makeInterpolatedStr(tk);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use a switch to extract the String instead of passing the token to makeInterpolatedStr

if (ex.length == 1)
return parseExprNext(ex[0]);
else
{
var result = ex[0];
for (i in 1...ex.length)
result = makeBinop("+", result, ex[i]);
return parseExprNext(mk(EParent(result), p1));
}
}
return parseExprNext(mk(EConst(c)));
case TPOpen:
tk = token();
Expand Down Expand Up @@ -644,6 +657,143 @@ class Parser {
}
}

function makeInterpolatedStr(tk:Token):Array<Expr> {
var ex = new Array();
var s = switch(tk)
{
case TConst(c):
switch(c)
{
case CString(s):
s;
case _:
null;
}
case _:
null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a String parameter so you don't have to unwrap it this way


if (s == null)
error(ECustom("Invalid string literal"), tokenMin, tokenMax);

var nextStr = "";
function pushNextStr() {
if (nextStr != "") {
ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax));
nextStr = "";
}
}

var i = 0;
var c = s.charCodeAt(i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For fast parsing, use StringTools.fastCodeAt(i) instead of charCodeAt

while (true)
{
if (StringTools.isEof(c))
{
pushNextStr();
break;
}

if (c != "$".code)
{
nextStr += String.fromCharCode(c);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a StringBuf instead of adding chars with +

c = s.charCodeAt(++i);
continue;
}

c = s.charCodeAt(++i);
if (c >= 48 && c <= 57 || c == "$".code)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use .code instead of char numbers 48 & 57 + add parenthesizes around the &&

{
nextStr += "$" + String.fromCharCode(c);
c = s.charCodeAt(++i);
continue;
}

if( idents[c] ) {
var id = String.fromCharCode(c);
while( true ) {
c = s.charCodeAt(++i);
if( StringTools.isEof(c) ) c = 0;
if( !idents[c] ) {
break;
}
id += String.fromCharCode(c);
}
pushNextStr();
ex.push(mk(EIdent(id), tokenMin, tokenMax));
nextStr = "";
}
else if (c == "{".code)
{
pushNextStr();
nextStr = "";
var lastInput = input;
var lastReadPos = readPos;
var lastChar = char;
var lastTokens = tokens;

function reset()
{
input = s;
readPos = i;
char = -1;
#if hscriptPos
tokens = new List();
#else
tokens = new haxe.ds.GenericStack<Token>();
#end
}
reset();
var brOpens = -1;
while( true ) {
var tk = token();
if ( tk == TBrOpen )
{
if (brOpens == -1)
brOpens = 0;
brOpens++;
}
else if ( tk == TBrClose )
{
if (brOpens == -1)
unexpected(tk);
brOpens--;
if (brOpens <= 0)
break;
}
if( tk == TEof )
error(EUnterminatedString, currentPos, currentPos);
}
var endPos = readPos - 1;
reset();
// cool hack to quickly close expressions
input = s.substring(i, endPos) + ";}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead start after the first {

readPos = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the token positions with -D hscriptPos will not be good if you don't setup additional things here

var a = new Array();
while( true ) {
var tk = token();
if( tk == TEof ) break;
push(tk);
parseFullExpr(a);
}
pushNextStr();
ex.push(mk(EBlock(a),0));
nextStr = "";
input = lastInput;
i = endPos + 1;
c = s.charCodeAt(i);
readPos = lastReadPos;
char = lastChar;
tokens = lastTokens;
}
else
{
nextStr += "$";
}
}
return ex;
}

function parseStructure(id) {
#if hscriptPos
var p1 = tokenMin;
Expand Down Expand Up @@ -1385,6 +1535,10 @@ class Parser {
inline function readChar() {
return StringTools.fastCodeAt(input, readPos++);
}

inline function peekChar() {
return StringTools.fastCodeAt(input, readPos);
}

function readString( until ) {
var c = 0;
Expand Down Expand Up @@ -1438,7 +1592,36 @@ class Parser {
esc = true;
else if( c == until )
break;
else {
else if (c == '$'.code && peekChar() == '{'.code)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not correct, it should only apply to single quote strings

{
b.addChar('$'.code);
var brOpens = -1;
while (true)
{
c = readChar();
if (StringTools.isEof(c))
{
line = old;
error(EUnterminatedString, p1, p1);
break;
}
b.addChar(c);
if ( c == '{'.code )
{
if (brOpens == -1)
brOpens = 0;
brOpens++;
}
else if ( c == '}'.code )
{
if (brOpens == -1)
invalidChar(c);
brOpens--;
if (brOpens <= 0)
break;
}
}
} else {
if( c == 10 ) line++;
b.addChar(c);
}
Expand Down Expand Up @@ -1589,7 +1772,8 @@ class Parser {
case "}".code: return TBrClose;
case "[".code: return TBkOpen;
case "]".code: return TBkClose;
case "'".code, '"'.code: return TConst( CString(readString(char)) );
case "'".code: return TConst( CString(readString(char), SingleQuotes) );
case '"'.code: return TConst( CString(readString(char), DoubleQuotes) );
case "?".code:
char = readChar();
if( char == ".".code )
Expand Down