Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
olivier-spinelli committed Jul 22, 2016
2 parents 9006f8b + ffc6db4 commit 4e4479c
Show file tree
Hide file tree
Showing 20 changed files with 576 additions and 126 deletions.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@ A very simple template engine (based on <% ... %> and <%= ... %> tag
It is under developpement and any contributions are welcome.

## Key aspects
- Primary goal is full thread safety and API security. Not performance!
- Safe scripting language implemented as a a state machine (no thread at all but nevertheless interuptible: breakpoints, step in, step over, etc.).
- Easy binding (two-way for writable properties and fields) to any external .Net object (uses reflection) but also an original way to publish an API to the script.
- No dependency (currently released only on .Net 4.5.1) and less than 100KB dll.
- Inspired from javascript but with important differences.
- Primary goal is full thread safety and API security. *Not performance!*
- Safe scripting language implemented as a a *state machine* (no thread at all but nevertheless interuptible: breakpoints, step in, step over, etc.).
- *Easy binding* (two-way for writable properties and fields for instance) to any external .Net object (uses reflection) that relies on a rather original way to publish an API to the script.
- *No dependency* (currently released only on .Net 4.5.1) and *still less than 100KB* dll.
- Inspired from javascript but with important differences to be more *.Net compliant*.

## To Do list
- Enable API securization (currently any properties or methods of external objects are callable).
- A simple call validation hook should minimally do the job.
- White/Black list and support of a kind of [SafeScript] attribute may be useful.
- White/Black list and support of a kind of [SafeScript] attribute or ISafeScript marker interface may be useful.
- Stop supporting javascript operators === and !==
- Current == and != operators must simply use .Net object.Equals method: no implicit conversion must be made.
- "Number" must be replaced with "Integer" and "Double". Integer must be the default but implicit conversion between
the two must be supported.
- Replace JSEvalDate object with a more like DateTime .Net object.
- String currently supports only indexer [] (instead of charAt() javascript method) and ToString() :).
- StringObj must support all other useful methods (Contains, Substring, etc.).
- Two-way support for native functions:
- From the external world to the script (any delegate must be callable just like objects' methods).
- Export script functions as native functions (NativeFunctionObj does the job in the opposite way):
- From the script to the external world (FunctionObj.ToNative() must return a callable delegate).
Evaluation of such delegate must take place on the primary thread on dedicated frame stacks.
- Transparently support async/await (actually any awaitable return) with the defined
but not implemented PExpr.DeferredKind.AsyncCall.

68 changes: 1 addition & 67 deletions Tests/Yodii.Script.Tests/AnalyserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace Yodii.Script.Tests
public class AnalyserTests
{
[Test]
public void EmptyParsing()
public void an_empty_string_is_a_syntax_error()
{
ExprAnalyser a = new ExprAnalyser();
JSTokenizer p = new JSTokenizer();
Expand All @@ -52,72 +52,6 @@ public void EmptyParsing()
}
}

[Test]
public void BadNumbers()
{
ExprAnalyser a = new ExprAnalyser();
JSTokenizer p = new JSTokenizer();

{
p.Reset( "45DD" );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}
{
p.Reset( "45.member" );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}
{
p.Reset( ".45.member" );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}
{
p.Reset( "45.01member" );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}
{
p.Reset( ".45.member" );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}
{
p.Reset( ".45.01member" );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}
{
p.Reset( "45.01e23member" );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}
}

[Test]
public void RoundtripParsing()
{
JSTokenizer p = new JSTokenizer();
Assert.That( JSTokenizer.Explain( JSTokenizerToken.Integer ), Is.EqualTo( "42" ) );

string s = " function ( x , z ) ++ -- { if ( x != z || x && z % x - x >>> z >> z << x | z & x ^ z -- = x ++ ) return x + ( z * 42 ) / 42 ; } void == typeof += new -= delete >>= instanceof >>>= x % z %= x === z !== x ! z ~ = x |= z &= x <<= z ^= x /= z *= x %=";
p.Reset( s );
string recompose = "";
while( !p.IsEndOfInput )
{
recompose += " " + JSTokenizer.Explain( p.CurrentToken );
p.Forward();
}
s = s.Replace( "if", "identifier" )
.Replace( "function", "identifier" )
.Replace( "x", "identifier" )
.Replace( "z", "identifier" )
.Replace( "return", "identifier" );

Assert.That( recompose, Is.EqualTo( s ) );
}

[Test]
public void SimpleExpression()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,44 @@
namespace Yodii.Script.Tests
{
[TestFixture]
public class WithGlobalContext
public class GlobalContextTests
{
[Test]
public void successful_namespace_registration()
{
GlobalContext c = new GlobalContext();
c.Register( "Numbers.One", 1 );
Assert.That( ScriptEngine.Evaluate( "Numbers.One", c ).ToString(), Is.EqualTo( "1" ) );
Assert.Throws<ArgumentException>( () => c.Register( "Numbers.One", 1 ) );
c.Register( "Numbers.Two", 2 );
Assert.That( ScriptEngine.Evaluate( "Numbers.One + Numbers.Two", c ).ToString(), Is.EqualTo( "3" ) );
}

[Test]
public void namespace_can_not_be_registered_on_or_below_a_registered_object()
{
GlobalContext c = new GlobalContext();
c.Register( "Numbers", 1 );
Assert.Throws<ArgumentException>( () => c.Register( "Numbers", 2 ) );
Assert.Throws<ArgumentException>( () => c.Register( "Numbers.One", 3 ) );
c.Register( "X.Numbers", 1 );
Assert.Throws<ArgumentException>( () => c.Register( "X.Numbers", 2 ) );
Assert.Throws<ArgumentException>( () => c.Register( "X.Numbers.One", 3 ) );
Assert.That( ScriptEngine.Evaluate( "X.Numbers", c ).ToString(), Is.EqualTo( "1" ) );
}

[Test]
public void object_can_not_be_regitered_on_or_below_a_registered_namespace()
{
GlobalContext c = new GlobalContext();
c.Register( "NS.Sub.NS.Obj", 1 );
Assert.Throws<ArgumentException>( () => c.Register( "NS", 2 ) );
Assert.Throws<ArgumentException>( () => c.Register( "NS.Sub", 3 ) );
Assert.Throws<ArgumentException>( () => c.Register( "NS.Sub.NS", 4 ) );
Assert.Throws<ArgumentException>( () => c.Register( "NS.Sub.NS.Obj", 5 ) );
Assert.That( ScriptEngine.Evaluate( "NS.Sub.NS.Obj", c ).ToString(), Is.EqualTo( "1" ) );
}

class Context : GlobalContext
{
public double [] AnIntrinsicArray = new double[0];
Expand Down
85 changes: 85 additions & 0 deletions Tests/Yodii.Script.Tests/NativeFunctionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#region LGPL License
/*----------------------------------------------------------------------------
* This file (Tests\Yodii.Script.Tests\WithGlobalContext.cs) is part of Yodii-Script.
*
* Yodii-Script is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Yodii-Script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with Yodii-Script. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright © 2007-2015,
* Invenietis <http://www.invenietis.com>, IN'TECH INFO <http://www.intechinfo.fr>
* All rights reserved.
*-----------------------------------------------------------------------------*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CK.Core;
using NUnit.Framework;

namespace Yodii.Script.Tests
{
[TestFixture]
public class NativeFunctionTests
{
[Test]
public void calling_a_void_delegate()
{
var c = new GlobalContext();
string called = null;
Action<string> a = delegate ( string s ) { called = s; };
c.Register( "CallMe", a );
TestHelper.RunNormalAndStepByStep( @"CallMe( 'I''m famous.' );", o =>
{
Assert.That( o, Is.SameAs( RuntimeObj.Undefined ) );
Assert.That( called, Is.EqualTo( "I'm famous." ) );
}, c );
}

[Test]
public void calling_a_static_function_requires_an_explicit_cast_to_resolve_method_among_method_group()
{
var c = new GlobalContext();
c.Register( "CallMe", (Func<string,string>)StaticFunc );
TestHelper.RunNormalAndStepByStep( @"CallMe( 'I''m famous.' );", o =>
{
Assert.IsInstanceOf<StringObj>( o );
Assert.That( o.ToString(), Is.EqualTo( "Yes! I'm famous." ) );
}, c );
}

static string StaticFunc( string s ) => "Yes! " + s;

class O
{
public string Text { get; set; }
public string InstanceMethod( string c ) => Text + c;
}

[Test]
public void calling_an_instance_method_requires_an_explicit_cast_to_resolve_method_among_method_group()
{
var obj = new O() { Text = "Oh My... " };
var c = new GlobalContext();
c.Register( "CallMe", (Func<string, string>)obj.InstanceMethod );
TestHelper.RunNormalAndStepByStep( @"CallMe( 'I''m famous.' );", o =>
{
Assert.IsInstanceOf<StringObj>( o );
Assert.That( o.ToString(), Is.EqualTo( "Oh My... I'm famous." ) );
}, c );
}


}
}
47 changes: 47 additions & 0 deletions Tests/Yodii.Script.Tests/TemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,52 @@ public void when_there_is_no_tag_there_is_no_script()
Assert.That( r.Script, Is.Null );
Assert.That( r.Text, Is.EqualTo( "There is no tag here." ) );
}

class Column
{
public string Name { get; set; }

public string Type { get; set; }

public bool IsPrimaryKey { get; set; }
}

class Table
{
public List<Column> Columns { get; set; }
public string Schema { get; set; }

public string TableName { get; set; }
}

[Test]
public void simple_template_from_Model_object()
{
var t = new Table()
{
Schema = "CK",
TableName = "tToto",
Columns = new List<Column>()
{
new Column() { Name = "Id", Type = "int", IsPrimaryKey = true },
new Column() { Name = "Name", Type = "varchar(40)" }
}
};
var c = new GlobalContext();
c.Register( "Model", t );
var e = new TemplateEngine( c );
var r = e.Process(
@"create table <%=Model.Schema%>.<%=Model.TableName%> (<%foreach c in Model.Columns {%>
<%=c.Name%> <%=c.Type%><%if c.IsPrimaryKey {%> primary key<%} if( c.$index < Model.Columns.Count - 1) {%>,<%}}%>
);" );
Assert.That( r.ErrorMessage, Is.Null );
Assert.That( r.Script, Is.Not.Null );
Assert.That( r.Text, Is.EqualTo( @"create table CK.tToto (
Id int primary key,
Name varchar(40)
);" ) );
}


}
}
88 changes: 88 additions & 0 deletions Tests/Yodii.Script.Tests/TokenizerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#region LGPL License
/*----------------------------------------------------------------------------
* This file (Tests\Yodii.Script.Tests\JSAnalyserTests.cs) is part of Yodii-Script.
*
* Yodii-Script is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Yodii-Script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with Yodii-Script. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright © 2007-2015,
* Invenietis <http://www.invenietis.com>, IN'TECH INFO <http://www.intechinfo.fr>
* All rights reserved.
*-----------------------------------------------------------------------------*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Yodii.Script;
using CK.Core;

namespace Yodii.Script.Tests
{
[TestFixture]
public class TokenizerTests
{
[Test]
public void RoundtripParsing()
{
JSTokenizer p = new JSTokenizer();
Assert.That( JSTokenizer.Explain( JSTokenizerToken.Integer ), Is.EqualTo( "42" ) );

string s = " function ( x , z ) ++ -- { if ( x != z || x && z % x - x >>> z >> z << x | z & x ^ z -- = x ++ ) return x + ( z * 42 ) / 42 ; } void == typeof += new -= delete >>= instanceof >>>= x % z %= x === z !== x ! z ~ = x |= z &= x <<= z ^= x /= z *= x %=";
p.Reset( s );
string recompose = "";
while( !p.IsEndOfInput )
{
recompose += " " + JSTokenizer.Explain( p.CurrentToken );
p.Forward();
}
s = s.Replace( "if", "identifier" )
.Replace( "function", "identifier" )
.Replace( "x", "identifier" )
.Replace( "z", "identifier" )
.Replace( "return", "identifier" );

Assert.That( recompose, Is.EqualTo( s ) );
}

[TestCase( "45DD" )]
[TestCase( "45.member" )]
[TestCase( ".45.member" )]
[TestCase( "45.01member" )]
[TestCase( ".45.member" )]
[TestCase( ".45.01member" )]
[TestCase( "45.01e23member" )]
public void bad_literal_numbers_are_ErrorNumberIdentifierStartsImmediately( string num )
{
JSTokenizer p = new JSTokenizer( num );
Assert.That( p.IsErrorOrEndOfInput, Is.True );
Assert.That( p.ErrorCode, Is.EqualTo( JSTokenizerError.ErrorNumberIdentifierStartsImmediately ) );
}

[TestCase( @"""a""", "a" )]
[TestCase( @"""a""""b""", @"a""b" )]
[TestCase( @"'a'", "a" )]
[TestCase( @"'a''b'", @"a'b" )]
[TestCase( @"'\u3713'", "\u3713" )]
[TestCase( @"'a\u3712b'", "a\u3712b" )]
public void successful_string_parsing( string s, string expected )
{
JSTokenizer p = new JSTokenizer( s );
string r = p.ReadString();
Assert.That( p.IsEndOfInput );
Assert.That( r, Is.EqualTo( expected ) );
}

}
}
Loading

0 comments on commit 4e4479c

Please sign in to comment.