Git Product home page Git Product logo

lsl-pyoptimizer's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lsl-pyoptimizer's Issues

Setup pre-compiled binaries for different platforms [Enhancement]

I was wondering if you had any plans for say pre-made binaries that the user could just download and run?

I locally setup a setup a working singlefile py2exe setup.py (using 32bit 2.7 python) and I edited main.py to correctly support the difference in the zip below.
LSL-PyOptimizer.zip

the specifics of the change to main.py are here

def main(argv):
    """Main executable."""
+    import os
+
+    if hasattr(sys,"frozen") and sys.frozen in ("windows_exe", "console_exe"):
+       lslopt.lslcommon.DataPath=(os.path.dirname(os.path.abspath(sys.executable)) + os.sep)
+   else:
    # If it's good to append the basename to it, it's good to append the
    # auxiliary files' names to it, which should be located where this file is.
        lslopt.lslcommon.DataPath = __file__[:-len(os.path.basename(__file__))]

Dead Code Removal does not remove all dead code

Example code:

default
{
	link_message(integer sender, integer num, string str, key keyVal)
	{
		integer dead = 0;
		if (num == 5)
			dead = 1;
		else
			dead = 2;
		llOwnerSay("Not Dead");
	}
}

Output with DCR + Constant Folding:

default
{
    link_message(integer sender, integer num, string str, key keyVal)
    {
        num == 5;
        llOwnerSay("Not Dead");
    }
}

Without constant folding it is even worse:

default
{
    link_message(integer sender, integer num, string str, key keyVal)
    {
        0;
        if (num == 5)
            1;
        else
            2;
        llOwnerSay("Not Dead");
    }
}

Re-running the output code back through the optimizer will remove the dead code (with Const folding on).

Expected Output:

default
{
    link_message(integer sender, integer num, string str, key keyVal)
    {
        llOwnerSay("Not Dead");
    }
}

Provide Python Packaging Information

Would it be possible to provide basic python packaging (setup.py/pyproject.toml, PEP-518) to simplify LSL-PyOptimizer installation? Were this done, the project could be installed via pip and the CLI automatically placed into the system path.

Optimizer ignores implicit float casting

Assuming the input is x = 5, y = 2

The following function f returns 3:

integer f (integer x, integer y) { return llCeil(x * 1.0 / y); }

Previous function gets optimized to a function q like this, which returns 2:

integer q (integer x, integer y) { return llCeil(x / y); }

Therefore I think that the optimizer should be able to handle this type of implicit float casting, if not we will get unexpected results due to the properties of integer division.

Keeping flags for constants intact in the output?

Hi. Is there any way to use your excellent LSL-PyOptimizer to clean up scripts while keeping the constant flags intact in the output (like ZERO_VECTOR, CHANGED_LINK, and all the other constants defined in builtins.txt)?

The reason is that I was hoping to use your scripts to clean up some LSL for public release but still keep them human-readable. The dead code removal and the other features are just perfect for this but I would like the constant flags to remain as text in the results, rather than being converted to their true values.

For example, at the moment, CHANGED_LINK is converted to 32, and ZERO_VECTOR is converted to <((float)0), ((float)0), ((float)0)>. However, I would like all the flags to remain as (or be converted back to) the original text versions for the results.

I have tried the -ConstFold option but that does not seem to achieve what I want.

Thanks again for all your amazing work :)

Update needed for PBR and other 2023 LSL Additions.

An update is needed to support PBR values such as for set prim params:
PRIM_RENDER_MATERIAL https://wiki.secondlife.com/wiki/PRIM_RENDER_MATERIAL
PRIM_GLTF_BASE_COLOR https://wiki.secondlife.com/wiki/PRIM_GLTF_BASE_COLOR
PRIM_GLTF_NORMAL https://wiki.secondlife.com/wiki/PRIM_GLTF_NORMAL
PRIM_GLTF_METALLIC_ROUGHNESS https://wiki.secondlife.com/wiki/PRIM_GLTF_METALLIC_ROUGHNESS
PRIM_GLTF_EMISSIVE https://wiki.secondlife.com/wiki/PRIM_GLTF_EMISSIVE

