📜 ⬆️ ⬇️

DuoCode: we translate C # in JavaScript

There is a programming language called C #. And there are so many developers who like it very much. And then there is a programming language called JavaScript. Somehow it happened that not all C # developers like it. Now imagine the situation: there is an avid C # developer. He loves C # very much, he writes all the projects on it. But fate decreed that he needed to write a client web application. You know, such that the user does not need to download and install anything for himself, so that he can simply open any browser in any operating system on any device - and the application is already there. And here our lyrical hero has a problem: it seems that JavaScript is perfect for this task, but for some reason you don’t really want to write on it. Fortunately, in the modern world there are many languages ​​that are translated into JavaScript (all sorts of TypeScript , CoffeScript and thousands of others). But our developer turned out to be very stubborn: he stubbornly does not want to change his beloved C # with "enemy" technologies.

Fortunately for him, the happy future is almost here. There is a project called DuoCode . He can translate C # code in JavaScript. While he is in beta, but he is already doing quite well: the innovations are supported C # 6.0, Generic types, Reflection, structures and LINQ, and you can debug final JavaScript on the original C #. Let's take a closer look at what a product is.


')

Hello DuoCode

To understand what is happening is easiest with examples. Let's start with the classic * Hello world *. So, we have a wonderful C # code:

// Original C# code using System; using DuoCode.Dom; using static DuoCode.Dom.Global; // C# 6.0 'using static' syntax namespace HelloDuoCode { static class Program { public class Greeter { private readonly HTMLElement element; private readonly HTMLElement span; private int timerToken; public Greeter(HTMLElement el) { element = el; span = document.createElement("span"); element.appendChild(span); Tick(); } public void Start() { timerToken = window.setInterval((Action)Tick, 500); } public void Stop() { window.clearTimeout(timerToken); } private void Tick() { span.innerHTML = string.Format("The time is: {0}", DateTime.Now); } } static void Run() { System.Console.WriteLine("Hello DuoCode"); var el = document.getElementById("content"); var greeter = new Greeter(el); greeter.Start(); } } } 

With a slight movement of the hand, it turns into JavaScript:

 // JavaScript code generated by DuoCode var HelloDuoCode = this.HelloDuoCode || {}; var $d = DuoCode.Runtime; HelloDuoCode.Program = $d.declare("HelloDuoCode.Program", System.Object, 0, $asm, function($t, $p) { $t.Run = function Program_Run() { System.Console.WriteLine$10("Hello DuoCode"); var el = document.getElementById("content"); var greeter = new HelloDuoCode.Program.Greeter.ctor(el); greeter.Start(); }; }); HelloDuoCode.Program.Greeter = $d.declare("Greeter", System.Object, 0, HelloDuoCode.Program, function($t, $p) { $t.$ator = function() { this.element = null; this.span = null; this.timerToken = 0; }; $t.ctor = function Greeter(el) { $t.$baseType.ctor.call(this); this.element = el; this.span = document.createElement("span"); this.element.appendChild(this.span); this.Tick(); }; $t.ctor.prototype = $p; $p.Start = function Greeter_Start() { this.timerToken = window.setInterval($d.delegate(this.Tick, this), 500); }; $p.Stop = function Greeter_Stop() { window.clearTimeout(this.timerToken); }; $p.Tick = function Greeter_Tick() { this.span.innerHTML = String.Format("The time is: {0}", $d.array(System.Object, [System.DateTime().get_Now()])); // try to put a breakpoint here }; }); 

It looks like this:



I'll tell you what you should pay attention to:Even this simple example is already pleasing. But such an application is not so difficult to write on JavaScript itself. Let's look at the examples more interesting.

Tic tac toe

The distribution includes an example of writing a great HTML game written in pure C #:



