Add a library loader that uses the local file system

This sort of loader is useful for snapshotting Dart code on the host.

Change-Id: I1b2b1fdc25a3989f63124e8ba222770d3f0959b7
diff --git a/file_loader/BUILD.gn b/file_loader/BUILD.gn
new file mode 100644
index 0000000..afcc748
--- /dev/null
+++ b/file_loader/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2016 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("file_loader") {
+  sources = [
+    "file_loader.cc",
+    "file_loader.h",
+    "string_converter.cc",
+    "string_converter.h",
+  ]
+
+  deps = [
+    "//dart/runtime:libdart",
+    "//lib/ftl",
+    "//lib/tonic/parsers",
+  ]
+}
diff --git a/file_loader/file_loader.cc b/file_loader/file_loader.cc
new file mode 100644
index 0000000..aadcfe7
--- /dev/null
+++ b/file_loader/file_loader.cc
@@ -0,0 +1,150 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "lib/tonic/file_loader/file_loader.h"
+
+#include <iostream>
+#include <memory>
+#include <utility>
+
+#include "lib/ftl/files/directory.h"
+#include "lib/ftl/files/file.h"
+#include "lib/ftl/files/path.h"
+#include "lib/ftl/files/symlink.h"
+#include "lib/ftl/logging.h"
+#include "lib/tonic/file_loader/string_converter.h"
+#include "lib/tonic/parsers/packages_map.h"
+
+namespace tonic {
+namespace {
+
+constexpr char kPackageScheme[] = "package:";
+constexpr size_t kPackageSchemeLength = sizeof(kPackageScheme) - 1;
+
+constexpr char kFileScheme[] = "file:";
+constexpr size_t kFileSchemeLength = sizeof(kFileScheme) - 1;
+
+constexpr char kFileURLPrefix[] = "file://";
+constexpr size_t kFileURLPrefixLength = sizeof(kFileURLPrefix) - 1;
+
+constexpr char kDartScheme[] = "dart:";
+
+// Extract the scheme prefix ('package:' or 'file:' from )
+std::string ExtractSchemePrefix(std::string url) {
+  if (url.find(kPackageScheme) == 0u)
+    return kPackageScheme;
+  if (url.find(kFileScheme) == 0u)
+    return kFileScheme;
+  return std::string();
+}
+
+// Extract the path from a package: or file: url.
+std::string ExtractPath(std::string url) {
+  if (url.find(kPackageScheme) == 0u)
+    return url.substr(kPackageSchemeLength);
+  if (url.find(kFileScheme) == 0u)
+    return url.substr(kFileSchemeLength);
+  return std::move(url);
+}
+
+}  // namespace
+
+FileLoader::FileLoader() {}
+
+FileLoader::~FileLoader() {}
+
+bool FileLoader::LoadPackagesMap(const std::string& packages) {
+  packages_ = packages;
+  dependencies_.insert(packages_);
+  std::string packages_source;
+  if (!files::ReadFileToString(packages_, &packages_source)) {
+    std::cerr << "error: Unable to load .packages file '" << packages_ << "'."
+              << std::endl;
+    return false;
+  }
+  packages_map_.reset(new PackagesMap());
+  std::string error;
+  if (!packages_map_->Parse(packages_source, &error)) {
+    std::cerr << "error: Unable to parse .packages file '" << packages_ << "'."
+              << std::endl
+              << error << std::endl;
+    return false;
+  }
+  return true;
+}
+
+Dart_Handle FileLoader::CanonicalizeURL(Dart_Handle library, Dart_Handle url) {
+  std::string string = StringFromDart(url);
+  if (string.find(kDartScheme) == 0u)
+    return url;
+  if (string.find(kPackageScheme) == 0u)
+    return url;
+  if (string.find(kFileScheme) == 0u)
+    return StringToDart(string.substr(kFileSchemeLength));
+
+  std::string library_url = StringFromDart(Dart_LibraryUrl(library));
+  std::string prefix = ExtractSchemePrefix(library_url);
+  std::string base_path = ExtractPath(library_url);
+  std::string simplified_path =
+      files::SimplifyPath(files::GetDirectoryName(base_path) + "/" + string);
+  return StringToDart(prefix + simplified_path);
+}
+
+std::string FileLoader::GetFilePathForURL(std::string url) {
+  if (url.find(kPackageScheme) == 0u)
+    return GetFilePathForPackageURL(std::move(url));
+  if (url.find(kFileScheme) == 0u)
+    return GetFilePathForFileURL(std::move(url));
+  return std::move(url);
+}
+
+std::string FileLoader::GetFilePathForPackageURL(std::string url) {
+  FTL_DCHECK(url.find(kPackageScheme) == 0u);
+  url = url.substr(kPackageSchemeLength);
+  size_t slash = url.find('/');
+  if (slash == std::string::npos)
+    return std::string();
+  std::string package = url.substr(0, slash);
+  std::string library_path = url.substr(slash + 1);
+  std::string package_path = packages_map_->Resolve(package);
+  if (package_path.empty())
+    return std::string();
+  if (url.find(kFileURLPrefix) == 0u)
+    return package_path.substr(kFileURLPrefixLength) + library_path;
+  return files::GetDirectoryName(packages_) + "/" + package_path + "/" +
+         library_path;
+}
+
+std::string FileLoader::GetFilePathForFileURL(std::string url) {
+  FTL_DCHECK(url.find(kFileURLPrefix) == 0u);
+  return url.substr(kFileURLPrefixLength);
+}
+
+std::string FileLoader::Fetch(const std::string& url) {
+  std::string path = GetFilePathForURL(url);
+  std::string source;
+  if (!files::ReadFileToString(files::GetAbsoluteFilePath(path), &source)) {
+    std::cerr << "error: Unable to find Dart library '" << url << "'."
+              << std::endl;
+    exit(1);
+  }
+  dependencies_.insert(path);
+  return source;
+}
+
+Dart_Handle FileLoader::Import(Dart_Handle url) {
+  Dart_Handle source = StringToDart(Fetch(StringFromDart(url)));
+  Dart_Handle result = Dart_LoadLibrary(url, Dart_Null(), source, 0, 0);
+  DART_CHECK_VALID(result);
+  return result;
+}
+
+Dart_Handle FileLoader::Source(Dart_Handle library, Dart_Handle url) {
+  Dart_Handle source = StringToDart(Fetch(StringFromDart(url)));
+  Dart_Handle result = Dart_LoadSource(library, url, Dart_Null(), source, 0, 0);
+  DART_CHECK_VALID(result);
+  return result;
+}
+
+}  // namespace tonic
diff --git a/file_loader/file_loader.h b/file_loader/file_loader.h
new file mode 100644
index 0000000..b60f3f2
--- /dev/null
+++ b/file_loader/file_loader.h
@@ -0,0 +1,47 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIB_TONIC_FILE_LOADER_FILE_LOADER_H_
+#define LIB_TONIC_FILE_LOADER_FILE_LOADER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "dart/runtime/include/dart_api.h"
+#include "lib/ftl/macros.h"
+#include "lib/tonic/parsers/packages_map.h"
+
+namespace tonic {
+
+class FileLoader {
+ public:
+  FileLoader();
+  ~FileLoader();
+
+  bool LoadPackagesMap(const std::string& packages);
+
+  const std::set<std::string>& dependencies() const { return dependencies_; }
+
+  Dart_Handle CanonicalizeURL(Dart_Handle library, Dart_Handle url);
+  Dart_Handle Import(Dart_Handle url);
+  Dart_Handle Source(Dart_Handle library, Dart_Handle url);
+
+  std::string Fetch(const std::string& url);
+
+ private:
+  std::string GetFilePathForURL(std::string url);
+  std::string GetFilePathForPackageURL(std::string url);
+  std::string GetFilePathForFileURL(std::string url);
+
+  std::set<std::string> dependencies_;
+  std::string packages_;
+  std::unique_ptr<PackagesMap> packages_map_;
+
+  FTL_DISALLOW_COPY_AND_ASSIGN(FileLoader);
+};
+
+}  // namespace tonic
+
+#endif  // LIB_TONIC_FILE_LOADER_FILE_LOADER_H_
diff --git a/file_loader/string_converter.cc b/file_loader/string_converter.cc
new file mode 100644
index 0000000..a9d3533
--- /dev/null
+++ b/file_loader/string_converter.cc
@@ -0,0 +1,24 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "lib/tonic/file_loader/string_converter.h"
+
+#include "lib/ftl/logging.h"
+
+namespace tonic {
+
+std::string StringFromDart(Dart_Handle string) {
+  FTL_CHECK(Dart_IsString(string));
+  uint8_t* utf8_array = nullptr;
+  intptr_t length = 0;
+  Dart_StringToUTF8(string, &utf8_array, &length);
+  return std::string(reinterpret_cast<const char*>(utf8_array), length);
+}
+
+Dart_Handle StringToDart(const std::string& string) {
+  return Dart_NewStringFromUTF8(reinterpret_cast<const uint8_t*>(string.data()),
+                                string.length());
+}
+
+}  // namespace tonic
diff --git a/file_loader/string_converter.h b/file_loader/string_converter.h
new file mode 100644
index 0000000..30e202e
--- /dev/null
+++ b/file_loader/string_converter.h
@@ -0,0 +1,24 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIB_TONIC_FILE_LOADER_STRING_CONVERTER_H_
+#define LIB_TONIC_FILE_LOADER_STRING_CONVERTER_H_
+
+#include <string>
+
+#include "dart/runtime/include/dart_api.h"
+
+namespace tonic {
+
+// Redundant with StdStringFromDart but better than adding a dependency on the
+// rest of tonic.
+std::string StringFromDart(Dart_Handle string);
+
+// Redundant with StdStringToDart but better than adding a dependency on the
+// rest of tonic.
+Dart_Handle StringToDart(const std::string& string);
+
+}  // namespace tonic
+
+#endif  // LIB_TONIC_FILE_LOADER_STRING_CONVERTER_H_