As well as:

New function llGetInventoryDesc - returns the description of the inventory item: https://wiki.secondlife.com/wiki/LlGetInventoryDesc
New function llIsFriend - returns TRUE if agent_id and the owner of the prim the script is in are friends: https://wiki.secondlife.com/wiki/LlIsFriend
New function llRezObjectWithParams - apply a list of prim properties that are applied when the object is rezzed: https://wiki.secondlife.com/wiki/LlRezObjectWithParams
New function llListFindListNext - find the index of non-unique list entries beyond the first instance: https://wiki.secondlife.com/wiki/LlListFindListNext
llSetDamage can now set negative damage: https://wiki.secondlife.com/wiki/LlSetDamage

New LSL changed() event type https://wiki.secondlife.com/wiki/CHANGED_RENDER_MATERIAL

And maybe some others that I've missed.

MemoryError in online version

Hi Sei Lisa,
I tried the online version http://lsl.blacktulip-virtual.com/lsl-pyoptimizer/online.php

and got the optimizer output:

Traceback (most recent call last):
  File "/home/sei/lslopt_master/main.py", line 742, in <module>
    ret = main(sys.argv)
  File "/home/sei/lslopt_master/main.py", line 738, in main
    werr(e.__class__.__name__ + ': ' + str(e) + '\n')
  File "/home/sei/lslopt_master/strutil.py", line 85, in werr
    sys.stderr.write(any2u(s, sys.stderr))
  File "/usr/lib/python2.7/encodings/__init__.py", line 100, in search_function
    level=0)
MemoryError

Is the script too large for the online version?

AttributeError: 'optimizer' object has no attribute 'PythonType2LSL'

Optimizer produces the error:
AttributeError: 'optimizer' object has no attribute 'PythonType2LSL'
When optimizing the following code with Constant Folding and Dead Code Removal

default
{
	link_message(integer u1, integer u2, string u3, key u4)
	{
		vector vec = <1, 1, 1>;
		float val = 2.0;
		float x = vec.x * val;
		
		llOwnerSay((string)x);
	}
}

This is the smallest reproducible I managed to get that throws the error, my code uses a lot of macros and constant folding that eventually results in the same error.

Also, I've noticed that with only Dead Code Removal enabled. (Constant folding off) it produces this snippet:

default
{
	link_message(integer u1, integer u2, string u3, key u4)
	{
		vector vec = <1, 1, 1>;
		2.;
		float x = vec.x * 2.;
		llOwnerSay((string)x);
	}
}

That 2.; is pointless.

Output with --preshow option appears to be (nearly) unmodified source

Under Windows 7, using

C:\Windows\System32\cmd.exe /k C:\PROGRA~2\Python27\python.exe ..\LSL-PyOptimizer\main.py --preshow -o main-preprocessed.lsl main.lsl

or

C:\Windows\System32\cmd.exe /k C:\PROGRA~2\Python27\python.exe ..\LSL-PyOptimizer\main.py --preshow >main-preprocessed.lsl main.lsl

results in a main-preprocessed.lsl file that is identical to main.lsl give or take a few changes in whitespace*. Specifically, #define declarations still exist in the output and the substitutions of defined values for the defined names have not been performed. Likewise #ifdef blocks are not removed, etc.

  • (A few characters that show as spaces in LSLEditor, show as newlines in LibreOffice in the main.lsl. In main-preprocessed.lsl, they are corrected to spaces. Also there is an extra newline at or two at the end of one file.)

Subtraction replaced by addition of a negative value

Simplified repro using with Constant folding, and DCR:

integer gPrevious;

default
{
	touch_end(integer num)
	{
		integer now = llGetUnixTime();
		if (now - gPrevious > 15)
		{
			gPrevious = now;
			llOwnerSay("Touch");
		}
	}
}

