Coder Social home page Coder Social logo

will62794 / tla-web Goto Github PK

View Code? Open in Web Editor NEW
64.0 6.0 7.0 25.96 MB

Interactive, web-based environment for exploring TLA+ specifications.

License: MIT License

HTML 0.94% JavaScript 42.61% TLA 54.27% Shell 0.08% Makefile 0.01% CSS 2.06% C 0.02%
tlaplus verification model-checking tla

tla-web's Issues

Use fields instead of indexes to address children

Super impressed with this! Just letting you know in case you weren't aware - you can use fields instead of indexes to address specific child nodes. I admit I've been pretty lazy about adding fields to the grammar because they aren't very useful in highlighting, but if this would be useful to you I would be happy to add the missing ones according to whatever scheme is most helpful.

Operators from standard modules do not work

---- MODULE Issue7 ----
EXTENDS TLC, Naturals, FiniteSets

VARIABLES x

Init == x = {}

Next == x' = Cardinality(x)

====
---- MODULE Issue7 ----
EXTENDS TLC, Naturals, Sequences

VARIABLES x

Init == x = <<>>

Next == x' = Len(x)

====
TypeError: Cannot read properties of undefined (reading 'length')
    at eval.js:900:40
    at lodash.min.js:98:241
    at lodash.min.js:54:66
    at ue (lodash.min.js:35:327)
    at Function.Kc [as mapValues] (lodash.min.js:98:213)
    at evalEq (eval.js:896:30)
    at evalBoundInfix (eval.js:1025:16)
    at evalBoundInfix (eval.js:2079:15)
    at evalExpr (eval.js:1598:16)
    at evalExpr (eval.js:2070:15)

Incomplete set of initial states

With e.g. EWD998 to N == 2 and Init!3 to /\ color \in [Node -> {"white"}], there should be four initial states, but there are only three.

Screen Shot 2022-07-07 at 9 38 32 PM

Variables values undefined when operator is primed

See undefined value of variable terminationDetected and priming of terminated operator:

Screen Shot 2022-07-12 at 7 41 26 AM

---------------------- MODULE AsyncTerminationDetection ---------------------
EXTENDS Naturals

N == 2

ASSUME NIsPosNat == N \in Nat \ {0}

Node == 0 .. N-1

VARIABLES 
  active,               \* activation status of nodes
  pending,              \* number of messages pending at a node
  terminationDetected

vars == << active, pending, terminationDetected >>

terminated == \A n \in Node : ~ active[n] /\ pending[n] = 0

-----------------------------------------------------------------------------

Init ==
    /\ active \in [ Node -> BOOLEAN ]
    /\ pending \in [ Node -> 0..1 ]
    /\ terminationDetected \in {FALSE, terminated}

Terminate(i) ==
    /\ active' \in { f \in [ Node -> BOOLEAN] : \A n \in Node: ~active[n] => ~f[n] }
    /\ pending' = pending
    /\ terminationDetected' \in {terminationDetected, terminated'}

SendMsg(i, j) ==
    /\ active[i]
    /\ pending' = [pending EXCEPT ![j] = @ + 1]
    /\ UNCHANGED << active, terminationDetected >>

Wakeup(i) ==
    /\ pending[i] > 0
    /\ active' = [active EXCEPT ![i] = TRUE]
    /\ pending' = [pending EXCEPT ![i] = @ - 1]
    /\ UNCHANGED << terminationDetected >>

DetectTermination ==
    /\ terminated
    /\ ~terminationDetected
    /\ terminationDetected' = TRUE
    /\ UNCHANGED << active, pending >>

-----------------------------------------------------------------------------

Next ==
    \/ DetectTermination
    \/ \E i \in Node, j \in Node:   
        \/ Terminate(i)
        \/ Wakeup(i)
        \/ SendMsg(i, j)
=============================================================================

Priority of :> not handled correctly

Hi, first off thanks for the project, this is very nice! However, there seems to be a bug in the interpreter, in handling the priority of the :> operator. It gets confused by this spec:

---- MODULE Web ----
EXTENDS TLC

VARIABLES
    x,
    y

Init ==
    /\ x = 0 :> 0
    /\ y = 1 :> 1

