Coder Social home page Coder Social logo

string-cache's Introduction

string-cache

Build Status

Documentation

A string interning library for Rust, developed as part of the Servo project.

Simple usage

In Cargo.toml:

[dependencies]
string_cache = "0.8"

In lib.rs:

extern crate string_cache;
use string_cache::DefaultAtom as Atom;

With static atoms

In Cargo.toml:

[package]
build = "build.rs"

[dependencies]
string_cache = "0.8"

[build-dependencies]
string_cache_codegen = "0.5"

In build.rs:

extern crate string_cache_codegen;

use std::env;
use std::path::Path;

fn main() {
    string_cache_codegen::AtomType::new("foo::FooAtom", "foo_atom!")
        .atoms(&["foo", "bar"])
        .write_to_file(&Path::new(&env::var("OUT_DIR").unwrap()).join("foo_atom.rs"))
        .unwrap()
}

In lib.rs:

extern crate string_cache;

mod foo {
    include!(concat!(env!("OUT_DIR"), "/foo_atom.rs"));
}

The generated code will define a FooAtom type and a foo_atom! macro. The macro can be used in expression or patterns, with strings listed in build.rs. For example:

fn compute_something(input: &foo::FooAtom) -> u32 {
    match *input {
        foo_atom!("foo") => 1,
        foo_atom!("bar") => 2,
        _ => 3,
    }
}

string-cache's People

Contributors

aidanhs avatar akosthekiss avatar atouchet avatar bors-servo avatar creativcoder avatar dsherret avatar dzbarsky avatar eijebong avatar emilio avatar frewsxcv avatar glennw avatar gw3583 avatar hcpl avatar jdm avatar kamilaborowska avatar kichjang avatar kmcallister avatar lucretiel avatar manishearth avatar mbrubeck avatar metajack avatar mrobinson avatar ms2ger avatar notriddle avatar nox avatar paulrouget avatar pcwalton avatar simonsapin avatar thekk avatar yonifeng 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

string-cache's Issues

Slim down crate by removing build script and build-deps

I noticed this crate has a build script which is only used to generate files for the tests, but not for the main library. This is unfortunate, as this pulls in a lot of heavy dependencies for anybody not using string_cache_codegen.

The reason is that the build script uses string_cache_codegen, therefore this is also a build dependency of this crate. Transitively this also pulls in all of phf_generator and old versions of rand. It would be great if this build dependency could be removed and the tests implemented somehow else.

Add "associated data" to support namespaces

The conceptual model for Atom becomes

struct Atom<T = ()> {
    string: String,
    assoc: Option<T>,
}