Results in the following optimized code:

integer gPrevious;

default
{
    touch_end(integer num)
    {
        integer now = llGetUnixTime();
        if (15 < now + -gPrevious)
        {
            gPrevious = now;
            llOwnerSay("Touch");
        }
    }
}

I'm not sure if the conversion of "if (now - gPrevious > 15)" to "if (15 < now + -gPrevious)" is correct, because now there is + and - which each take up a byte each, don't they?

Also, I would excpect the + - to end up being two operations at the lowest level: negation and addition, compared to - just being a single subtraction, which should be faster.

Or is there some weird LSL quirk I am not aware of?

Inconsistent replacement of NULL_KEY with ""

the optimizer breaks this script:

integer driverListener;

startDriverAndOwnerListener(key driver)
{
    llListenRemove(driverListener);
    if (driver != NULL_KEY)
    {
        driverListener = llListen(483, "", driver, "");
    }
}

default
{
    state_entry()
    {
        startDriverAndOwnerListener(NULL_KEY);
    }
}

output:

integer driverListener;

startDriverAndOwnerListener(key driver)
{
    llListenRemove(driverListener);
    if (!(driver == "00000000-0000-0000-0000-000000000000"))
    {
        driverListener = llListen(483, "", driver, "");
    }
}

default
{
    state_entry()
    {
        startDriverAndOwnerListener("");
    }
}

NULL_KEY is replaced with "" in state_entry, but not in the if condition

Concatenate constant strings produces duplicate results

The following code:

default
{
	state_entry()
	{
		string tmp0 = llGetScriptName();
		list tmp1 = [llGetOwner(), llGetOwner()];
		list tmp2 = [llGetScriptName(), llGetScriptName()];
		string encoded = llDumpList2String([tmp0, llList2CSV(tmp1), "Hello", 123, llList2CSV(tmp2)], "|");
		llOwnerSay(encoded);
	}
}

Produces:

default
{
    state_entry()
    {
        string tmp0 = llGetScriptName();
        list tmp1 = [llGetOwner(), llGetOwner()];
        list tmp2 = [llGetScriptName(), llGetScriptName()];
        string encoded = tmp0 + ("|123|Hello|123|" + (llList2CSV(tmp1) + ("|123|Hello|123|" + llList2CSV(tmp2))));
        llOwnerSay(encoded);
    }
}

Which results in way too many 123|Hello sequences

Options Used:
Constant folding, Concatenate constant strings, Dead code removal

Concatenate constant strings is the one that causes it.

Correct result is something like:

default
{
    state_entry()
    {
        string tmp0 = llGetScriptName();
        list tmp1 = [llGetOwner(), llGetOwner()];
        list tmp2 = [llGetScriptName(), llGetScriptName()];
        string encoded = tmp0 + ("|" + (llList2CSV(tmp1) + ("|" + ("Hello" + ("|" + ("123" + ("|" + llList2CSV(tmp2))))))));
        llOwnerSay(encoded);
    }
}

Jump statements with default returns delete the required return value

This is a little of a special case because jump statements are rarely used but take this function for an example:

list out(){
    integer n = 3;
    @continue;
    llOwnerSay((string)n);
    --n;
    if( !n )
        return [];
    jump continue;
    return [];    
}

After optimizing:

list _()
{
    integer loc_n = 3;
    @J_autoGen00001;
    llOwnerSay((string)loc_n);
    --loc_n;
    if (!loc_n)
        return [];
    jump J_autoGen00001;
    // Note that return []; was removed here
}

Even though the return []; at the end of the original function is never reached because of the jump statement, LSL requires it in order to compile.

llChar optimization causing generic lexer error

My code is using llChar to convert numbers to chars that are outside of what the compiler can handle. This causes error: invalid character '\002' in input stream when the optimizer tries to convert it to a string.

Example: llChar( 2) gets replaced with "�" which causes the error.

New LSL commands missing

