Git Product home page Git Product logo

cel-go's Introduction

Common Expression Language

Go Report Card GoDoc

The Common Expression Language (CEL) is a non-Turing complete language designed for simplicity, speed, safety, and portability. CEL's C-like syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript.

// Check whether a resource name starts with a group name.
resource.name.startsWith("/groups/" + auth.claims.group)
// Determine whether the request is in the permitted time window.
request.time - resource.age < duration("24h")
// Check whether all resource names in a list match a given filter.
auth.claims.email_verified && resources.all(r, r.startsWith(auth.claims.email))

A CEL "program" is a single expression. The examples have been tagged as java, go, and typescript within the markdown to showcase the commonality of the syntax.

CEL is ideal for lightweight expression evaluation when a fully sandboxed scripting language is too resource intensive. To get started, try the Codelab.

A dashboard that shows results of cel-go conformance tests can be found here.



Overview

Determine the variables and functions you want to provide to CEL. Parse and check an expression to make sure it's valid. Then evaluate the output AST against some input. Checking is optional, but strongly encouraged.

Environment Setup

Let's expose name and group variables to CEL using the cel.Declarations environment option:

import "github.com/google/cel-go/cel"

env, err := cel.NewEnv(
    cel.Variable("name", cel.StringType),
    cel.Variable("group", cel.StringType),
)

That's it. The environment is ready to be used for parsing and type-checking. CEL supports all the usual primitive types in addition to lists, maps, as well as first-class support for JSON and Protocol Buffers.

Parse and Check

The parsing phase indicates whether the expression is syntactically valid and expands any macros present within the environment. Parsing and checking are more computationally expensive than evaluation, and it is recommended that expressions be parsed and checked ahead of time.

The parse and check phases are combined for convenience into the Compile step:

ast, issues := env.Compile(`name.startsWith("/groups/" + group)`)
if issues != nil && issues.Err() != nil {
    log.Fatalf("type-check error: %s", issues.Err())
}
prg, err := env.Program(ast)
if err != nil {
    log.Fatalf("program construction error: %s", err)
}

The cel.Program generated at the end of parse and check is stateless, thread-safe, and cachable.

Type-checking in an optional, but strongly encouraged, step that can reject some semantically invalid expressions using static analysis. Additionally, the check produces metadata which can improve function invocation performance and object field selection at evaluation-time.

Macros

Macros are optional but enabled by default. Macros were introduced to support optional CEL features that might not be desired in all use cases without the syntactic burden and complexity such features might desire if they were part of the core CEL syntax. Macros are expanded at parse time and their expansions are type-checked at check time.

For example, when macros are enabled it is possible to support bounded iteration / fold operators. The macros all, exists, exists_one, filter, and map are particularly useful for evaluating a single predicate against list and map values.

// Ensure all tweets are less than 140 chars
tweets.all(t, t.size() <= 140)

The has macro is useful for unifying field presence testing logic across protobuf types and dynamic (JSON-like) types.

// Test whether the field is a non-default value if proto-based, or defined
// in the JSON case.
has(message.field)

Both cases traditionally require special syntax at the language level, but these features are exposed via macros in CEL.

Evaluate

Now, evaluate for fun and profit. The evaluation is thread-safe and side-effect free. Many different inputs can be sent to the same cel.Program and if fields are present in the input, but not referenced in the expression, they are ignored.

// The `out` var contains the output of a successful evaluation.
// The `details' var would contain intermediate evaluation state if enabled as
// a cel.ProgramOption. This can be useful for visualizing how the `out` value
// was arrive at.
out, details, err := prg.Eval(map[string]interface{}{
    "name": "/groups/acme.co/documents/secret-stuff",
    "group": "acme.co"})
fmt.Println(out) // 'true'

Partial State

What if name hadn't been supplied? CEL is designed for this case. In distributed apps it is not uncommon to have edge caches and central services. If possible, evaluation should happen at the edge, but it isn't always possible to know the full state required for all values and functions present in the CEL expression.

To improve the odds of successful evaluation with partial state, CEL uses commutative logical operators &&, ||. If an error or unknown value (not the same thing) is encountered on the left-hand side, the right hand side is evaluated also to determine the outcome. While it is possible to implement evaluation with partial state without this feature, this method was chosen because it aligns with the semantics of SQL evaluation and because it's more robust to evaluation against dynamic data types such as JSON inputs.