Any atom where assoc.is_some() is interned in the dynamic table. html5ever and Servo will use this to store namespace and prefix (see #26), with None representing the HTML namespace and an empty prefix. This shrinks a qualified name to a single word, without any performance penalty to HTML names.

One complication is that we need two different notions of equality on this associated data. An XML document can contain nodes which use different prefixes to produce the same qualified name. We can't combine these in the interning table, because it's possible to read the original prefix out of the DOM. But when we're comparing atoms for equality we need to ignore the prefix.

Also, the global interning table needs to be aware of the type T somehow. We could have a set of tables indexed by the type T, hopefully resolving the polymorphism at compile time. (It should really be more like the entire crate is parametrized on T.) In C++ I would use a static member variable in a class template, but I don't know of any analogous mechanism in Rust (I checked and static muts inside generic functions get combined.)

New version

I added the ability to add docs to generated code that another PR relies on. Any chance of a version bump to unblock that PR?

May we have a comment explaining why the library implements its own fixed length hashmap?

around about here

buckets: [Option<Box<StringCacheEntry>>; NB_BUCKETS],

I'm mostly just curious. I wonder if it would be worth considering using a parallel hashmap? In my own project, I'm eventually going to need to intern things other than strings. Is there a reason the generic interner from the syntax crate wasn't appropriate for servo's strings? Could the generic interner be improved to the point of being suitable?

Did not compile with latest rustc

root@test3:/rust/string-cache# make
rustc -L ../../phf/rust-phf /root/rust/string-cache/macros/src/lib.rs --out-dir .
/root/rust/string-cache/macros/src/lib.rs:13:50: 13:63 error: feature has been removed
/root/rust/string-cache/macros/src/lib.rs:13 #![feature(macro_rules, plugin_registrar, quote, managed_boxes, phase)]
^~~~~~~~~~~~~
error: aborting due to previous error
make: *** [libstring-cache.dummy] Error 101
root@test3:
/rust/string-cache# rustc -v
rustc 0.12.0-nightly (136ab3c6b 2014-10-04 00:17:04 +0000)

Prefer inline representation over static?

Currently, static representation takes priority over inline.

For example, Atom::from("bananas") performs the following operations:

  • Calculate hash of "bananas".
  • Calculate index in static set (several arithmetic operations).
  • String comparison of "bananas" and the string in the static set at that index.

If "bananas" is not in the static set, it falls back to inline representation.

Is there a reason why strings of 7 bytes or less don't skip all the above, and go direct to using inline representation?

As far as I can see, inline representation is much cheaper, and which representation takes priority doesn't matter for the rest of Atom's implementation - as long as it's consistent.

Short strings could still be input to the codegen so they can be used in atom!(), but codegen would not add them to the static set, and instead generate pack_inline() calls (instead of pack_static()).

I can't see any downside to this, and imagine it could be significantly more performant for use cases where there are lots of short strings in use. But am I missing something?

If you think this may be worth looking into, I'd be happy to implement and benchmark.

atom::tests::repr fails on armv7hl and s390x

armv7hl

---- atom::tests::repr stdout ----
thread 'atom::tests::repr' panicked at 'assertion failed: `(left == right)`
  left: `0`,
 right: `8`', src/atom.rs:802:9
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
failures:
    atom::tests::repr

s390x

---- atom::tests::repr stdout ----
thread 'atom::tests::repr' panicked at 'assertion failed: 0x6500000000000011 != 0x0000000000006511', src/atom.rs:777:13
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
failures:
    atom::tests::repr

s390x is Big-Endian

Move out the list of static strings?

Currently, this repository contains an hard-coded list of static strings that is tailored for Servo. Would it be possible to move this list out of string-cache so that Servo and other potential users can maintain their own version of it? Perhaps by making Atom generic over a type with a trait that provides the list as an associated static item.

UB detected by miri

Hi. I'm author of the swc project and swc is using this library.

While trying to run tests with miri, miri detected UB


error: Undefined Behavior: trying to retag from <wildcard> for SharedReadOnly permission at alloc99614[0x0], but no exposed tags have suitable permission in the borrow stack for this location
   --> /Users/kdy1/.cargo/registry/src/github.com-1ecc6299db9ec823/string_cache-0.8.4/src/atom.rs:256:21
    |
256 |                     &(*entry).string
    |                     ^^^^^^^^^^^^^^^^
    |                     |
    |                     trying to retag from <wildcard> for SharedReadOnly permission at alloc99614[0x0], but no exposed tags have suitable permission in the borrow stack for this location
    |                     this error occurs as part of retag at alloc99614[0x0..0x10]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
    = note: BACKTRACE:
    = note: inside `<string_cache::atom::Atom<swc_atoms::JsWordStaticSet> as std::ops::Deref>::deref` at /Users/kdy1/.cargo/registry/src/github.com-1ecc6299db9ec823/string_cache-0.8.4/src/atom.rs:256:21
note: inside `tests::access_tag` at crates/jsdoc/src/lib.rs:710:21

My rust-toolchain is

nightly-2022-09-14

and I ran cargo miri test from the root of swc repository.

I'm not sure, but I guess this can be related to segfault mentioned in #33

Remove the plugin?

The string_cache_plugin crate provides atom! and ns! syntax extension that turns static atoms into their interned representation at compile time. In #95 I’ve introduced an alternative: the string_cache crate has a build script that generates a giant (couple thousand lines) macro_rules! macro:

https://gist.github.com/anonymous/1f4638851d6e3c8f1975#file-ns_atom_macros_without_plugin-rs-L326

It’s ugly, but it runs on stable Rust while the plugin breaks and needs to be updated when compile internals change.

I’m tempted to remove the plugin and change every downstream user to use the macro. Or is there still a reason to prefer the plugin? CC @Manishearth @pcwalton @frewsxcv

0.7.4 no longer builds with rustc 1.34.2

0.7.4 no longer builds with rustc 1.34.2, notably distributed with Debian Buster:

error[E0658]: use of unstable library feature 'maybe_uninit' (see issue #53491)
   --> /home/teythoon/.cargo/registry/src/github.com-1ecc6299db9ec823/string_cache-0.7.4/src/atom.rs:506:26
    |
506 |         let mut buffer = mem::MaybeUninit::<[u8; 64]>::uninit();
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0599]: no function or associated item named `uninit` found for type `std::mem::MaybeUninit<[u8; 64]>` in the current scope
   --> /home/teythoon/.cargo/registry/src/github.com-1ecc6299db9ec823/string_cache-0.7.4/src/atom.rs:506:56
    |
506 |         let mut buffer = mem::MaybeUninit::<[u8; 64]>::uninit();
    |                          ------------------------------^^^^^^
    |                          |
    |                          function or associated item not found in `std::mem::MaybeUninit<[u8; 64]>`

error: aborting due to 2 previous errors

This is annoying, because a patch-level update introduced a dependency on a newer compiler, creating problems with the cargo update and cargo publish workflows for anyone using Debian Buster. Please publish a version 0.7.5 with the offending change reverted.

Add a copiable atom type

The Atom type is not Copy (from what I understand of the source code, because of the need to drop unused atoms), which make it not as usable as it could be.

In a lot of projects (like on-off CLI utilities, etc), we don't really care about dropping unused atoms, so we don't actually need the Drop implementation and Atom could in principle implement Copy.

Currently, I use my own type, which is a very hacky implementation of this idea:

//RawAtom is the type generated by string_cache
use interning_raw::RawAtom as RawAtom;
use std::ops::Deref;
use std;

//A struct without the PhantomData field, so that we can implement Copy
#[derive(Copy, Clone)]
pub struct Atom {
	pub unsafe_data: u64
}

impl Deref for Atom {
	type Target = str;

	fn deref(&self) -> &Self::Target {
		assert_eq!(std::mem::size_of::<Atom>(), std::mem::size_of::<RawAtom>());
		//Horribly unsafe, but it *should* work,
		//as the second field of RawAtom is zero-sized
		//and shouldn't affect the in-memory representation
		unsafe {
			std::mem::transmute::<&Atom, &RawAtom>(self)
		}.deref()
	}
}

impl<'a> From<&'a str> for Atom {
	fn from(val: &str) -> Atom {
		let raw: RawAtom = val.into();
		let atom = Atom { unsafe_data: raw.unsafe_data };

		//We leak the RawAtom, so that its (potential) storage isn't deallocated
		std::mem::forget(raw);
		atom
	}
}

//Implementations of Eq, Hash, etc which mirror those in string_cache::atom

Obviously, it's not ideal:

  • it's an awful hack, depending on implementation details;
  • I can't use the atom! macro, and if I define my own, I can't use it in pattern position:
macro_rules! my_atom {
	($atom:expr) => {
		//This is not a pattern!
		atom!($atom).into::<$crate::interning::Atom>()
	}
}

So, here's my idea:

  • add a CopiableAtom type, with only the unsafe_data field, implementing Copy;
  • when constructing a dynamic CopiableAtom, leak the underlying data to ensure it will never be dropped;
  • add a knob to the AtomType builder to choose whether to create an Atom or a CopiableAtom; for example:
string_cache_codegen::AtomType::new_copiable("foo::FooAtom", "foo_atom!")...
//or
string_cache_codegen::AtomType::new("foo::FooAtom", "foo_atom!").copiable()...

Status of provenance/aliasing issues detected by Miri

Hi, I wish I were being helpful here but I'm just reporting the status of some hackery I've done. (hours wasted here, etc)

This is one of the most-downloaded crates that roundtrips pointer values through integers. I'm looking into most such crates to understand if it's possible to rewrite such roundtrips or if there's something better that can be done to support what libraries want to do.

For this crate, I do not think that it is possible to patch out the roundtrips, but the problem isn't the round-tripping. This crate wants to store pointers inside Atom, and to do that in a well-defined way we need a type which is capable of storing provenance and also plain old bytes, so we could use a raw pointer type or MaybeUninit. But we want to uphold the niche which it currently has, so the only type which is eligible is NonNull. But putting NonNull in that field breaks string-cache-codegen, because it puts Atom in a macro_rules! pattern:

error: to use a constant of type `std::ptr::NonNull` in a pattern, `std::ptr::NonNull` must be annotated with `#[derive(PartialEq, Eq)]`
    --> /tmp/markup5ever-0.11.0/target/miri/x86_64-unknown-linux-gnu/debug/build/markup5ever-85445da718f099e3/out/generated.rs:3381:39
     |
3381 | ...html") => { $ crate :: ATOM_NAMESPACE__68_74_74_70_3A_2F_2F_77_77_77_2E_77_33_2E_6F_72_67_2F_31_39_39_39_2F_78_68_74_6D_6C } ;
     |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
    ::: interface/tree_builder.rs:89:9
     |
89   |         expanded_name!(html "template") => flags.template = true,
     |         ------------------------------- in this macro invocation
     |
     = note: this error originates in the macro `namespace_url` (in Nightly builds, run with -Z macro-backtrace for more info)

I've put up the changes that would remove the ptr-int-ptr roundtrips (as well as some aliasing issues with Box that current SB has) in a branch in case anyone is interested: master...saethlin:fix-aliasing

Eliminate the pub field in Atom

Right now the data field is public for use by the atom!() macro. You can violate memory safety by putting garbage in this field. We should make it private somehow, or get support into rustc for unsafe fields.

Plan to use parking lot?

Hi. I'm working to improve the performance of swc (https://swc.rs) ​

I profiled my bundler, and found that pthread_wait is consuming lots of time.
So I forked this and switched to parking_lot, and it results in a big difference.
(Fork: https://github.com/swc-project/string-cache)

(Total time: 165 ms => 153ms)

Before:
image

After:
image

I will send a PR if you are okay with using parking lot.
If not, I'll just maintain a fork.

Unsoundness in dynamic_set.rs and `from_mutated_str`

Reviewing this crate's use of unsafe identified a few issues:

return NonNull::from(&mut **entry);

let entry_ptr: *mut Entry = &mut **entry_ptr;

These construct a &mut Entry that may exist concurrently with the &Entry references unsafely constructed by many methods on Atom. These should use the new ptr::addr_of_mut helper which avoids the hazard.

current = unsafe { &mut (*entry_ptr).next_in_bucket };

This similarly constructs a unique reference to a field, which may actually get written while an aliasing &Entry is live elsewhere. This probably needs an UnsafeCell.

let buffer = unsafe { &mut *buffer.as_mut_ptr() };

This constructs a reference to uninitialized memory. Raw pointer writes should be used instead.

What guarantees do u32 Atom hash values offer?

This uses the phf* (perfect hash function) crates underneath, and if I understand correctly, phf offers guaranteed perfect, no collision, hash values for a compile time aggregated (static) set.

However as implemented here, these u64 hashes are shift-xor'd to u32 values:

// This may or may not be great...

Does this not reintroduce a risk of collision? Or does string_cache_codegen also include a guaruntee that the u32-bit hash doesn't collide for a static set? Or does string_cache not really need to care about such collisions?

Depending on the answer, I might be able to offer a doc PR.

Implement Deref for Atom → str

Disclaimer: I have yet to read through the source code for this project; my only interactions with it have been through servo/servo

It'd be really convenient to be able to do &atom instead of atom.as_slice() everywhere to get a str. It'd greatly increase the ergonomics with utilizing this library

Build failure on Rust 1.32 due to use of unstable feature 'maybe_uninit'

I just tried running a build on my project, which has its version pinned on Rust 1.32, and it fails on string-cache with the following error:

error[E0658]: use of unstable library feature 'maybe_uninit' (see issue #53491)
   --> /home/travis/.cargo/registry/src/github.com-1ecc6299db9ec823/string_cache-0.7.4/src/atom.rs:506:26
    |
506 |         let mut buffer = mem::MaybeUninit::<[u8; 64]>::uninit();
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0599]: no function or associated item named `uninit` found for type `std::mem::MaybeUninit<[u8; 64]>` in the current scope
   --> /home/travis/.cargo/registry/src/github.com-1ecc6299db9ec823/string_cache-0.7.4/src/atom.rs:506:26
    |
506 |         let mut buffer = mem::MaybeUninit::<[u8; 64]>::uninit();
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `std::mem::MaybeUninit<[u8; 64]>`

error: aborting due to 2 previous errors

Some errors occurred: E0599, E0658.
For more information about an error, try `rustc --explain E0599`.
error: Could not compile `string_cache`.

It seems like the source of this compatibility breakage was #222.

I notice that there isn't a minimum version bound in .travis.yml:

rust:
- nightly
- beta
- stable

I'm fine if I need to bump up my minimum supported version of rustc, but would it be possible to add a minimum version to give developers on string_cache more pause before deciding to use newer features of Rust? It would also really help us out when we need to decide what version we need to re-pin to downstream! 😅

0.8.5 is a "breaking change"

error[E0283]: type annotations needed
  --> {.. snip ..}\browserslist-rs-0.12.3\src\data\caniuse.rs:91:35
   |
91 |     let chrome = CANIUSE_BROWSERS.get(&"chrome".into()).unwrap();
   |                                   ^^^ ---------------- type must be known at this point
   |                                   |
   |                                   cannot infer type of the type parameter `Q` declared on the associated function `get`
   |
   = note: multiple `impl`s satisfying `Atom<BrowserNameAtomStaticSet>: Borrow<_>` found in the following crates: `core`, `string_cache`:
           - impl<Static> Borrow<str> for Atom<Static>
             where Static: StaticAtomSet;
           - impl<T> Borrow<T> for T
             where T: ?Sized;
note: required by a bound in `AHashMap::<K, V, S>::get`
  --> {.. snip ..}\ahash-0.7.6\src\hash_map.rs:81:12
   |
81 |         K: Borrow<Q>,
   |            ^^^^^^^^^ required by this bound in `AHashMap::<K, V, S>::get`
help: consider specifying the generic argument
   |
91 |     let chrome = CANIUSE_BROWSERS.get::<Q>(&"chrome".into()).unwrap();
   |                                      +++++

I think this was caused by #266, reverting to 0.8.4 fixes the error

Build fails on macOS

I get the following error when building on macOS 10.13.4 using Rust 1.25.0:

error: failed to run custom build command for `string_cache v0.7.2 (file:///Users/mohamed/Downloads/string-cache)`
process didn't exit successfully: `/Users/mohamed/Downloads/string-cache/target/debug/build/string_cache-c547615f76cbab49/build-script-build` (signal: 6, SIGABRT: process abort signal)
--- stderr
dyld: Library not loaded: @rpath/libproc_macro-982dc7a0685c08f6.dylib
  Referenced from: /Users/mohamed/Downloads/string-cache/target/debug/build/string_cache-c547615f76cbab49/build-script-build
  Reason: image not found

New 0.2.x release to fix rustc error

We use the 0.2.29 release in perf.rust-lang.org's html5ever benchmark which currently errors with this error. Can we release a 0.2.30 with a fix? I can try to prepare a PR it would be accepted.

error: macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths
  --> /home/alex/.cargo/registry/src/github.com-1ecc6299db9ec823/string_cache-0.2.29/src/lib.rs:71:13
   |
71 |     pub use atom;
   |             ^^^^
   |
note: the macro is defined here
  --> /tmp/.tmpFazpQK/target/debug/build/string_cache-589ae806702507d8/out/atom_macro.rs:2:1
   |
2  | / macro_rules! atom {
3  | | (\"sdev\") => { $crate::Atom { unsafe_data: 0x2 } };
4  | | (\"onstart\") => { $crate::Atom { unsafe_data: 0x100000002 } };
5  | | (\"overflow\") => { $crate::Atom { unsafe_data: 0x200000002 } };
...  |
127| | (\"stroke-miterlimit\") => { $crate::Atom { unsafe_data: 0x4f400000002 } };
127| | }
   | |_^

error: aborting due to previous error

cc rust-lang/rust#53495

Doesn't compile with latest rust

~/D/r/string-cache (master|✚2) $ cargo build
   Compiling string_cache_plugin v0.0.0 (file:///Users/coreyf/Development/rust/string-cache)
plugin/src/atom/../../../shared/repr.rs:50:17: 50:32 error: trivial cast: `&u64` as `*const u64`, #[deny(trivial_casts)] on by default
plugin/src/atom/../../../shared/repr.rs:50         data: ((x as *const u64) as *const u8).offset(1),
                                                           ^~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `string_cache_plugin`.

To learn more, run the command again with --verbose.

i figured i'd get rid of the cast, but then a new error appears

~/D/r/string-cache (master|✚1) $ cargo build
   Compiling string_cache_plugin v0.0.0 (file:///Users/coreyf/Development/rust/string-cache)
plugin/src/atom/../../../shared/repr.rs:50:16: 50:17 error: mismatched types:
 expected `*const u8`,
    found `&u64`
(expected u8,
    found u64) [E0308]
plugin/src/atom/../../../shared/repr.rs:50         data: (x as *const u8).offset(1),
                                                          ^
error: aborting due to previous error
Could not compile `string_cache_plugin`.

To learn more, run the command again with --verbose.

running with

~/D/r/string-cache (master|✚2) $ rustc -vV
rustc 1.0.0-nightly (27901849e 2015-03-25) (built 2015-03-26)
binary: rustc
commit-hash: 27901849e07558639b8decc03707e0317ae8280e
commit-date: 2015-03-25
build-date: 2015-03-26
host: x86_64-apple-darwin
release: 1.0.0-nightly

Is there a way to print these in rust-lldb?

I am using CLion with the Rust plugin.

Trying to debug a variable in the swc-project/swc project.

$ p src

(string_cache::atom::Atom<swc_atoms::JsWordStaticSet>) src = {
  unsafe_data = 140464742296960 {
    0 = 140464742296960
  }
  phantom = {}
}

Do a version bump.

How should a version bump be done for this crate after #183?

My idea is:

  • Breaking version bump of string-cache-codegen, given the new hashes array is written from there and it wouldn't be compatible with older versions of string-cache.
  • Making string-cache depend on the newer string-cache-codegen version, given the new field is just public so it can be accessed from macros, not part of the public API.
  • Minor bump on that.

But I guess @nox may have different opinions? Should we do a breaking bump everywhere?

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.