Hi there, there's a few new LSL functions that are missing:
llLinksetDataDeleteFound
llLinksetDataCountFound

mcpp preprocessor doesn't recognize CSV formatted LSL types like lists and vectors.

Something like

define ListAction(myList, arg) ... //Do something listy.

ListAction([a,b], c);

results in a too many arguments error from the preprocessor when the macro is invoked, and the line is skipped. In the case of vectors and rotations this can probably be worked around by splitting the vector or rotation over 3 or 4 arguments, but in the case of a list with a variable number of arguments, no such workaround is feasible.

Would it be possible to massage the input to the preprocessor by replacing CSV formatted values, specifically lists, rotations, and vectors with, say, otherwise unlikely to occur, appropriately formatted strings, then restoring the original values on return from the preprocessor? Something like:

[a, [b], "c]", ""d""] <-> "pYoPtLiSt = [a, [b], "c]", "\"d\""]"

I believe that this would allow macros to handle such items gracefully with a minimum of effort and undesirable side effects.

(except for adding a backslash to embedded quotes and doubling the number of backslashes in ..." sequences, I don't think other character values would require special handling. Specifically, vectors and rotations inside lists should not need to be pre-preprocessed separately.)

I'm not familiar enough with mcpp and its ilk to be sure, but an alternative to the above string method might be simply to temporarily replace the commas in such values with another unique delimiter, escaping or doubling the delimiter if it occurs elsewhere in the value. Eg:

[a, [b], "c]", "de"] <-> [a [b]"c]" "de"] or [a, [b], "c]", "de"] <-> [a [b]"c]"` "d``e"]

In the escaped case, it would be necessary to deal with ...sequences within the original value appropriately. However, in the doubled delimiter case,...` sequences would not require separate handling.

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2

Hi there,

on one script I tried to optimize, I get these kind of errors (one per each attempt, the numbers seem to be arbitrarily changing, the 2912 shows up often though):

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 4295: ordinal not in range(128)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 200: ordinal not in range(128)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 2912: ordinal not in range(128)

I tested my build process with a different script, and it ran fine, so I assume it's something in my current script the optimizer stumbles across.

My current build process:

  1. Edit the source in VSCode
  2. Paste the file into SL and compile
  3. Copy the Firestorm preprocessed code
  4. Paste the preprocessed code into a file preprocessed.lsl
  5. Run PyOptimizer with -O +ShrinkNames option

VSCode Build Task macro:

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "optimize",
            "type": "shell",
            "command": "/usr/bin/python ${userHome}/GitHub/LSL-PyOptimizer/main.py ${workspaceFolder}/Optimized/preprocessed.lsl  -O +ShrinkNames -o ${workspaceFolder}/Optimized/optimized.lsl ",
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

Exception when a global that is used in a list is optimized out

Test case:

string a = "x";
list b = [a];

default
{
    timer()
    {
        b += "";
    }
}

Output:

Traceback (most recent call last):
  File "main.py", line 518, in <module>
    ret = main()
  File "main.py", line 501, in main
    script = script_header + outs.output(ts, options)
  File "<path redacted>/lslopt/lsloutput.py", line 441, in output
    ret += self.OutCode(node)
  File "<path redacted>/lslopt/lsloutput.py", line 385, in OutCode
    ret += ' = ' + self.OutExpr(child[0]['orig'])
  File "<path redacted>/lslopt/lsloutput.py", line 270, in OutExpr
    ret = '[' + self.OutExprList(child) + ']'
  File "<path redacted>/lslopt/lsloutput.py", line 193, in OutExprList
    ret += self.OutExpr(item)
  File "<path redacted>/lslopt/lsloutput.py", line 253, in OutExpr
    return self.FindName(expr)
  File "<path redacted>/lslopt/lsloutput.py", line 167, in FindName
    if 'scope' in node and 'NewName' in self.symtab[node['scope']][node['name']]:
KeyError: 'a'

The reason is that the output handler tries to output the original expression for that list element, which is kept in 'orig', when the symbol has already been deleted and removed from the symbol table.

The fix is complicated, and will probably involve getting rid of the 'orig' hack and finding a more reliable solution for using key variables inside lists in globals.

UnicodeEncodeError: 'charmap' codec can't encode character

I have the following snippet.lsl that causes the above error.

integer menuChannel = 1;

showDialog(string user, string description, list buttons) {
    llDialog(user, description, buttons, menuChannel);
}

default {
    state_entry() {
        string pageString = " ";
        list options = [];
        showDialog(llGetOwner(), "Test", ["" + " Prev" + pageString, "" + " Back", "" + " Next" + pageString] + llList2List(options, page * 9, page * 9 + 8);
    }
}

I run python ./optimizer/main.py -H -O addstrings,-extendedglobalexpr,optsigns,optfloats .\snippet.lsl -o .\snippet.lslo

And here's a traceback with -y.

Traceback (most recent call last):
  File "./optimizer/main.py", line 661, in <module>
    ret = main(sys.argv)
  File "./optimizer/main.py", line 630, in main
    ReportError(script, e)
  File "./optimizer/main.py", line 36, in ReportError
    sys.stderr.write(script[fieldpos(script, '\n', e.lno):lastpos].decode('utf8') + u"\n")
  File "C:\Python27\lib\encodings\cp850.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u25c0' in position 43: character maps to <undefined>

The line that causes the error is showDialog(llGetOwner(), "Test", ["◀" + " Prev" + pageString, "▼" + " Back", "▶" + " Next" + pageString] + llList2List(options, page * 9, page * 9 + 8);
More specifically, I'm missing a closing parenthesis.

I'm on a Windows 10 machine, if that matters 👍

Avoid defining unnecessary variables while inlining

At the beginning of an inlined function, all parameters get assigned to new local variables even if they are never written to and therefore could be used without creating a copy.

An example with the default settings + Manual function inlining:

foo(integer num) inline {
  llSay(num, "foo");
}

default {
  touch_start(integer num) {
    foo(num);
  }
}

Gets turned into:

default
{
    touch_start(integer LslLibrary)
    {
        {
            integer loc_num = LslLibrary;
            {
                llSay(loc_num, "foo");
            }
        }
    }
}

There does not seem to be a reason to create loc_num in this case.

Mangles Unicode Literals

This is likely a Python 2.7 thing, but this optimizer transforms something like this:

["⑬","①","⑲","⑨","⑩"]

Into this:

[ "⑬", "①", "⑲", "⑨", "⑩"]

Making this tool unfortunately unusable if any Unicode characters are present at all, it seems.

Some float<->string conversions return incorrect results or raise an exception

First of all, thank you for LSL-PyOptimizer, it's clearly been thoughtfully designed and written!

I'm putting these as one issue since they're all relatively small and are closely related, but let me know if you'd prefer I split them up. For reference, they're all based on assertions from LL's LSL Language Test 2.


llOwnerSay((string)((float)"-0.0")); is optimized to llOwnerSay("0.000000");, but SL outputs -0.000000.

I think the sign bit is lost due to the denormal handling at

, and a math.copysign() may be needed if rounding to 0.0 is correct there.


llOwnerSay((string)(1.4e-45 == (float)"1.4e-45")); is optimized to llOwnerSay("0");, but SL outputs 1. I think this is also related to the above handling of denormals in string->float conversions, but I'm not sure what the correct fix would be.


llOwnerSay((string)0.00000000000001); fails to compile and an IndexError is raised at

while s[i] in u'0.':

It looks like this may be an issue with any float small enough that the string representation is 0.000[...], but is not actually equal to 0.0.

NAK causes Firestorm script compiler to error

When attempting to use the NAK constant to detect uncached notecards with llGetNotecardLineSync () in a script, LSL-PO replaces the NAK constant with a string consisting of "\n^U\n", (i.e., %0A%15%0A).

The Firestorm compiler then produces the following error:

error: generic lexer error: invalid character '\025' in input stream

when it encounters the CTRL-U.

Online version, handling

Hi Sei Lisa,
for me (windows/firefox) it would be nice if the Optimizer output would be placed inside a textarea or to have an option to get the raw optimizer output. With both variants I would be able to mark the complete output (windows: ctrl+A) and copy it to the clipboard. With the current version I have to mark the optimizer output manually (a little boring with larger scripts).

Greetings
Leona

Combined lists getting incorrectly spliced

For some reason I have only been able to reproduce it in this particular script:
https://pastebin.com/2NR7Dgam

Line 153: runMethod((string)LINK_ROOT, "got Status", 1, (list)("")+ ((list)1 + 3 + (integer)(-cost*100) + "" + 0), JSON_INVALID);

When optimized becomes: D("1", "got Status", 1, (list)1 + 3 + (integer)(-loc_cost * 100) + "" + 0, "�");

As you can see it starts with (list)1+3... when it should be (list)"" + 1 +3...

Integration with Firestorm preprocessor conditional defines

I am using the Firestorm preprocessor conditional defines quite extensively. So my workflow looks like this:

  1. Edit code in Visual Studio Code
  2. Paste it into SL and let it compile
  3. Grab the preprocessed output
  4. Run the preprocessed output through PyOptimizer
  5. Paste the preprocessed and optimized code into SL again and let it compile

It would be nice if that workflow could be reduced a bit if PyOptimize would recognize the FS conditional defines.

`integer global=other_global;` raises KeyError when ConstFold is disabled

Sometimes I disable ConstFold to maintain readability of if / else chains, but it appears that causes issues when you have an SAIdentifier on the RHS of a global's declaration.

Given the script

integer FOO=1;
integer BAR=FOO;
default {
  state_entry() {
    llOwnerSay((string)BAR);
  }
}

A llOwnerSay("1"); will be correctly emitted when ConstFold is enabled, but when it's disabled an exception is raised:

Traceback (most recent call last):
  File "/home/user/source/LSL-PyOptimizer/main.py", line 779, in <module>
    ret = main(sys.argv)
  File "/home/user/source/LSL-PyOptimizer/main.py", line 742, in main
    script = script_header + script_timestamp + outs.output(ts, options)
  File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 556, in output
    ret += self.OutCode(node)
  File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 483, in OutCode
    ret += ' = ' + self.OutExpr(child[0])
  File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 305, in OutExpr
    return self.FindName(expr)
  File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 186, in FindName
    and 'NewName' in self.symtab[node.scope][node.name]):
KeyError: 'FOO'

Seems like FOO isn't in the symbol table for its scope at the point the RHS of BAR's declaration is being handled?

Invalid signs after const folding with Optimize signs disabled.

The following code:

default
{
	link_message(integer p_val, integer notUsed1, string notUsed2, key notUsed3)
	{
		float pivot_x = 0.0;
		float pivot_y = -0.65;
		float theta = 0.01 * p_val;

		float x = pivot_x * llCos(theta) - pivot_y * llSin(theta);
		float y = pivot_x * llSin(theta) + pivot_y * llCos(theta);
		
		llOwnerSay("Pivot: " + (string)x + ", " + (string)y);
	}
}

Optimizes to:

default
{
	link_message(integer p_val, integer notUsed1, string notUsed2, key notUsed3)
	{
		float theta = 0.01 * p_val;
		float x = --0.65 * llSin(theta);
		float y = -0.65 * llCos(theta);
		llOwnerSay("Pivot: " + (string)x + ", " + (string)y);
	}
}

--0.65 is a syntax error
Using options: Constant folding, Dead code removal, but with Optimize signs disabled.

Because of pivot_x being 0, the formula collapses in to:

x = 0 - y * llSin(θ)
x = 0 - (-0.65 * sin(θ))
x = -(-0.65 * sin(θ))

but since the optimizer does not use parentheses, it becomes a -- operator.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.