Next == UNCHANGED <<x, y>>

====

But this works:

---- MODULE Web ----
EXTENDS TLC

VARIABLES
    x,
    y

Init ==
    /\ x = (0 :> 0)
    /\ y = (1 :> 1)

Next == UNCHANGED <<x, y>>

====

Parse error if Next is defined on one line

This module causes a parse error, although it's correct syntax:

------------------------------- MODULE Conjunct ----------------------------- 
EXTENDS TLC, Naturals

VARIABLES x

Action1 == 1 = 2 /\ <<>>[0]
Action2 == x' = x + 1

Next == Action1 \/ Action 2
Init == x = 176
=============================================================================

Replace the Next definition thus to allow parsing:

Next ==
  \/ Action1
  \/ Action 2

The first module should parse identically.

Handle model values

Decide how to handle "model values" for instantiation of CONSTANT parameters. Currently, it is expected that string values are to be used in place of model values.

Short-circuit evaluation in conjunctions

Repro for disjunct: type TRUE \/ <<"a">>[2] = "b" in the REPL.

Expected: result TRUE, the second half isn't evaluated.

Actual: no result, "swallowing parse errors during repl evaluation" is logged in the console. The REPL tried to interpret the second half and failed with an index error.

Repro for conjunct: type FALSE /\ <<>>[1] = "a" in the REPL.

Expected: result FALSE, the second half isn't evaluated.

Actual: no result, "swallowing parse errors during repl evaluation" is logged in the console. The REPL tried to interpret the second half and failed with an index error.

Can I embed this in another website?

  1. This is amazingly cool, thank you for making this.
  2. If possible, I think this would be a great addition to learntla. Is it something I can embed in another website? If so, how? No worries if this is impossible or would take too long to explain!

UNCHANGED vars disjunct

---- MODULE lockserver_nodefs ----
EXTENDS TLC, Naturals

Server == {"s1", "s2"}
Client == {"c1", "c2"}

VARIABLE semaphore
VARIABLE clientlocks
vars==<<semaphore, clientlocks>>

TypeOK ==
    /\ semaphore \in [Server -> {TRUE, FALSE}]
    /\ clientlocks \in [Client -> SUBSET Server]

\* Initially each server holds its lock, and all clients hold no locks.
Init == 
    /\ semaphore = [i \in Server |-> TRUE]
    /\ clientlocks = [i \in Client |-> {}]

Next == 
    \/ \E c \in Client, s \in Server : 
        /\ semaphore[s] = TRUE
        /\ clientlocks' = [clientlocks EXCEPT ![c] = clientlocks[c] \cup {s}]
        /\ semaphore' = [semaphore EXCEPT ![s] = FALSE]
    \/ \E c \in Client, s \in Server : 
        /\ s \in clientlocks[c]
        /\ clientlocks' = [clientlocks EXCEPT ![c] = clientlocks[c] \ {s}]
        /\ semaphore' = [semaphore EXCEPT ![s] = TRUE]
    \/ UNCHANGED vars

====
TypeError: Cannot read properties of null (reading 'fingerprint')
    at eval.js:412:45
    at Array.map (<anonymous>)
    at FcnRcdValue.fingerprint (eval.js:412:34)
    at TupleValue.fingerprint (eval.js:279:20)
    at evalEq (eval.js:1217:32)
    at evalBoundInfix (eval.js:1338:16)
    at evalBoundInfix (eval.js:2530:15)
    at evalExpr (eval.js:2075:16)
    at evalExpr (eval.js:2517:15)
    at evalExpr (eval.js:2066:16)

The problem appears to be UNCHANGED vars, whereas the equivalent formula UNCHANGED <<semaphore, clientlocks>> works fine.

Folds not supported

I'd like to use folds over sequences, but they don't seem to be supported. See an example below. I assume that recursive definitions are tricky to add? Could you suggest a workaround? This is at the moment a bit of a blocker for me - I'd like to use tla-web as "interactive docs" for some of the subsystems I've modeled in TLA, but the models do rely on folds.

---- MODULE Web ----