In the following truth-table, the symbols <x> and <y> represent error or unknown values, with the ? indicating that the branch is not taken due to short-circuiting. When the result is <x, y> this means that the both args are possibly relevant to the result.

Expression Result
false && ? false
true && false false
<x> && false false
true && true true
true && <x> <x>
<x> && true <x>
<x> && <y> <x, y>
true || ? true
false || true true
<x> || true true
false || false false
false || <x> <x>
<x> || false <x>
<x> || <y> <x, y>

In the cases where unknowns are expected, cel.EvalOptions(cel.OptTrackState) should be enabled. The details value returned by Eval() will contain the intermediate evaluation values and can be provided to the interpreter.Prune function to generate a residual expression. e.g.:

// Residual when `name` omitted:
name.startsWith("/groups/acme.co")

This technique can be useful when there are variables that are expensive to compute unless they are absolutely needed. This functionality will be the focus of many future improvements, so keep an eye out for more goodness here!

Errors

Parse and check errors have friendly error messages with pointers to where the issues occur in source:

ERROR: <input>:1:40: undefined field 'undefined'
    | TestAllTypes{single_int32: 1, undefined: 2}
    | .......................................^`,

Both the parsed and checked expressions contain source position information about each node that appears in the output AST. This information can be used to determine error locations at evaluation time as well.

Install

CEL-Go supports modules and uses semantic versioning. For more info see the Go Modules docs.

And of course, there is always the option to build from source directly.

Common Questions

Why not JavaScript, Lua, or WASM?

JavaScript and Lua are rich languages that require sandboxing to execute safely. Sandboxing is costly and factors into the "what will I let users evaluate?" question heavily when the answer is anything more than O(n) complexity.

CEL evaluates linearly with respect to the size of the expression and the input being evaluated when macros are disabled. The only functions beyond the built-ins that may be invoked are provided by the host environment. While extension functions may be more complex, this is a choice by the application embedding CEL.

But, why not WASM? WASM is an excellent choice for certain applications and is far superior to embedded JavaScript and Lua, but it does not have support for garbage collection and non-primitive object types require semi-expensive calls across modules. In most cases CEL will be faster and just as portable for its intended use case, though for node.js and web-based execution CEL too may offer a WASM evaluator with direct to WASM compilation.

Do I need to Parse and Check?

Checking is an optional, but strongly suggested, step in CEL expression validation. It is sufficient in some cases to simply Parse and rely on the runtime bindings and error handling to do the right thing.

Where can I learn more about the language?

  • See the CEL Spec for the specification and conformance test suite.
  • Ask for support on the CEL Go Discuss Google group.

Where can I learn more about the internals?

  • See GoDoc to learn how to integrate CEL into services written in Go.
  • See the CEL C++ toolchain (under development) for information about how to integrate CEL evaluation into other environments.

How can I contribute?

Some tests don't work with go test?

A handful of tests rely on Bazel. In particular dynamic proto support at check time and the conformance test driver require Bazel to coordinate the test inputs:

bazel test ...

License

Released under the Apache License.

Disclaimer: This is not an official Google product.

cel-go's People

Contributors

alvaroaleman avatar awillis avatar ayas avatar chaopeng avatar cici37 avatar dangerontheranger avatar dependabot[bot] avatar eobrain avatar gabrielsz avatar jcking avatar jimlarson avatar jiyunyao avatar jnthntatum avatar jpbetz avatar kortschak avatar kyessenov avatar l46kok avatar livebranch avatar madi8229 avatar nicksnyder avatar notyourusualaccountname2 avatar rachelmyers avatar rblank avatar rhnvrm avatar sefk avatar shahedalmashni avatar timn avatar tristonianjones avatar ultrasaurus avatar victoriajyang avatar

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  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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cel-go's Issues

Refactor proto alias

Issue Filing Checklist

  • The change is large enough it can't be addressed with a simple
    Pull Request

Change

Refactor proto alias in .go files for cel-go

Unify error codes / messages

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request

Change

Error messages are formatted strings which are not uniform. While this was
okay for development, the errors should be consolidated into error codes
with a set of associated details. Further, when multiple errors are encountered
they should be aggregated together rather than simply picking the first one.

Proposed error codes:

Code Details Description
ATTRIBUTE_NOT_FOUND type, attribute Map or object access using an unknown attribute.
DIVIDE_BY_ZERO n/a Division by zero, also reported for modulus operations.
DUPLICATE_ATTRIBUTE attribute name Map or object construction supplies same key value more than once.
IDENTIFIER_NOT_FOUND identifier name Variable or function name could not be found.
INVALID_ARGUMENT argument value Invalid argument supplied to a function.
OVERLOAD_NOT_FOUND function signature Function defined, but no matching overload found.
TYPE_NOT_FOUND type name Type definition could not be found.

Example

return types.NewError(errors.ATTRIBUTE_NOT_FOUND, value.Type(), field)

Introduce non-strict comprehensions

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

The termination condition for all comprehensions is <loop-cond> != false in order
to ensure that when the loop-condition does not terminate on comprehensions where
the loop-step is computed from a non-strict operator (&&, ||). This can result in
inefficient computation when the loop-step is strict (literally any other operator).

To account for this difference, the parser macros should expand non-strict
loop-conditions to be a function call to a not_strictly_false(<loop-cond>) function
which returns true when the loop condition value is error, unknown, or true. The new
function will absorb errors / unknowns just like the non-strict accumulators for exists
and all macros.

Consider having a separate walk of the AST

TODO in interpreter/prune.go: 30
Consider having a separate walk of the AST that finds common subexpressions. This can be called before or after constant folding to find common subexpressions.

Support pack / unpack of `google.protobuf.Any` values

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request

Change

The CEL language definition indicates that google.protobuf.Any values are automatically
packed / unpacked to their underlying proto message during expression evaluation. At present,
the interpreter does not handle this.

Example

If msg is declared as a message containing a field any of type google.protobuf.Any, the
value of any could be any protobuf message type supported by the types.Provider and
it should be automatically converted to this underlying type

// treat msg.any as a Timestamp when it is a google.protobuf.Timestamp
msg.any < timestamp("2017-01-01T00:00:00Z")

Language design philosophy

I am trying to get familiar with cel lang in the context of opa project. Since I can't open issues against the cel-spec, I am filing it here. I am no language designer. From the readme I see that

CEL is a non-Turing complete language designed to be portable and fast. It is best suited to applications where sandboxing a full-fledged language like JavaScript or Lua would be too resource intensive, but side-effect free dynamic computations are strongly desired.

I am curious to understand why the following 2 are desired properties:

  • non-Turing complete language
  • side-effect free dynamic computations

Thanks.

Type container considered at check-time, but not at interpretation.

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

In CEL, type definitions may exist within a specific container as indicated on the
last argument of checker.Check(expr, env, <container>). The container is used to resolve
type and enum definitions. At interpretation time, the container is used to resolve enum
names, but not used in conjunction with resolving type names. This leads the checker
to indicate a program is valid, but the interpreter to fail.

Example

parsed, errors := parser.ParseText("Expr{id: 1}")

provider := types.NewProvider(&expr.Expr{})
env :=  checker.NewStandardEnv(errors, provider)
checked := checker.Check(parsed, env, "google.api.expr.v1")

i := interpreter.NewStandardInterpreter(provider)
eval := i.NewInterpretable(interpreter.NewCheckedProgram(checked, "google.api.expr.v1"))
result, _ := eval.Eval(interpreter.NewActivation(map[string]interface{}{}))
// yields: unknown type 'Expr'
// using fully-qualified names avoids the issue.

ConvertToNative does not handle proto2 primitives as pointers.

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

For proto3 generated structs, primitive values are set by value. For proto2, primitive values
are set by pointer. ConvertToNative needs to handle the change from value to pointer for
primitive values.

Example

if typeDesc.Kind() == reflect.Ptr && typeDesc.Elem().Kind() == reflect.String {
  return &string(s), nil
}

Tool to stringify ASTs

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple
    Pull Request

Change

Given an AST generate a human-readable string from it.

Example

Translate an AST of the form call('_||_', ident(a), ident(b))
into the human readable string a || b.

The translation step need not honor line-breaks or parentheses
present in the original expression, but must capture the semantic
meaning of the original.

Alternatives Considered

This functionality does not yet exist. An alternative might be to
record the original string from which the AST was generated, but
this would likely be overkill and serving both the string and AST
form of an expression in production is undesirable.

Proto message construction does not set oneof fields properly.

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

When a proto generated struct contains a oneof field, the reflection code under common/types/pb
does not appropriately account for structural gymnastics of wrapping the oneof in the correct
wrapper.

Example

The following example should create an expression with a literal value of true:

google.api.expr.v1.Expr{
   id: 1,
   literal_expr: google.api.expr.v1.Literal{
     bool_value: true}}

However, the output from the interpreter is: no such field "BoolValue"

Ensure CEL lexis is compatible with Google SQL

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

String literals are supposed in CEL are supposed to align with GoogleSQL. At present,
not all escape sequences are supported properly:

Issues

  • \xhh, \Xhh conversion of 2 hex chars to unicode code point.
  • \Uhhhhhhhh conversion of unicode escape with 8 hex chars supported incorrectly as \Uhhhh
  • r|R and b|B prefixes supported, but no support for rb for a raw byte string
  • Missing docs on standardizing all newlines to \n
  • Inconsistent treatment of standard escape patterns

Create a type registry

Create a type registry for managing type identifiers and traits for
use with type-checking and interpretation.

CEL supports primitives and message based types. There is a need
for some of these types to be treated in an abstract manner as well
as a need to annotate types as having traits to indicate they have
support operator overloads.

can't "go get" the repo

$ go get -u github.com/google/cel-go/...
package github.com/google/cel-spec/proto/checked/v1/checked: cannot find package "github.com/google/cel-spec/proto/checked/v1/checked" in any of:
	/usr/local/google/home/cbro/go/src/github.com/google/cel-spec/proto/checked/v1/checked (from $GOROOT)
	/usr/local/google/home/cbro/src/github.com/google/cel-spec/proto/checked/v1/checked (from $GOPATH)
package github.com/google/cel-spec/proto/v1/syntax: cannot find package "github.com/google/cel-spec/proto/v1/syntax" in any of:
	/usr/local/google/home/cbro/go/src/github.com/google/cel-spec/proto/v1/syntax (from $GOROOT)
	/usr/local/google/home/cbro/src/github.com/google/cel-spec/proto/v1/syntax (from $GOPATH)
package github.com/google/cel-go/server: found packages main (main.go) and server (server.go) in /usr/local/google/home/cbro/src/github.com/google/cel-go/server

Related to #51.

I know Bazel has appeal, but if it's getting in the way of Go developers actually using the package, something is wrong.

Ensure interpretation result is proto serializable

CEL evaluation may be distributed across processes, so the output of one
program may be treated as input to another. It is important for this reason
that interpretation results (valued, error, or unknown) are proto serializable.

Partial evaluation

Change

Support the evaluation of an expression with partial information.

Example

If the expression a + expensive < 10 || c >= 11 receives values for a=11
and c=10, but not expensive the output of the partial evaluation should
yield:

11 + expensive < 10 || false

The computed expression can be simplified to: expensive < -1. In order to
support such transformations, the interpreter should generate a stable (error
or non-error) or unknown value with tracking for the state observed for an
expression. The tracked state should be detailed enough to generate a new
expression or minimally detect which inputs were crucial to the computation
of the result.

Alternatives Considered

CEL could only execute expressions in serial with complete information. This
would simplify the CEL implementation and align with the expectations of
many developers.

Confusing example - how is common.Errors used and why is it passed around?

The example in the readme...

// Parse the expression and returns the accumulated errors.
p, errors := parser.ParseText("a || b && c.exists(x, x > 2)")
if len(errors.GetErrors()) != 0 {
    return nil, fmt.Error(errors.ToDisplayString()))
}

// ...
env := checker.NewStandardEnv(packages.DefaultPackage, typeProvider, errors)
// ...
if len(errors.GetErrors()) != 0 {
    return nil, fmt.Error(errors.ToDisplayString()))
}

This is quite confusing - why is the errors struct being passed around? What's the reason for returning it from the parser and passing it into the environment?

From my reading, the checker env isn't re-usable or goroutine-safe.

As a newcomer to the package, I'd expect a flow like:

  • create environment, provide type information, etc.
  • parse and check the expression against the environment
  • evaluate the expression

The current interface seems to entangle these operations.

JSON and well-known types not handled properly during type-resolution

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

When a value has a google.protobuf.Value type (or derivative), or a well-known type
such as google.protobuf.Any or google.protobuf.Duration, the type is treated as a
MESSAGE by the checker.go when it should be mapped to the appropriate built-in
or well-known type value.

Create a docgen tool

The docgen tool should generate documentation of the identifiers and functions
supported within an environment (checker/env.go). The tool should probably
generate markdown.

Switch from the checked.proto Type to the value.proto TypeValue

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request

Change

The checked.proto has a type value which is used by the checker, but not elsewhere.
In the interest of making the evaluation results serializable between check and runtime,
the checked.proto should be wrapped to ensure that either the checked or runtime proto
value can be produced from some common internal representation.

Use protobuf generated structs directly

The internal type system used within the parser and type-checker
is convenient for formatting debug strings, but using the protobuf
representations directly will simplify the implementation, provide
valuable feedback about the usability of the abstract CEL representation,
and reduce the likelihood of mismatches between the specification
and the implementation.

Remove homogeneous aggregate type restrictions

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

CEL is a dynamically typed language. As such there is no way to specify a type-designator
for an aggregate type literal, e.g. list<int>[1, 2, 3]. This means that all aggregate types defined
inline within an expression should be treated as dyn values. Currently, the type-checker asserts
type-agreement between all elements in a list or map unless expressly cast to dyn. This check
should be removed, preferring instead to identify the most general type element in the list.

Example

Currently fails to type-check: [1u, 1] but should succeed as the interpreter handles this
expression just fine.

Support `google.protobuf.Value` types as JSON

CEL supports types based on protocol buffers. These types are treated
as object that support field accesses by name. Protocol buffers also
support JSON-like data via google.protobuf.Value messages. These
messages are intended to be supported as primitives, maps, and lists
so the message field names should be abstracted away by the
type-checker and interpreter.

Update CEL.g4 to better handle arithmetic computations

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple [Pull Request(./PULL_REQUEST_TEMPLATE.md)

Change

The lexing of negative numbers makes the parsing of arithmetic expressions
less than ideal. Moving the negative number detection to the parser will address
this usability issue.

Example

The expression 4--4 is currently ambiguous without additional spaces. This should
read as call('-', '4', '-4') once the parser is changed.

Recent versions of protobuf compiler generate extra fields in go structs

Issue Filing Checklist

  • There are no issues that match the desired change
  • The change is large enough it can't be addressed with a simple Pull Request
  • If this is a bug, there are a concrete set of repro steps

Change

Recent versions of protobuf compiler generate extra fields in go structs. This causes some
tests to break. It is recommended that the new fields which appear to be for tracking internal
protobuf state, be expressly ignored by the code in common/types/pb/ directory as well as
by the object.go and provider.go files.

Example

See an example of the new fields in https://godoc.org/github.com/golang/protobuf/proto.

To reproduce a test failure, update WORKSPACE to use https://github.com/bazelbuild/rules_go
version 0.12.0 or later and run all tests with

$ bazel test ...
and you'll see in the logs for common/types/go_default_test

--- FAIL: TestProtoObject_Iterator (0.00s)
        object_test.go:51: 
        Got [id comprehension_expr XXX_NoUnkeyedLiteral XXX_unrecognized XXX_sizecache], 
        wanted [id comprehension_expr]
FAIL

Alternatives Considered

One option is to rely on the extra fields being prefixed with XXX_ (hooray for in-band signaling!).
This might be simplest, but is fragile, since it seems to be an undocumented convention.

Another option is to eschew Go-level reflection and rely on the reflection in the proto library for
accessing proper fields names in a Message.

Improve protobuf type support at type-check

Change

The interpreter and type-checker have different notions of which types
are supported. The interpreter is quite good at resolving types from a
protobuf file descriptor, but the type-checker only looks at types that have
been provided to the InMemoryTypeProvider. A unified type provider
would improve support for resolving enums at type-check time while
simplifying the type provision implementation.

Example

// checker setup.
typeProvider := providers.NewTypeProvider(<proto-instances>)
env := checker.NewStandardEnv(common.NewErrors(), typeProvider)
c, errors := checker.Check(<expr>, env, <container>)

// interpreter setup referring to type provider and checked output `c`
i := interpreter.NewStandardInterpreter(typeProvider)
eval := i.NewInterpretable(interpreter.NewCheckedProgram(c, ""))

Alternatives Considered

The type provision could remain separate between the type-checker and
interpreter as they have slightly different use cases. One really cares about
whether a field exists on a type (checker), and another really care about
how to construct a type (interpreter). However, as these concepts are heavily
related, it would be useful to combine them.

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.