The game code includes enum s and indexers:

 public enum Player { None = 0, X = 1, O = -1 } public sealed class Board { public static Player Other(Player player) { return (Player)(-(int)player); } private readonly Player[] Squares; public readonly int Count; public Player this[int position] { get { return Squares[position]; } } public Board() // empty board { //Squares = new Player[9]; Squares = new Player[] { Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None, Player.None }; } private Board(Board board, Player player, int position) : this() { Array.Copy(board.Squares, Squares, 9); Squares[position] = player; Count = board.Count + 1; } public bool Full { get { return Count == 9; } } public Board Move(Player player, int position) { if (position < 0 || position >= 9 || Squares[position] != Player.None) { throw new Exception("Illegal move"); } return new Board(this, player, position); } public Player GetWinner() { if (Count < 5) return Player.None; Player result; bool winning = IsWinning(0, 1, 2, out result) || IsWinning(3, 4, 5, out result) || IsWinning(6, 7, 8, out result) || IsWinning(0, 3, 6, out result) || IsWinning(1, 4, 7, out result) || IsWinning(2, 5, 8, out result) || IsWinning(0, 4, 8, out result) || IsWinning(2, 4, 6, out result); return result; } private bool IsWinning(int p0, int p1, int p2, out Player player) { int count = (int)Squares[p0] + (int)Squares[p1] + (int)Squares[p2]; player = count == 3 ? Player.X : count == -3 ? Player.O : Player.None; return player != Player.None; } } 

Notice how you manage to manage with DOM elements:

 public static void Main(string[] args) { for (var i = 0; i < 9; i++) { Dom.HTMLInputElement checkbox = GetCheckbox(i); checkbox.checked_ = false; checkbox.indeterminate = true; checkbox.disabled = false; checkbox.onclick = OnClick; } if (new Random().Next(2) == 0) ComputerPlay(); UpdateStatus(); } private static dynamic OnClick(Dom.MouseEvent e) { int position = int.Parse(((Dom.HTMLInputElement)e.target).id[1].ToString()); try { board = board.Move(Player.X, position); } catch { Dom.Global.window.alert("Illegal move"); return null; } Dom.HTMLInputElement checkbox = GetCheckbox(position); checkbox.disabled = true; checkbox.checked_ = true; if (!board.Full) ComputerPlay(); UpdateStatus(); return null; } private static Dom.HTMLInputElement GetCheckbox(int index) { string name = "a" + index.ToString(); Dom.HTMLInputElement checkbox = Dom.Global.document.getElementById(name).As<Dom.HTMLInputElement>(); return checkbox; } 

Webgl

Want to work with WebGL? No problems! Take the C # code:

 using DuoCode.Dom; using System; namespace WebGL { using GL = WebGLRenderingContext; internal static class Utils { public static WebGLRenderingContext CreateWebGL(HTMLCanvasElement canvas) { WebGLRenderingContext result = null; string[] names = { "webgl", "experimental-webgl", "webkit-3d", "moz-webgl" }; foreach (string name in names) { try { result = canvas.getContext(name); } catch { } if (result != null) break; } return result; } public static WebGLShader CreateShaderFromScriptElement(WebGLRenderingContext gl, string scriptId) { var shaderScript = (HTMLScriptElement)Global.document.getElementById(scriptId); if (shaderScript == null) throw new Exception("unknown script element " + scriptId); string shaderSource = shaderScript.text; // Now figure out what type of shader script we have, based on its MIME type int shaderType = (shaderScript.type == "x-shader/x-fragment") ? GL.FRAGMENT_SHADER : (shaderScript.type == "x-shader/x-vertex") ? GL.VERTEX_SHADER : 0; if (shaderType == 0) throw new Exception("unknown shader type"); WebGLShader shader = gl.createShader(shaderType); gl.shaderSource(shader, shaderSource); // Compile the shader program gl.compileShader(shader); // See if it compiled successfully if (!gl.getShaderParameter(shader, GL.COMPILE_STATUS)) { // Something went wrong during compilation; get the error var errorInfo = gl.getShaderInfoLog(shader); gl.deleteShader(shader); throw new Exception("error compiling shader '" + shader + "': " + errorInfo); } return shader; } public static WebGLProgram CreateShaderProgram(WebGLRenderingContext gl, WebGLShader fragmentShader, WebGLShader vertexShader) { var shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); bool linkStatus = gl.getProgramParameter(shaderProgram, GL.LINK_STATUS); if (!linkStatus) throw new Exception("failed to link shader"); return shaderProgram; } public static WebGLTexture LoadTexture(WebGLRenderingContext gl, string resourceName) { var result = gl.createTexture(); var imageElement = Properties.Resources.duocode.Image; imageElement.onload = new Func<Event, dynamic>((e) => { UploadTexture(gl, result, imageElement); return true; }); return result; } public static void UploadTexture(WebGLRenderingContext gl, WebGLTexture texture, HTMLImageElement imageElement) { gl.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, GL.ONE); gl.bindTexture(GL.TEXTURE_2D, texture); gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, imageElement); gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR); gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_NEAREST); gl.generateMipmap(GL.TEXTURE_2D); gl.bindTexture(GL.TEXTURE_2D, null); } public static float DegToRad(float degrees) { return (float)(degrees * System.Math.PI / 180); } } } 

And we apply DuoCode magic to it:

 WebGL.Utils = $d.declare("WebGL.Utils", System.Object, 0, $asm, function($t, $p) { $t.CreateWebGL = function Utils_CreateWebGL(canvas) { var result = null; var names = $d.array(String, ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]); for (var $i = 0, $length = names.length; $i != $length; $i++) { var name = names[$i]; try { result = canvas.getContext(name); } catch ($e) {} if (result != null) break; } return result; }; $t.CreateShaderFromScriptElement = function Utils_CreateShaderFromScriptElement(gl, scriptId) { var shaderScript = $d.cast(document.getElementById(scriptId), HTMLScriptElement); if (shaderScript == null) throw new System.Exception.ctor$1("unknown script element " + scriptId); var shaderSource = shaderScript.text; // Now figure out what type of shader script we have, based on its MIME type var shaderType = (shaderScript.type == "x-shader/x-fragment") ? 35632 /* WebGLRenderingContext.FRAGMENT_SHADER */ : (shaderScript.type == "x-shader/x-vertex") ? 35633 /* WebGLRenderingContext.VERTEX_SHADER */ : 0; if (shaderType == 0) throw new System.Exception.ctor$1("unknown shader type"); var shader = gl.createShader(shaderType); gl.shaderSource(shader, shaderSource); // Compile the shader program gl.compileShader(shader); // See if it compiled successfully if (!gl.getShaderParameter(shader, 35713 /* WebGLRenderingContext.COMPILE_STATUS */)) { // Something went wrong during compilation; get the error var errorInfo = gl.getShaderInfoLog(shader); gl.deleteShader(shader); throw new System.Exception.ctor$1("error compiling shader '" + $d.toString(shader) + "': " + errorInfo); } return shader; }; $t.CreateShaderProgram = function Utils_CreateShaderProgram(gl, fragmentShader, vertexShader) { var shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); var linkStatus = gl.getProgramParameter(shaderProgram, 35714 /* WebGLRenderingContext.LINK_STATUS */); if (!linkStatus) throw new System.Exception.ctor$1("failed to link shader"); return shaderProgram; }; $t.LoadTexture = function Utils_LoadTexture(gl, resourceName) { var result = gl.createTexture(); var imageElement = WebGL.Properties.Resources().get_duocode().Image; imageElement.onload = $d.delegate(function(e) { WebGL.Utils.UploadTexture(gl, result, imageElement); return true; }, this); return result; }; $t.UploadTexture = function Utils_UploadTexture(gl, texture, imageElement) { gl.pixelStorei(37440 /* WebGLRenderingContext.UNPACK_FLIP_Y_WEBGL */, 1 /* WebGLRenderingContext.ONE */); gl.bindTexture(3553 /* WebGLRenderingContext.TEXTURE_2D */, texture); gl.texImage2D(3553 /* WebGLRenderingContext.TEXTURE_2D */, 0, 6408 /* WebGLRenderingContext.RGBA */, 6408 /* WebGLRenderingContext.RGBA */, 5121 /* WebGLRenderingContext.UNSIGNED_BYTE */, imageElement); gl.texParameteri(3553 /* WebGLRenderingContext.TEXTURE_2D */, 10240 /* WebGLRenderingContext.TEXTURE_MAG_FILTER */, 9729 /* WebGLRenderingContext.LINEAR */); gl.texParameteri(3553 /* WebGLRenderingContext.TEXTURE_2D */, 10241 /* WebGLRenderingContext.TEXTURE_MIN_FILTER */, 9985 /* WebGLRenderingContext.LINEAR_MIPMAP_NEAREST */); gl.generateMipmap(3553 /* WebGLRenderingContext.TEXTURE_2D */); gl.bindTexture(3553 /* WebGLRenderingContext.TEXTURE_2D */, null); }; $t.DegToRad = function Utils_DegToRad(degrees) { return (degrees * 3.14159265358979 /* Math.PI */ / 180); }; }); 

You can independently poke a demo on the official site. It looks like this:



Raytracer

And this is not the limit! One example includes the full-fledged RayTracer (with vector math, color and lighting, camera and surfaces — all on pure C #):



Debugging

It sounds incredible, but you can debug this miracle directly in the browser. C # sources include:



Currently, debugging is possible in VS 2015, IE, Chrome and Firefox.

A couple more examples

When broadcasting from C # to JavaScript, one of the most sensitive issues is structures. Today, DuoCode only supports immutable structures, but for a good project this should be enough (as we know, mutable structures should be avoided).

C #:

 public struct Point { public readonly static Point Zero = new Point(0, 0); public readonly int X; public readonly int Y; public Point(int x, int y) { X = x; Y = y; } } 

Javascript:

 HelloDuoCode.Program.Point = $d.declare("Point", null, 62, HelloDuoCode.Program, function($t, $p) { $t.cctor = function() { $t.Zero = new HelloDuoCode.Program.Point.ctor$1(0, 0); }; $t.ctor = function Point() { this.X = 0; this.Y = 0; }; $t.ctor.prototype = $p; $t.ctor$1 = function Point(x, y) { this.X = x; this.Y = y; }; $t.ctor$1.prototype = $p; }); 

Personally, I am particularly pleased that there is full LINQ support:

C #:

 public static IEnumerable<int> Foo() { return Enumerable.Range(0, 10).Where(x => x % 2 == 0).Select(x => x * 3); } 

Javascript:

 $t.Foo = function Program_Foo() { return System.Linq.Enumerable.Select(System.Int32, System.Int32, System.Linq.Enumerable.Where(System.Int32, System.Linq.Enumerable.Range(0, 10), $d.delegate(function(x) { return x % 2 == 0; }, this)), $d.delegate(function(x) { return x * 3; }, this)); }; 

Small pleasures like Generic , params , nullable , method overloading, defaults are also included:

C #:

 public class Foo<T> where T : IComparable<T> { public void Bar(int? x, T y, string z = "value") { System.Console.WriteLine((x ?? -1) + y.ToString() + z); } public void Bar(string z, params object[] args) { } } // Main new Foo<int>().Bar(null, 2); 

Javascript:

 HelloDuoCode.Program.Foo$1 = $d.declare("Foo`1", System.Object, 256, HelloDuoCode.Program, function($t, $p, T) { $t.ctor = function Foo$1() { $t.$baseType.ctor.call(this); }; $t.ctor.prototype = $p; $p.Bar$1 = function Foo$1_Bar(x, y, z) { System.Console.WriteLine$10($d.toString(($d.ncl(x, -1))) + y.ToString() + z); }; $p.Bar = function Foo$1_Bar(z, args) {}; }, [$d.declareTP("T")]); // Main new (HelloDuoCode.Program.Foo$1(System.Int32).ctor)().Bar$1(null, 2, "value"); 

Conclusion

Let me remind you that DuoCode is still in beta state, but today the list of features pleasantly pleases the eye:



Development goes fast enough, there are constantly updates with new features. Let's hope that we are literally a few steps away from that bright future, when you can write really complex client web applications in C #, using the full power of the language and related development tools.

Source: https://habr.com/ru/post/252079/


All Articles