\* The following two definitions are copied verbatim from the community modules
MapThenFoldSet(op(_,_), base, f(_), choose(_), S) ==
  LET iter[s \in SUBSET S] ==
        IF s = {} THEN base
        ELSE LET x == choose(s)
             IN  op(f(x), iter[s \ {x}])
  IN  iter[S]

FoldLeft(op(_, _), base, seq) == 
  MapThenFoldSet(LAMBDA x,y : op(y,x), base,
                 LAMBDA i : seq[i],
                 LAMBDA S : Max(S),
                 DOMAIN seq)


VARIABLE
    seq,
    val

Init ==
    /\ seq = << 1, 2, 3 >>
    /\ val = FoldLeft(LAMBDA x, y: x + y, 0, seq)

Next == UNCHANGED << seq, val >>
   
====

Support module semantics

For the use cases I envision, I was not planning to implement full blown semantics of modules and module imports e.g. EXTENDS, etc. For example, for now I'm just considering operators from the standard modules to be globally available at all times, even without being explicitly imported. This behavior could be re-considered in the future, though, if necessary.

Can't use EXCEPT on sequences, or Append on sequence-domain functions

Sometimes, you need to "reassign" part of a sequence in your next-state relation.

Here's a minimal example of what I mean (this spec does not work):

---- MODULE test ----
EXTENDS Naturals

VARIABLE seq

Init ==
    /\ seq = <<1, 2, 3>>

Next ==
    /\ seq' = [seq EXCEPT ![1] = IF @ = 5 THEN 1 ELSE @ + 1]

Spec == Init /\ [][Next]_seq

====

This doesn't work currently, because I imagine EXCEPT's implementation asserts that it was given a function or record.
On its own, it would be nice if this worked. I think, however, that this exposes a slightly more fundamental things that could be addressed.

The problem is that it doesn't work the "other way round" either. I can sort of work around the issue like this (this spec works):

---- MODULE test ----
EXTENDS Naturals

VARIABLE seq

Init ==
    /\ seq = <<1, 2, 3>>

Next ==
    /\ seq' = [ i \in DOMAIN seq |->
    	IF i = 1 THEN
        	IF seq[1] = 5 THEN 1 ELSE seq[1] + 1
        ELSE seq[i]]

Spec == Init /\ [][Next]_seq

====

The problem, then, becomes that I can't go back to using seq like a sequence. If I have a spec that uses both EXCEPT and Append on the same value in alternation, then that itself becomes a problem. See this last example, which also does not work:

---- MODULE test ----
EXTENDS Naturals, Sequences

VARIABLE seq

Init ==
    /\ seq = <<1, 2, 3>>

Inc ==
    /\ seq' = [ i \in DOMAIN seq |->
    	IF i = 1 THEN
        	IF seq[1] = 5 THEN 1 ELSE seq[1] + 1
        ELSE seq[i]]

App ==
	/\ seq' = Append(seq, seq[1])

Next ==
	\/ Inc
    \/ App

Spec == Init /\ [][Next]_seq

====

I guess, ultimately, it would be very nice if <<0, 0, 0>> = [i \in DOMAIN {1, 2, 3} |-> 0] (or, enough implementation edge cases to make this true for the user, at least).

Let me know if you'd like more test cases along this theme - I can probably come up with a couple more.

Feature Request: ALIAS

Hi - I'm Finn, a Microsoft Research intern working with @lemmy on some TLA+ specifications. You'll have seen me CC'd in one or two other issues. This issue is on the same theme, but now I'm closer to the polishing stage and have a bit more leeway to put effort into e.g tech for visualizing our work.
I'm trying to make this issue as complete as possible, so apologies if I re-state something you already know about.

When exploring more complex specifications, sometimes a feature of interest will not directly be in the state space. When in this situation, TLC offers the option (called ALIAS) to override the way it displays state, such that other features can be shown alongside / instead of the raw state variables.

Here's a heavily abridged fragment of something I'm working on that would benefit from this (I made sure this version works in the current explorer):

---- MODULE Example ----
EXTENDS Naturals, Sequences

VARIABLE log, idx

Init ==
    /\ log = <<>>
    /\ idx = 0

IncIdx ==
    /\ idx' = idx + 1
    /\ UNCHANGED log

JumpIdx ==
    /\ idx > 0
    /\ idx' \in 0..Len(log)
    /\ UNCHANGED log

GrowLog ==
    /\ log' = Append(log, idx)
    /\ UNCHANGED idx

Read ==
    IF   idx < Len(log)
    THEN {log[idx]}
    ELSE {}

Next ==
    \/ IncIdx
    \/ JumpIdx
    \/ GrowLog

Spec == Init /\ [][Next]_<<log, idx>>

Alias == [
    log |-> log,
    idx |-> idx,
    Read |-> Read
]

====

The take-away is that Read is derivable from any given state, but it might hard to calculate on the spot for humans, so having it spelled out in the visuals would improve the exploring experience for specs like this one. In particular, forcing someone unfamiliar with a spec to run one or more operator definitions in their heads as they try to understand a spec would be ... headache inducing.

From my perspective, an MVP implementation would be to look for an operator called Alias and, if it's there, assume it will yield a function associating a "virtual" set of state variables with a set of values to display at each state.

I have some cycles available to help develop such a thing, if that's of interest. Let me know if I should go and try to put together a PR.

Add ability to explode/split trace view on a specified constant set

It is often helpful to view the state variables in a trace by process (e.g. nodes, servers, etc.). Adding the ability to explode/split the trace on an arbitrary constant set could be one approach to this i.e. instead of the viewer displaying a single trace, a separate trace is shown for each process.

Quantification over two or more variables not supported

---- MODULE Issue ----
EXTENDS Naturals

VARIABLE x, y

Init == 
    /\ x = 0
    /\ y = 0

Next == 
    \E i,j \in 1..3:
    	/\ x' = i
        /\ y' = j
====

Workaround:

---- MODULE Issue ----
EXTENDS Naturals

VARIABLE x, y

Init == 
    /\ x = 0
    /\ y = 0

Next == 
    \E i \in 1..3, j \in 1..3:
    	/\ x' = i
        /\ y' = j
====

Show action details on hover in next state selection

When hovering over an action name in the next-state selection pane, it would be helpful if additional details of that action were shown e.g., the code for that action and/or structured details about the specific set of enabled guards, modified variables, etc.

@muratdem

Four boolean values: TRUE, FALSE, false, true

See values of variable terminationDetected:

Screen Shot 2022-07-12 at 7 37 33 AM

---------------------- MODULE AsyncTerminationDetection ---------------------
EXTENDS Naturals

N == 2

ASSUME NIsPosNat == N \in Nat \ {0}

Node == 0 .. N-1

VARIABLES 
  active,               \* activation status of nodes
  pending,              \* number of messages pending at a node
  terminationDetected

vars == << active, pending, terminationDetected >>

terminated == \A n \in Node : ~ active[n] /\ pending[n] = 0

-----------------------------------------------------------------------------

Init ==
    /\ active \in [ Node -> BOOLEAN ]
    /\ pending \in [ Node -> 0..1 ]
    /\ terminationDetected \in {FALSE, terminated}

Next ==
	UNCHANGED vars

====

LAMBDA expressions

---- MODULE Issue123 ----

Op(l(_), v) ==
	l(v)

VARIABLE x

Init ==
    x = FALSE
    
Next ==
    x' = Op(LAMBDA v: ~v, x)

Spec == Init /\ [][Next]_x

====
TypeError: Cannot read properties of undefined (reading 'val')
    at evalBoundOp (eval.js:1788:71)
    at evalExpr (eval.js:2070:16)
    at evalExpr (eval.js:2517:15)
    at eval.js:1187:31
    at lodash.min.js:98:241
    at lodash.min.js:54:66
    at ue (lodash.min.js:35:327)
    at Function.Kc [as mapValues] (lodash.min.js:98:213)
    at evalEq (eval.js:1184:30)
    at evalBoundInfix (eval.js:1338:16)

Explicitly defined op doesn't work either:

---- MODULE Issue123 ----
EXTENDS Naturals

Neg(v) ==
   v

Op(l(_), v) ==
	l(v)

VARIABLE x

Init ==
	x = FALSE
    
Next ==
    x' = Op(Neg, x)

Spec == Init /\ [][Next]_x

====

Bogus "tx_id" doesn't exist in function domain.

The following spec works fine in TLC, but fails exploration with tla-web with:

Error: argument "tx_id" doesn't exist in function domain.
    at assert (eval.js:39:15)
    at FcnRcdValue.applyArg (eval.js:422:9)
    at evalExpr (eval.js:3679:31)
    at evalExpr (eval.js:4000:16)
    at evalExpr (eval.js:3462:17)
    at evalExpr (eval.js:4000:16)
    at evalLetIn (eval.js:3161:26)
    at evalExpr (eval.js:3400:16)
    at evalExpr (eval.js:4000:16)
    at eval.js:2692:19

The source of the error appears to be StatusCommittedResponseAction.

---- MODULE SingleNode ----

EXTENDS Naturals, Sequences, SequencesExt

RwTxRequest == "RwTxRequest"
RwTxResponse == "RwTxResponse"
RoTxRequest == "RoTxRequest"
RoTxResponse == "RoTxResponse"
TxStatusReceived == "TxStatusReceived"

CommittedStatus == "CommittedStatus"
InvalidStatus == "InvalidStatus"
TxStatuses == {
    CommittedStatus,
    InvalidStatus
    }

FirstBranch == 1

Views == Nat

\* Sequence numbers start at 1, 0 is used a null value
SeqNums == Nat

\* TxIDs consist of a view and sequence number and thus start at (1,1)
TxIDs == Views \X SeqNums

\* This models uses a dummy application where read-write transactions 
\* append an integer to a list and then reads the list
\* Read-only transactions simply read the list
Txs == Nat

VARIABLES history

HistoryTypeOK ==
    \A i \in DOMAIN history:
        \/  /\ history[i].type \in {RwTxRequest, RoTxRequest}
            /\ history[i].tx \in Txs
        \/  /\ history[i].type \in {RwTxResponse, RoTxResponse}
            /\ history[i].tx \in Txs
            /\ history[i].observed \in Seq(Txs)
            /\ history[i].tx_id \in TxIDs
        \/  /\ history[i].type = TxStatusReceived
            /\ history[i].tx_id \in TxIDs
            /\ history[i].status \in TxStatuses

\* Abstract ledgers that contains only client transactions (no signatures)
\* Indexed by view, each ledger is the ledger associated with leader of that view 
\* In practice, the ledger of every CCF node is one of these or a prefix for one of these
\* This could be switched to a tree which can represent forks more elegantly
VARIABLES ledgerBranches

LedgerTypeOK ==
    \A view \in DOMAIN ledgerBranches:
        \A seqnum \in DOMAIN ledgerBranches[view]:
            \* Each ledger entry is tuple containing a view and tx
            \* The ledger entry index is the sequence number
            /\ ledgerBranches[view][seqnum].view \in Views
            /\ "tx" \in DOMAIN ledgerBranches[view][seqnum] => ledgerBranches[view][seqnum].tx \in Txs

\* In this abstract version of CCF's consensus layer, each ledger is append-only
LedgersMonoProp ==
    [][\A view \in DOMAIN ledgerBranches: IsPrefix(ledgerBranches[view], ledgerBranches'[view])]_ledgerBranches

vars == << history, ledgerBranches >>

TypeOK ==
    /\ HistoryTypeOK
    /\ LedgerTypeOK

Init ==
    /\ history = <<>>
    /\ ledgerBranches = [ x \in 1..FirstBranch |-> <<>>]

IndexOfLastRequested ==
    SelectLastInSeq(history, LAMBDA e : e.type \in {RwTxRequest, RoTxRequest})

NextRequestId ==
    IF IndexOfLastRequested = 0 THEN 0 ELSE history[IndexOfLastRequested].tx+1

\* Submit new read-write transaction
\* This could be extended to add a notion of session and then check for session consistency
RwTxRequestAction ==
    /\ history' = Append(
        history, 
        [type |-> RwTxRequest, tx |-> NextRequestId]
        )
    /\ UNCHANGED ledgerBranches

\* Execute a read-write transaction
RwTxExecuteAction ==
    /\ \E i \in DOMAIN history :
        /\ history[i].type = RwTxRequest
        \* Check transaction has not already been added to a ledger
        /\ \A view \in DOMAIN ledgerBranches: 
            \A seqnum \in DOMAIN ledgerBranches[view]: 
                "tx" \in DOMAIN ledgerBranches[view][seqnum]
                => history[i].tx /= ledgerBranches[view][seqnum].tx
        \* Note that a transaction can be added to any ledger, simulating the fact
        \* that it can be picked up by the current leader or any former leader
        /\ \E view \in FirstBranch..Len(ledgerBranches):
                ledgerBranches' = [ledgerBranches EXCEPT ![view] = 
                    Append(@,[view |-> view, tx |-> history[i].tx])]
        /\ UNCHANGED history

LedgerBranchTxOnly(branch) ==
    LET SubBranch == SelectSeq(branch, LAMBDA e : "tx" \in DOMAIN e)
    IN [i \in DOMAIN SubBranch |-> SubBranch[i].tx]

\* Response to a read-write transaction
RwTxResponseAction ==
    /\ \E i \in DOMAIN history :
        \* Check request has been received and executed but not yet responded to
        /\ history[i].type = RwTxRequest
        /\ {j \in DOMAIN history: 
            /\ j > i 
            /\ history[j].type = RwTxResponse
            /\ history[j].tx = history[i].tx} = {}
        /\ \E view \in FirstBranch..Len(ledgerBranches):
            /\ \E seqnum \in DOMAIN ledgerBranches[view]: 
                /\ "tx" \in DOMAIN ledgerBranches[view][seqnum]
                /\ history[i].tx = ledgerBranches[view][seqnum].tx
                /\ history' = Append(
                    history,[
                        type |-> RwTxResponse, 
                        tx |-> history[i].tx, 
                        observed |-> LedgerBranchTxOnly(SubSeq(ledgerBranches[view],1,seqnum)),
                        tx_id |-> <<ledgerBranches[view][seqnum].view, seqnum>>] )
    /\ UNCHANGED ledgerBranches

\* Sending a committed status message
\* Note that a request could only be committed if it's in the highest view's ledger
StatusCommittedResponseAction ==
    /\ \E i \in DOMAIN history :
        LET view == history[i].tx_id[1]
            seqno == history[i].tx_id[2]
        IN /\ history[i].type = RwTxResponse
           /\ Len(Last(ledgerBranches)) >= seqno
           /\ Last(ledgerBranches)[seqno].view = view
           \* There is no future InvalidStatus that's incompatible with this commit
           \* This is to accomodate StatusInvalidResponseAction making future commits invalid,
           \* and is an unnecessary complication for model checking. It does facilitate trace
           \* validation though, by allowing immediate processing of Invalids without
           \* needing to wait for the commit history knowledge to catch up.
           /\ \lnot \E j \in DOMAIN history:
                /\ history[j].type = TxStatusReceived
                /\ history[j].status = InvalidStatus
                /\ history[j].tx_id[1] = view
                /\ history[j].tx_id[2] <= seqno
           \* Reply
           /\ history' = Append(
              history,[
                type |-> TxStatusReceived, 
                tx_id |-> history[i].tx_id,
                status |-> CommittedStatus]
              )
    /\ UNCHANGED ledgerBranches

\* Append a transaction to the ledger which does not impact the state we are considering
AppendOtherTxnAction ==
    /\ \E view \in FirstBranch..Len(ledgerBranches):
        ledgerBranches' = [ledgerBranches EXCEPT ![view] = 
                    Append(@,[view |-> view])]
    /\ UNCHANGED history

\* A CCF service with a single node will never have a view change
\* so the log will never be rolled back and thus transaction IDs cannot be invalid
Next ==
    \/ RwTxRequestAction
    \/ RwTxExecuteAction
    \/ RwTxResponseAction
    \/ StatusCommittedResponseAction
    \/ AppendOtherTxnAction
====

/cc @heidihoward @achamayou

Lasso detection rules out behaviors

-------------------------------- MODULE S -------------------------------
VARIABLES p

Init == p = FALSE

Next == p' \in BOOLEAN
============================================================================

The evaluator won't let me generate a behavior such as F, T, T, F, ... The moment when two consecutive states are equivalent, the back to state marker is added, causing, e.g., this example to become F, T, T, T, T, T, ....

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.