クソ雑魚エンジニアのメモ帳

学んだことを書くところ

Vue.jsのエラーハンドリングについて調べた件(後編)

おはようございます。GW1日目ですね。

前回の続きで、今回は「エラーの情報をどこに集約する?」という点について書いていきたいと思います。

  1. どうやって検知する?
  2. どこにエラーの情報を集約する? ←イマココ

検証に使用したコードはこちら デモはこちら スライドはこちら

概要

集約する場所として、自作のログ収集サーバーを挙げられる方もいらっしゃるとは思います。ですが、それなりにコストがかかり、なおかつそこそこの可用性も必要なはずだと思うので、わざわざ自前で作る意味・利点がほとんどないと思います。

そこで、集約する場所として、世間一般的にどのようなサービスを使うのがポピュラーなのか調べてみた結果、何個かありました。

  • googleAnalytics
  • Sentry
  • bugsnag
  • Rollbar

順に紹介していきます。

googleAnalytics

言わずと知れた、googleアクセス解析ツールです。世の中のほとんどのpublicなアプリケーションはgoogleAnalyticsをhtmlに読み込ませているはずです。

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-XXXXXXXX-X"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-XXXXXXX-X');
</script>

これのおまけ機能として、javascriptga()という関数が利用可能になっています。これはページ遷移・イベント発生など様々な情報をトラッキングするための関数です。そのトラッキングできルものの一つに例外があります。

developers.google.com

使い方は、単にエラーが発生した際に以下のフォーマットで関数を呼び出すだけ。

ga('send', 'exception', {
    'exDescription': {記録したいエラー情報(string)},
    'exFatal': false
  })

実際に呼び出した後は、googleAnalyticsのダッシュボードから簡単に確認でき、OSバージョンだったり画面の解像度だったり豊富な情報を組み合わせてグラフを作成できます。

f:id:Kouchannel55:20190810150900p:plain

ただし、デフォルトの状態では確認できません。メニューの カスタム>カスタムレポート>新しいカスタムレポート からレポートを作成する必要があります。

f:id:Kouchannel55:20190810151106p:plain

前編にて紹介した Vue.config.errorHandler と合わせて使用するとこんな感じ

Vue.config.errorHandler = function(err, vm, info) {
  // eslint-disable-next-line no-undef
  ga('send', 'exception', {
    'exDescription': 'catched by `Vue.config.errorHandler`\n' + err.toString(),
    'exFatal': false
  })
}

利点・欠点を挙げます。まず利点

  • お金が100%かからない
  • すでにga()で他のトラッキングをしている場合、簡単に導入できる
  • gaのダッシュボードを利用しておしゃれなグラフを作成できる

欠点

  • 初期の設定がめんどくさい
  • gtag()関数とごちゃまぜになりがち
  • エラーの情報として、単一の文字列しか渡せないかつ最大150バイトまで
    • スタックトレースとかlineNoとかは単一の文字列に含めて、後で自分でパースしなければならない

Sentry

こちらはアクセス解析ツールというよりログ収集ツールです。ログの収集に特化しています

しかも、公式ドキュメントに紹介されているのでとても便利そうな予感がします!!!

Error tracking services Sentry and Bugsnag provide official integrations using this option. https://vuejs.org/v2/api/#errorHandler

さらに、Sentryには、Vuejs専用のライブラリが存在します。

sentry.io

docs.sentry.io

サインアップすると 言語選んで、ライブラリ入れて、その場で発火したかどうかテストできるので本当に開発者に優しいです。

f:id:Kouchannel55:20190810152915p:plain
言語を選んで

f:id:Kouchannel55:20190810152910p:plain
試しに発火させましょう

yarn add @sentry/browser @sentry/integrations
import Vue from 'vue'
import * as Sentry from '@sentry/browser';
import * as Integrations from '@sentry/integrations';

Sentry.init({
  dsn: 'https://<key>@sentry.io/<project>',
  integrations: [new Integrations.Vue({Vue, attachProps: true})],
});

一瞬。しかも収集が簡単なだけでなくダッシュボードはとても充実しています!以下のキャプチャをご覧ください。

f:id:Kouchannel55:20190810153307p:plain
一覧画面

f:id:Kouchannel55:20190810154021p:plain
詳細画面

めちゃくちゃ見やすい。一覧画面はエラーの種類ごとにまとめられてるし、直近の発生回数や発生したユーザー数、さらにはこのエラーを修正するための開発者をアサインする機能までついております。

詳細画面ではIP/OS/ブラウザ/スタックトレースが見えます。これらはgaでも取得できましたが、驚くのは「エラーが発生するまでの直近のユーザーの操作」が記録されていること。こんなんあればエラーの原因もすぐにわかりそうですね。控えめに言って神

しかも無料プランでこの情報量が見れるんです。太っ腹。

sentry.io

ただし、一つ注意しないといけないのは Vue.config.errorHandler と併用できない点です。 Vue.config.errorHandler を定義していると、Sentryが発火しなくなります。

おそらく、Sentryの中で Vue.config.errorHandler を定義していて、そのあとにVue.config.errorHandlerを自前で書くと上書きされてしまうのだと思います。気をつけましょう

利点・欠点を挙げます。まず利点

  • 情報量が多い
  • 情報が見やすい
  • エラーに対する開発者のアサイン・イシュー管理まで可能
  • ドキュメントが丁寧

欠点

  • チームで利用するには課金が必要

bugsnag

こちらもSentry同様、Vuejs公式ドキュメントに紹介されています。Sentryと同じように便利そうです。

docs.bugsnag.com

// ES module-style import
import bugsnag from '@bugsnag/js'
import bugsnagVue from '@bugsnag/plugin-vue'

// commonjs/node-style require
var bugsnag = require('@bugsnag/js')
var bugsnagVue = require('@bugsnag/plugin-vue')

var bugsnagClient = bugsnag('YOUR_API_KEY')

bugsnagClient.use(bugsnagVue, Vue)

Rollbar

rollbar.com

こちらはVuejs公式ドキュメントには紹介されていませんが、Vuejs専用のライブラリがあります。Sentryと同じように便利そうです。

import Vue from 'vue'
import App from './App'
import router from './router'

var Rollbar = require('vue-rollbar');

Vue.config.productionTip = false;

Vue.use(Rollbar, {
     accessToken: 'ACCESS-TOKEN',
     captureUncaught: true,
     captureUnhandledRejections: true,
     enabled: true,
     source_map_enabled: true,
     environment: 'production',
     payload: {
       client: {
            javascript: {
               code_version: '1.0'
            }
       }
     }
});

new Vue({
 el: '#app',
 router,
 render: h => h(App)
})

まとめ

  • 基本的にはSentryが神
  • すでにgoogleAnalyticsのレポートをゴリゴリに使っているPJの場合には、エラーの収集もgoogleAnalyticsを使用すると他の指標といい感じのグラフが作れそう

Vue.jsのエラーハンドリングについて調べた件(前編)

こんばんは。ニトリのホテルスタイル枕が最高すぎて会社に遅刻しそうになるmorifujiです、

VuejsOsaka#2で発表した内容ですが、一部はしょったり早口で説明した部分が多かったので、ここに細かく書いていこうと思います。

検証に使用したコードはこちら デモはこちら スライドはこちら

背景

弊社の場合、新規PJの開発中などはステージング環境を常にクライアントにOPENにして都度都度フィードバックをもらうことにしています。その中での話。

クライアント「なんかトップページでローディングが止まんないよ」

僕「むむ。コンソールのログ見せてもらえませんか?」

クライアント「。。。(ナニイッテンダコイツ)」(ここでレスが帰ってこなくなる)

こんな感じのことがたまーにあります。このやりとりって結構大変なんですよねクライアントによってITリテラシーがバラバラなので、、、しかもこの後にローカルで環境を再現してエラーの内容を把握してってなると、結構時間が無駄になってる気がします。

また、改めて考えるとサーバーのエラーは当然のようにトラッキングして集約するのに、フロントのエラーがほったらかしなのはどうなのとも思いました。そこで今回はVuejsで構築したSPAの上で発生したエラーをトラッキングするための仕組みを考えてみました。

エラーをトラッキングするとなると、考えることは以下の2点になるかなと思います。

  1. どうやって検知する?
  2. どこにエラーの情報を集約する?

どちらも試してみたので、順を追って書いていきます、

どうやって検知する?

ざっくり3種類方法があるようなのでそれぞれ調べてみました。

  • window.onerror
  • errorCaptured
  • Vue.config.errorHandler

window.onerror

Vue.jsはJavascriptの上で動作します。なので、当然Javascript自体のエラー検知の機構を使うことができます。それが window.onerror です。

developer.mozilla.org

使い方はこのような感じ。

window.onerror = function(message, source, line, column, error) {
  console.log('catched by `window.onerror_`', message)
<button onclick="HOGEHOGEHOGE">PureなJavascriptでエラー</button>

この状態でbuttonを押すとonerrorがキャッチします。onerrorの引数はこんな感じ。

引数 説明 サンプルでの引数
message エラーメッセージ(string) "Uncaught ReferenceError: HOGEHOGEHOGE is not defined"
source エラーが発生したJavascriptのファイル名(string) "http://localhost:8080/index"
line エラーが発生したJavascriptの行番号 28
column エラーが発生したJavascriptの列番号 34
error 発生したエラー エラーオブジェクト(下記の画像参照)

f:id:Kouchannel55:20190808005043p:plain

errorCaptured

こちらはVuejs独自のエラー検知の機構で、Vuejsの各Component独自に定義することができます。

vuejs.org

SFCでの使い方はこのような感じ。

<template>
  <div class="hello">Hello World!</div>
</template>

<script>
export default {
  name: 'HelloWorld',
  errorCaptured(err, vm, info) {
    console.log('catched by `CHILD errorCaptured`', err.toString())
  },
}
</script>

dataとかpropsとかと並列に定義できます。

親子関係の各Componentに定義されていてなおかつ子Component内でエラーが発生した場合は、以下の図のように親のComponentの errorCaptured を順番に発火させてエラーが伝播していきます。エラーが発生した孫Componentの errorCaptured は発火しないのがポイントですね、

f:id:Kouchannel55:20190808003413p:plain

このエラー伝播は errorCaptured の返り値を false にすることで途中で止めることができます。上記の例では子Componentから親Componentへのエラー伝播を止めることができます。

f:id:Kouchannel55:20190808003914p:plain

Vue.config.errorHandler

こちらもVuejs独自のエラー検知の機構で、引数も errorCaptured と同一ですが、グローバルに設定する点が全く異なります

vuejs.org

使い方は以下のような感じ。

Vue.config.errorHandler = function(err, vm, info) {
  console.log('catched by `Vue.config.errorHandler`', err.toString())
}

errorCaptured のエラー伝播がルートComponentまで到達した場合は、 Vue.config.errorHandler が発火します。

f:id:Kouchannel55:20190808005754p:plain

ということは途中でエラーの伝播が止まった場合は Vue.config.errorHandler は発火しません

f:id:Kouchannel55:20190808010005p:plain

まとめ

3種類紹介しましたが、これを踏まえて僕なりのエラー検知機構の使い分け場面を考えてみました。

  • window.onerror
    • 画面内でVuejs以外のPureなJavascriptが動いていて、その部分に関してのエラーを検知したい場合に有効
  • errorCaptured
    • Vuejsの階層構造の中で、Componentによってエラー検知処理を分けたい場合に有効
    • 例えば、画面によってエラーの検知処理を変えたい場合や、特定のComponent内のエラーは握りつぶしたい場合とか?
  • Vue.config.errorHandler
    • Vuejs内のエラーを一元的に処理したい場合に有効
    • 例えば、全ての画面でのどんなエラーでも収集したい場合

他にもこう言う用途があるよ!みたいなのがあれば教えてくださいー

残りの 2. どこにエラーの情報を集約する? と言う話は、別記事にしようと思いますのでもう少しだけお待ちくださいー。

ではでは

v-kansai Vue.js/Nuxt.js meetup#8 に参加しました!

こんばんは、さっきv-kansai#8に参加したところですがブログ書く枠なので ブログ書かされました書きました。よく考えたら久しぶりにはてぶろ更新した

vuekansai.connpass.com

vueとtypescriptと私

docs.google.com

vueとtypescriptの相性悪くない?→確かに複雑度が上がる

「型安全欲しくないですか?」ということで2パターンtypescriptの導入方法を紹介してました。

Vue.extendパターン

jsの状態からあまり変化なく自然に書けるパターン。全く知らなかった。

computed/data/watchは普通。

ただしpropsが一癖あるらしい

// component外部
type PropPbjType = {
  id: Number,
  name: String
}

// component内部
props: {
  val: String,
  obj: Object as PropType<PropObjType>
}

これは結構大変ですよね、複数人開発をしている場合とかに他人のcomponentのpropsの型を見るときにtypeで定義されていると型がわかりにくそう。それ以外は非常に良さげ :100:

vue-property-decoratorパターン

これは実務で使ったことあるパターンでした。vue要素が薄いのは本当にわかります。

「propsの!は必須。つけないとエラーが起こるらしい」→これはなったことがなかった。コンパイルオプションの設定次第(strictNullChecksとか?)なのかな?

vuexとtypescriptは一晩中話せる←この話がきになる

Google Nest HubでVue.jsを使った話、LINE Thingsでちょっとだけセキュアなシステム作った話

speakerdeck.com

LINE API EXPERTながおまるさんのお話。VUIめちゃつよな人が登壇してて本当に何でもできる方なんだなと思いました。

Google Nest HubでVue.jsを使った話

GoogleNestHubというデバイスに表示する画面を、vuejsを使ってInteractiveCanvasを操作できるという話(←あってるか怪しい)

どういうものを作ったのかはこの画像を見ると理解しやすそうでした

上の画像含めinteractiveCanvasはここら辺で詳しく見れそうです。

LINE Thingsでちょっとだけセキュアなシステム作った話

今年4月にLINEThingsが自動通信機能が搭載したらしく、 BLEに対応したIoTデバイスと端末が10m以内になったら、IoT→端末→(ネットワーク)→AWSlambda→AmazonConnectを通して送信できる。

IoTデバイス→端末(LINEThings)の接続はネット設定が不必要らしい。これはすごい。だいたいIoTデバイスの方がネット設定がないといけないIoTばかりなイメージ(知らんけど)なので驚きました

Function API You will use in future

speakerdeck.com

vue3が今年中に出る!?という話からvue3で採択予定のFunctionAPIについての話でした。

FunctionAPI

vue2でもプラグインを噛ませれば使えるとのこと

// yarn add vue-function-api

import {plugin} from "vue-function-api"

Vue.use(plugin)

簡単な例↓

<template>
  <div>
    <span>count is {{ count }}</span>
    <span>plusOne is {{ plusOne }}</span>
    <button @click="increment">count++</button>
  </div>
</template>

<script>
  import Vue from 'vue';
  import { value, computed, watch, onMounted } from 'vue-function-api'

  export default {
    setup() {
      // reactive state
      const count = value(0);
      // computed state
      const plusOne = computed(() => count.value + 1);
      // method
      const increment = () => {
        count.value++;
      };
      // watch
      watch(
        () => count.value * 2,
        val => {
          console.log(`count * 2 is ${val}`);
        }
      );
      // lifecycle
      onMounted(() => {
        console.log(`mounted`);
      });
      // expose bindings on render context
      return {
        count,
        plusOne,
        increment,
      };
    },
  };
</script>

https://github.com/vuejs/vue-function-api#single-file-component

setup()がなにやら色々やってそう。以下、setup()内で何をどう書くかについての説明

dataの作成

value関数を使う

const count = value(0)

dataへのアクセス

→{変数}.value

count.value

computedの定義

→関数で包む

const plusOne = computed(() => count.value+1)

methodsの定義

→関数で包む

const increment = () => {
  count.value++;
}

watchの定義

→関数で包む!?あれ?監視する値を関数で渡してる!?

watch(
  () => count.value*2,
  value => {
    console.log(`hogehoge`)
  }
)

FunctionAPI使わない書き方はこう

computed: {
  doubleValue() {
    return this.count * 2
  }
},
watch: {
  doubleValue(newValue) {
    console.log(`hogehoge`)
  }
}

FunctionAPIの場合は、内部的に count.value*2をcomputedに持ってるんですかねえ?奥が深い。。

総括として、(まるでReactやん)と(この書き方objectではなくfunctionの方が無駄が少なくない?)という感想が生まれた

後者に関しては、先ほどの例をこんな感じで書けないのか?という話

export default function(): Object {
      // reactive state
      const count = value(0);
      // computed state
      const plusOne = computed(() => count.value + 1);
      // method
      const increment = () => {
        count.value++;
      };
      // watch
      watch(
        () => count.value * 2,
        val => {
          console.log(`count * 2 is ${val}`);
        }
      );
      // lifecycle
      onMounted(() => {
        console.log(`mounted`);
      });
      // expose bindings on render context
      return {
        count,
        plusOne,
        increment,
      };
 };

RFCこちら

vueの好きさが伝わってきて、やっぱり学生って熱意すごいなと思いました。

NuxtとVuejsで読みづらいコード

(スライド未確認)

v-ifが多すぎる

<div v-if="status='active'">
  <button class="btn btn-active">アクティブ</button>
</div>
<div v-if="status='pending'">
  <button class="btn btn-pending">ペンディング</button>
</div>
<div v-if="status='complete'">
  <button class="btn btn-complete">完了</button>
</div>

みたいなコードができると、可読性が弱い・仕様変更に弱いという点で避けるべきという話。

→この場合は改善案としてstatusLabel()btnStyle()に分けるべきとのこと。

<buttn :class="btnStyle" class="btn">
  {{ statusLabeal }}
</button>

綺麗ですね :star:

v-ifが何個続いたら許せないか議論になりそう。僕は3つv-ifが出たら唸る自信がある

ちなみに僕だったらstatusLabelとbtnStyleをくっつける気がする(←論外)

props多すぎる問題

意味のある単位で渡すのが大事。

そもそも根本的な原因は?

役割・責務をよく考えずにcomponentを作っているせい

スーパーわかる。一定の親componentがある場合のみしか動作しない子componentとかはまさにそれ。

VuexFireSQLについて

docs.google.com

FireSQLとは

  • FirebaseをオマージュしたMySQL
  • リアルタイムにMySQLの変更を検知できる
  • マネージドMySQLでも可能

https://firebaseopensource.com/projects/jsayol/firesql/

やべーものがでてきたとおもった(小並感)

コネクションが枯渇しそうなのとフロントでSQLを発行できるのはセキュリティ的に大丈夫なのかについて気になったので本人に聞いて見たが、やっぱりそうらしい

Functional Component In Vuejs

slides.com

そういえばこの前全く自分もおんなじLTしてたなあと思った。振り返ると自分のLTはほんま完成度低かった

彼のはわかりやすくておしゃれですごかった。

Coreのバージョンが2.3.0以前の場合、 プロパティを受け入れる時は必須

全く知らなかった。

FPSは2倍ほど

やっぱりFunctionalComponentは早い。。

無限スクロールを導入する

(スライドがtwitterに上がってなかったので、ブログの方から拝借させていただきました🙇‍♂️)

webneko.dev

vue-infinite-loadingかと思ったらvue-infinite-loadingだった。

僕は泥臭そうな話が好きなので、PHPレンダリングしているvuejsのプロジェクトがどういうものなのか気になりました。

あまり無限スクロールの概念自体がわからない人が多いらしいですが、要はtwitterみたいなもんなのです。

vue-infinite-loadingの公式にデモがあるので見るとすぐにわかると思います。

https://peachscript.github.io/vue-infinite-loading/

無限スクロールを実装する時の他の選択肢として、 IntersectionObserverを使った実装もしたことがあるけどこっちは人気ないのかな、、

Mozillahttps://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API

以下は僕がIntersectionObserver使った時のコード

<-- 何かしらのv-forするcomponent -->
<awesome-list v-for="item in items" :key="item.id" />

<-- 発火する基準となるDOM -->
<div refs="loading-spin">
    <div v-if="isLoading">読み込み中です</div>
    <div v-if="hasNoMoreData">全て表示しています</div>
</div>
{
    /**
     * Observerを設定
     */
    setIntersectionObserver() {
      const triggerElement = this.$refs['loading-spin'];
      if (!triggerElement) {
        return;
      }

      const observer = new IntersectionObserver(changes => {
        const clientHeight = document.documentElement.clientHeight;
        for (const change of changes) {
          const rect = change.target.getBoundingClientRect();
          // 画面内に入ったとき
          if (rect.top > 0 && rect.top <= clientHeight) {
            // まずObserver解除
            this.clearIntersectionObserver();
            // データを取得
            this.isLoading = true;
            
            // データの取得処理
            // promiseなりなんなり
            
            // 取得後にもう一度Observer設定
            this.$nextTick(() => {
              this.setIntersectionObserver();
            })
            this.isLoading = false;
          }
        }
      });

      // 監視開始
      observer.observe(triggerElement);
      this.observer = observer;
    },
    /**
     * observerを解除
     */
    clearIntersectionObserver() {
      if (!this.observer) {
        return;
      }

      this.observer.unobserve(this.$refs['loading-spin']);
      this.observer = null;
    },
}

総括

なんかやる気が出たので明後日LTすることにした。多分エラーのトラッキングの話

多少枠が空いているはずなのでみなさまぜひご参加ください

vuejs-osaka.connpass.com

v-kansai#4 に参加しました!

こんばんは。最近物欲がないmorifujiです。

昨日3/20の夜に、v-kansaiの第4回が大阪で開催されたので参加レポートを書かされた書きました!

vuekansai.connpass.com

誰だお前

  • atma株式会社で働いてるクソザコエンジニアです。
  • 直近だとnodejsでスクレイピングしてます。
  • vueに関係する業務だとvuejs/nuxtjs/netlify結構使います。
  • nuxtよりごりごりvuejsかくタイプです。

civiluo.co.jp

感想

Nuxt.jsとnetlifyが優しかった話(遅刻したので正確なタイトルわかりません 😭)

netlifyに関しては、開発者への優しさに溢れてますよねー。

yarn.lockがあったら勝手にyarnしてくれたりとか、ブランチごとにpreviewのサイトが建てられていたりとかA/Bテストやってくれたりとか画像を最適化してくれたりとか。全部GUIでぽちぽちするだけなのでいいですよねー。

光があれば闇もあるということで優しくない(むしろ辛い)ところを自分なりに考えてみました

  • Trailing slashの設定がない
  • netlify functionsのドキュメントが少ない(lambdaのドキュメントを読めということでしょうが)

Nuxt.js のディレクト

nuxtのディレクトリについて逐一丁寧に説明していただきました。

nuxtって、すでにディレクトリ構成が洗練されているものなので、小〜中規模のPJまではディレクトリ構成をそのまま使えて幸せですよね。vuejsだと個々のPJによってディレクトリ構成がオレオレ構成に変えられていたりする(し、ディレクトリ構成のREADMEがないことが多い)。

強欲なエンジニアというパワーワードが聞けて面白かった

余談ですが、登壇者の方どこかでお見かけしたと思ってましたがPHPカンファレンスにいらしゃった方なんですね(一度だけ前参加した)。

あと。そのあとのMCの人との会話の中で、 page単位のビルドだと、pageを移動するたびに同じcomponentやjsを再度読み込むが、全体のビルドだと、初期ロードの一度で読むのでオーバーヘッドがない、みたいな話が出てたが少しわからなかった。誰か詳しく教えて。

speakerdeck.com

揮発性の高いcomponentを作る

twitterでよく見る人の発表でした。

揮発性の高いcomponent=ダイアログとかツールチップとか

グローバルメソッドにする発想がなかったので目から鱗だった。

confirm()と同じようなノリでかけるのは面白い

// confirm()と似てる
const isAgreed1 = await this.$dialog("hogehoge", "warning", options)
const isAgreed2 = confirm("hogehoge")

メリットとしてあげられたのは以下の通り

  • メンテの向上
  • テンプレートを軽く
  • stateやローカルdataを圧迫しない

テンプレートを軽くできるのは本当に嬉しい。使うたびにコンポーネントをimportしてhandlerつけてって作業は本当に煩わしい。

ただ、ダイアログというと状態管理が必要だろうし、個人的にはtypescriptが欲しくなりそうな内容だった。まあけどそこまでするならルートコンポーネントにダイアログ配置してvuexで表示非表示の制御がベターなんだろうなあと思った。

speakerdeck.com

capacitor

capacitorの紹介でした。

capacitorとcordovaの違いとしてあげられたのは以下の通り。

capacitor Cordova 何がどう変わるか
ビルド時、アプリの必要ファイル群全てを吐き出す 必要ファイルのwwwのみ capacitorだとビルド時間が早い/gitの管理が楽
コアプラグインがあり、本体のバージョンに常に追随する コアプラグインはない capacitorはバージョンを上げるときに、依存関係をcordovaに比べあまり気にしなくて良い
プラグインはまだあまりなさそう 4380個のプラグイン(昨日夜時点) プラグインはcordovaの方が多い

まだベータ版なのでコミュニティは大きくはないがめっちゃ期待できますねー

VueとAWSAppSyncで始めるチャットアプリ開発

AWSAppSyncでgraphQLを使ってチャットアプリを作った事例の紹介

AWSのAppSyncはあまり知らなかったので今回のイベントで一番興味があったスライドですね。

queryとmutationの概念はわかったけどResolverがわからず放置した記憶。。

気になった点

  • 必要なattributeだけ取得できるっていうのは本当に嬉しい点と同時にパフォーマンスの調整がサーバー側でできるのかどうかが少し心配。
    • クエリとかmutationの定義とかで制御できるのかな?誰か教えて
  • エンドポイントが一つだからログは少し見にくい?
    • cloudwatchにログを吐くことはできるからcloudwatchでごにょごにょすれば可能とのこと
  • AppSyncから外部エンドポイント叩ける?
    • 無理とのこと。やるなら、lambdaを用意して、AppSync=>lambda=>外部エンドポイントとのこと。
    • このスライドだと直接叩いているようだけどどうなのだろう? 🧐

ちらりと映るbulmaがオシャレだった。

www.slideshare.net

NuxtをLightsailとGAEに デプロイした話

lightsailでdockerを使ってnuxtを建てていたが、GCPに移行した話。

lightsailとdocker/dockerswarmって相性どうなんだろう?awsでdocker使われるならecsなイメージだったので驚きでした。料金面でいうと天地の差があるのでもともとはlightsal選んだんですかね、

GCPはコンソールが優しくていいですよねー、感動 😭

slides.com

Web猫ブログに型安全を

nuxtの2.4.0からtypescriptのサポートを始めたので、typescriptを導入した話。

弊社のとあるnuxtのPJ(ちょうどバージョン2.4.0)でも規模が大きくなりそうなのでtypescriptを導入しないといけないという状態だったため非常に勉強になりました。

typescript非対応なライブラリを使うとしんどそうだが大丈夫そうとのこと

contentfulというサービスもスライドにちらっと映ってたけど、よさげ。CMS界隈あまり知りませんがコンテンツをjsonで管理しやすいようにする流れがあるんですかね?

master.d2ezecqbj32kby.amplifyapp.com

AWS Lambdaで Vue.js SSRしてみた

npmプラグインvue-server-renderer)を使えばnuxtなしでSSRができるという話。

ssrならnuxt。nuxtならssrというレベルでnuxtが有名で、その選択肢を忘れていた。。

公式ガイドがあって優しい。けどユニバーサルなコードを書くために気をつける点が多くて難易度はnuxtに比べて高そう、以下抜粋。

  • カスタムディレクティブがそのままフロントに返されるとエラーになる
  • サーバーでの実行を考慮していないライブラリはSSRでエラー(例えばwindowみたいな変数使うライブラリとか)

vuejsでMVVM(タイトル未詳)

規模が大きくなるとmvvmの形には自然となりますよね。ただ意識はあまりしていなかったので必要性を再認識しました。

ionic/vue(タイトル未詳)

まだベータ版ですがionicがvuejsに対応した話。

あまりionic触らないので少しわからなかった 😇

speakerdeck.com

まとめ

初のvuejsのイベントでしたが結構vuejs周りのエコシステム(AppSync/vue-server-renderer)の勉強になりました。余裕あればLTやりたい!

登壇者の方ありがとうございました!また参加させていただきます!

もうprettierで消耗したくない人へのvueでのeslint設定

タイトル通り。vscodeとvuejsを使ってのlintの設定をprettierなしでやってみた。

tl;dr

対象ファイルとpackage.jsonと.eslintrc.jsの対応表👇

構文チェック・自動フォーマット
対象ファイル
npmライブラリ(package.json) .eslintrc.jsでのextends
.vue(<template>) eslint-plugin-vue plugin:vue/recommended
.vue(<script>) eslint eslint:recommended
. js eslint eslint:recommended
yarn add -D eslint eslint-plugin-vue
module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
    // タグの最後で改行しないで
    "vue/html-closing-bracket-newline": [2, {"multiline": "never"}],
    // 不要なカッコは消す
    "no-extra-parens": 1,
    // 無駄なスペースは削除
    "no-multi-spaces": 2,
    // 不要な空白行は削除。2行開けてたらエラー
    "no-multiple-empty-lines": [2, {"max": 1}],
    // 関数とカッコはあけない(function hoge() {/** */})
    "func-call-spacing": [2, "never"],
    // true/falseを無駄に使うな
    "no-unneeded-ternary": 2,
    // セミコロンは禁止
    "semi": [2, "never"],
    // 文字列はシングルクオートのみ
    "quotes": [2, "single"],
    // varは禁止
    "no-var": 2,
    // jsのインデントは2
    "indent": [2, 2],
    // かっこの中はスペースなし!違和感
    "space-in-parens": [2, "never"],
    // コンソールは許可
    "no-console": 0,
    // カンマの前後にスペース入れる?
    "comma-spacing": 2,
    // 配列のindexには空白入れるな(hogehoge[ x ])
    "computed-property-spacing": 2,
    // キー
    "key-spacing": 2,
    // キーワードの前後には適切なスペースを
    "keyword-spacing": 2,
  }
}
{
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        {
          "language": "vue",
          "autoFix": true,
        },
   ],
    "eslint.autoFixOnSave": true,
}

環境

とあるプロジェクトでnuxt+vuejsを使って実装をしていた時の話。開発初期段階だったのもありnuxtが2.4をリリースしたというのでアップデートしてみた。しかし急にeslintとprettierの構文チェック・自動フォーマットが効かなくなった。。。。

開発環境はvscode

package.jsonのパッケージはだいたいこんな感じ。

  • vue@2.6.6
  • nuxt@2.4.3
  • babel-eslint@7.2.3
  • eslint@4.19.1
  • eslint-config-standard@10.2.1
  • eslint-loader@1.9.0
  • eslint-plugin-html@3.1.1
  • eslint-plugin-import@2.7.0,
  • eslint-plugin-node@5.1.1,
  • eslint-plugin-promise@3.5.0,
  • eslint-plugin-standard@3.0.1,
  • eslint-plugin-vue@5.2.2
  • eslint-config-prettier@4.0.0
  • eslint-plugin-prettier@3.0.1

そこで、再び構文チェック・自動フォーマットが効くように半日頑張ったが無理だった。半日溶けた。

問題点

  1. eslint系が12種と多い、多すぎる😇😇😇😇😇、どれが何の役割を果たしているかわからない
  2. prettier・veturも絡んでいる
  3. 依存関係がわからない(vscodeのconfig.json.eslintrc.jsにもprettierがある。)
  4. 元々の設定もどこかしらのドキュメントを参考につぎはぎしていたので何の設定をどこで制御できるのかわからない

👉カオス味を減らしたい

👉👉prettierはもういらないのでは??

👉👉👉prettierをなくして最低限のeslintによる構文チェック・自動フォーマットを設定する

手順

僕の.eslintrc.jsがいつまでも正しいかどうかわからないので記録を残しておく。苦しんで覚えたい人は再現してほしい。おれのしかばねをこえてゆけ💀💀💀

.vueの構文チェックその1

まずは自動フォーマットはひとまず無視して、構文チェックができるようにする。

eslint公式にはvuejsに関する記述はなかったが、 eslint-plugin-vueには Official ESLint plugin for Vue.jsという記載があったのでこれを信じる。

これだけでいいみたい。

  • eslint
  • eslint-plugin-vue

https://vuejs.github.io/eslint-plugin-vue/user-guide/#installation

以下は不要なもの。多すぎ。使っている人はいますぐpackage.jsonから消し去ろう

  • eslint-config-standard@10.2.1
  • eslint-loader@1.9.0
  • eslint-plugin-html@3.1.1
  • eslint-plugin-import@2.7.0,
  • eslint-plugin-node@5.1.1,
  • eslint-plugin-promise@3.5.0,
  • eslint-plugin-standard@3.0.1,
  • eslint-config-prettier@4.0.0
  • eslint-plugin-prettier@3.0.1

あとは .eslintrc.jsの設定だけ。これでOK

module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    // 'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
  }
}

https://vuejs.github.io/eslint-plugin-vue/user-guide/#configuration

この段階ではまだvscodeでの自動フォーマットは効いていないはず。落ち着いてまずは構文チェックができるかを確認

# srcフォルダにvueファイルがある前提

$ eslint --ext .js,.vue src
# または
$ eslint "src/**/*.{js,vue}"

構文チェックがうまく走るとおそらくこんな表示になる。

$ yarn run eslint "pages/**/*.{js,vue}"
....
....
✖ 70 problems (21 errors, 49 warnings)
  13 errors and 49 warnings potentially fixable with the `--fix` option.

出力からわかると思うが、--fixをつけることでフォーマットすることができる。

このことからeslintを用いることで、vueファイルの構文チェック・自動フォーマットができることが確認できた。

vscode上での自動フォーマット

こんどはCmd + Sft + Psettingと打ち、Open Settings(JSON)を選択。

ここで、vscode上でeslint構文チェック・自動フォーマットができるように以下を設定

{
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "vue",
      "autoFix": true,
    },
  ],
  "eslint.autoFixOnSave": true,
}

このことからvalidate=構文チェックかつautoFixOnSave=自動フォーマットなのがわかる

試しに、.vueファイルのtemplate内のインデントをぐちゃぐちゃにするとエラーが出て、Cmd + Sを何回か押すと自動フォーマットができることが確認できる。

しかしまだ不完全のようである。.vue<script>タグの中が全くフォーマットされていない

.vueの構文チェックその2

.eslintrc.jsを振り返り、以下の行をコメントアウト

module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
  }
}

すると、例えば以下のvueファイルで自動フォーマットをするとconsole.log("hoge");;;;;;;が自動で消えるようになる

<template>
  <div>
    <p>aaaa</p>
  </div>
</template>

<script>
export default {
  created() {
-      console.log("hoge");;;;;;
+   console.log("hoge");
          const    a     =     "A"
    var b = (4+(1-1))
    let c = [  1  ,  2 ,  3]


    const d = "zzzzzzzz"
  }
}
</script>

🎊🎊🎊おめでとう🎊🎊🎊。ここまで来ればeslintの設定はほとんど終わり。だけどscriptの中はまだまだ混沌としているので、あとでカスタマイズ。

rulesをカスタマイズ

<template>のカスタマイズ

今までの内容を見るに、vueファイルの<template>の構文チェックを行なっているのは eslint-plugin-vueであり、<template>eslint-plugin-vue独自のルールが適用されている。

独自のルールはここにまとめられている。今回の設定(以下ファイル)では、 'plugin:vue/recommended'として導入されているので、Priority A: Essential (Error Prevention)Priority B: Strongly Recommended (Improving Readability)の内容も含んでおり、一通りvuejs公式が推奨するルールはだいたい入っている。

module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',     // これ
    'plugin:vue/recommended'
  ],
  rules: {
  }
}

'plugin:vue/recommended'に入っていないルールであっても自分で追加することができる。

例えば筆者は以下の部分をカスタマイズした。

  rules: {
    // タグの最後で改行しないで
    "vue/html-closing-bracket-newline": [2, {"multiline": "never"}],
  }

簡単にいうと、2はエラー。このルールを厳しく適用する

html-closing-bracket-newlineは、 'plugin:vue/recommended'には含まれているがカスタマイズしないと以下の構文のpタグがOKになってしまう。

<template>
  <div>
    <p
      hoge="#"
      huga="%%%"
      >
      aaaa
    </p>
  </div>
</template>

これは個人的には気に入らないので、"multiline": "never"という設定にした。

<template>
  <div>
    <p
      hoge="#"
-     huga="%%%"
-     >
+     huga="%%%">
      aaaa
    </p>
  </div>
</template>

注意点として、自動フォーマットができないルールもいくつかある。ルールの一覧画面に🔧マークがなければ自動フォーマットができないので注意しよう、

<script>をカスタマイズ

これが思ったよりしんどい。

これも今までの内容を見るに、<script>の構文チェックを行なっているのはeslintである。eslintのルールはこちらにある。100個以上あってなかなかしんどいが、一通り目を通すと非常に勉強になるのでみなさんにも読んでほしい。

今回は'eslint:recommended',として導入しており、ルール一覧画面では✔️マークがあるルールは全て含まれている。(こちらも注意点として、自動フォーマットができないルールもいくつかある。ルールの一覧画面に🔧マークがなければ自動フォーマットができない。)

ただし、recommendedでは先ほど出てきた構文でも特に何もエラー・警告を吐かない。ツッコミどころしかないのだが。。

          const    a     =     "A"
    var b = (4+(1-1))
    let c = [  1  ,  2 ,  3]


    const d = "zzzzzzzz"

これをどう直すかは人次第なので一意見として僕のrulesをいかに貼ります。

  rules: {
    // 不要なカッコは消す
    "no-extra-parens": 1,
    // 無駄なスペースは削除
    "no-multi-spaces": 2,
    // 不要な空白行は削除。2行開けてたらエラー
    "no-multiple-empty-lines": [2, {"max": 1}],
    // 関数とカッコはあけない(function hoge() {/** */})
    "func-call-spacing": [2, "never"],
    // true/falseを無駄に使うな
    "no-unneeded-ternary": 2,
    // セミコロンは禁止
    "semi": [2, "never"],
    // 文字列はシングルクオートのみ
    "quotes": [2, "single"],
    // varは禁止
    "no-var": 2,
    // jsのインデントは2
    "indent": [2, 2],
    // かっこの中はスペースなし!違和感
    "space-in-parens": [2, "never"],
    // コンソールは許可
    "no-console": 0,
    // カンマの前後にスペース入れる?
    "comma-spacing": 2,
    // 配列のindexには空白入れるな(hogehoge[ x ])
    "computed-property-spacing": 2,
    // キー
    "key-spacing": 2,
    // キーワードの前後には適切なスペースを
    "keyword-spacing": 2,
  }

ちなみに2は先ほど出てきたが、10もここで出てきた。1は警告(エラーではない)、0は許可。

例えばこのルールで先ほどの構文を自動フォーマットするとこうなる。幸せ。

    const a = 'A'
    let b = 4+(1-1)
    let c = [ 1, 2, 3]

    const d = 'zzzzzzzz'

まとめ。

prettierの設定に疲れた人はやって見ると良い。

対象ファイルとpackage.jsonと.eslintrc.jsの対応表👇

構文チェック・自動フォーマット
対象ファイル
npmライブラリ(package.json) .eslintrc.jsでのextends
.vue(<template>) eslint-plugin-vue plugin:vue/recommended
.vue(<script>) eslint eslint:recommended
. js eslint eslint:recommended
yarn add -D eslint eslint-plugin-vue
module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
    // タグの最後で改行しないで
    "vue/html-closing-bracket-newline": [2, {"multiline": "never"}],
    // 不要なカッコは消す
    "no-extra-parens": 1,
    // 無駄なスペースは削除
    "no-multi-spaces": 2,
    // 不要な空白行は削除。2行開けてたらエラー
    "no-multiple-empty-lines": [2, {"max": 1}],
    // 関数とカッコはあけない(function hoge() {/** */})
    "func-call-spacing": [2, "never"],
    // true/falseを無駄に使うな
    "no-unneeded-ternary": 2,
    // セミコロンは禁止
    "semi": [2, "never"],
    // 文字列はシングルクオートのみ
    "quotes": [2, "single"],
    // varは禁止
    "no-var": 2,
    // jsのインデントは2
    "indent": [2, 2],
    // かっこの中はスペースなし!違和感
    "space-in-parens": [2, "never"],
    // コンソールは許可
    "no-console": 0,
    // カンマの前後にスペース入れる?
    "comma-spacing": 2,
    // 配列のindexには空白入れるな(hogehoge[ x ])
    "computed-property-spacing": 2,
    // キー
    "key-spacing": 2,
    // キーワードの前後には適切なスペースを
    "keyword-spacing": 2,
  }
}
{
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        {
          "language": "vue",
          "autoFix": true,
        },
   ],
    "eslint.autoFixOnSave": true,
}

おまけ

eslintのruleと死闘を繰り広げてるときに投げられた無慈悲なメッセージ

f:id:Kouchannel55:20190221202322p:plain

ESLintを入れただけでは今までのおまえのコードは何一つ綺麗にならない。おまえはメキシコの夕日よりも赤いコードを見ることになるだろう。

https://qiita.com/khsk/items/0f200fc3a4a3542efa90

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装6章)

こんばんは。10日ぶりの更新です。

時間が取れなくて前回からだいぶ遅くなった :sweat_drops:

www.oreilly.co.jp

記録用git

vol.1

vol.2

vol.3

vol.4

vol.5

vol.6 いまここ

6章 アセンブラ

理論

この章ではアセンブリ言語アセンブラによって機械語に変換できるようにする。

アセンブリ言語

LOAD R3 7などの人間が読みやすい言語。

機械語

バイナリである。先ほどのLOAD R3 7は、ハードウェアの論理設計上で、

LOADは最初の8ビット。R3(レジスタ)は次の8ビット。7(アドレス)を残りの16ビットで表現するというようなルールが決められており、それに従って出力されたバイナリ

シンボル

アセンブリ言語上でメモリアドレスを間接的に指定できる変数。

任意のシンボルは、どこかのアドレスの位置を参照している

利用方法としては一般的に以下の2種類がある

  • ラベル
    • LOOPなど。gotoやjmpで利用するためのラベル、タグ。この行の次の行のアドレスを指し示す。この行自体は機械語に出力されない。
  • 変数
    • count=0i=1のように利用できる。変数はRAMと混合しないようにメモリのある地点から格納される。

シンボル解決

アセンブラは、シンボルを使わない場合だとルールに従ってバイナリに変換するだけで良い。しかし

、シンボルを利用する場合はシンボルが表すアドレスを全て保存しシンボルが利用されている箇所全てを置換する必要がある。

理論(Hack)

アセンブリ言語

各行は、以下のうちのどれかで構成される

  • A命令またはC命令の命令
  • シンボル
  • 意味のない行

また、コメントは //で書き、その行のそれ以降の文字は全てアセンブラに無視される

定義済みシンボル

R0...R15THISARGSは定義済みシンボル。

ラベル RAMアドレス
R0 0
R1 1
... ...
R15 15
SP 0
LCL 1
ARGS 2
THIS 3
THAT 4

実装

アセンブリを実装しなければならない。

2段階で実装する。

  1. シンボルがないアセンブリの場合のアセンブラ
  2. シンボルがあるアセンブリの場合のアセンブラ

1の場合は一度アセンブリを舐めるだけで良い。というのも、上から順に変換することが可能であるため。

2の場合、ファイルを舐める作業は2回必要である。というのも、シンボルは先に利用して後から宣言できるため。

こんな場合👇

@LOOP_END
0;JMP
...
...
...
(LOOP_END)

なので、1度目は、L命令を全てシンボルテーブルに保存して、2度目はそれを元に、出力していく。

特にこだわりがないので言語はkotlinでやってみた。

assemblerディレクトリにkotlinのプロジェクトファイルを保存してあります

Parserモジュール

import java.io.File
import java.io.InputStream

class Parser(filePath: String) {

    enum class COMMAND_TYPE {
        A_COMMAND,
        C_COMMAND,
        L_COMMAND,
        NONE
    }

    var inputLines = mutableListOf<String>()

    var commandIndex: Int = -1

    var binaryIndex: Int = -1

    init {
        val inputStream: InputStream = File(filePath).inputStream()
        val lineList = mutableListOf<String>()

        inputStream.bufferedReader().useLines { lines -> lines.forEach { lineList.add(it) } }
        lineList.forEach {
            // コメントは除去
            val formattedLine = it.split("//")[0]

            if (formattedLine.isEmpty()) {
                return@forEach
            }
            this.inputLines.add(formattedLine)
        }
    }

    // 入力にまだコマンドが存在するか
    fun hasMoreCommands(): Boolean {

        val nextLine = inputLines.getOrNull(this.commandIndex + 1)
        return nextLine !== null
    }

    // 次のコマンドを読み込む
    fun advance() {
        this.commandIndex++
    }

    // 現コマンドの種類を表す
    fun commandType(): COMMAND_TYPE {
        val command = getCurrentCommand()
        return when {
            command.startsWith("@") -> COMMAND_TYPE.A_COMMAND
            command.startsWith("(") && command.endsWith(")") -> COMMAND_TYPE.L_COMMAND
            else -> COMMAND_TYPE.C_COMMAND
        }
    }

    fun symbol(): String {
        val command = this.getCurrentCommand()
        val commandType = this.commandType()
        if (commandType !== COMMAND_TYPE.L_COMMAND && commandType !== COMMAND_TYPE.A_COMMAND) {
            return ""
        }

        if (commandType === COMMAND_TYPE.L_COMMAND) {
            return command.replace("(", "").replace(")", "")
        }

        return command.replace("@", "").trim()
    }

    fun dest(): String {
        val command = this.getCurrentCommand()
        if (!command.contains("=")) {
            return ""
        }
        return command.split("=")[0].trim()
    }

    fun comp(): String {
        val command = this.getCurrentCommand()
        var comp = command
        if (command.contains(";")) {
            comp = command.split(";")[0].trim()
        }

        if (command.contains("=")) {
            comp = command.split("=")[1].trim()
        }

        return comp
    }

    fun jump(): String {
        val command = this.getCurrentCommand()
        if (!command.contains(";")) {
            return ""
        }
        return command.split(";")[1].trim()
    }

    private final fun getCurrentCommand(): String {
        return inputLines.get(this.commandIndex).trim()
    }

    fun reset() {
        commandIndex = -1
        binaryIndex = -1
    }
}

愚直に実装。

ただし以下の部分に注意

// コメントは除去
val formattedLine = it.split("//")[0]

Codeモジュール

destはロジックに落とし込めた。jmpはロジック無理と割り切ってmapで行った。compはロジックに落とそうとすると非常に難しかった。

もしかしてと思って他の人の回答を見ると、そのまさかMapで持たせてた。まあそれが早いよね。

class Code() {
    companion object {
        fun dest(nimonic: String): String {
            var d = ""

            d += if (nimonic.contains("A")) {
                "1"
            } else {
                "0"
            }

            d += if (nimonic.contains("D")) {
                "1"
            } else {
                "0"
            }

            d += if (nimonic.contains("M")) {
                "1"
            } else {
                "0"
            }

            return d
        }

        fun comp(nimonic: String): String {
            val map = mapOf(
                    "0" to "0101010",
                    "1" to "0111111",
                    "-1" to "0111010",
                    "D" to "0001100",
                    "A" to "0110000",
                    "!D" to "0001101",
                    "!A" to "0110001",
                    "-D" to "0001111",
                    "-A" to "0110011",
                    "D+1" to "0011111",
                    "A+1" to "0110111",
                    "D-1" to "0001110",
                    "A-1" to "0110010",
                    "D+A" to "0000010",
                    "D-A" to "0010011",
                    "A-D" to "0000111",
                    "D&A" to "0000000",
                    "D|A" to "0010101",
                    "M" to "1110000",
                    "!M" to "1110001",
                    "-M" to "1110011",
                    "M+1" to "1110111",
                    "M-1" to "1110010",
                    "D+M" to "1000010",
                    "D-M" to "1010011",
                    "M-D" to "1000111",
                    "D&M" to "1000000",
                    "D|M" to "1010101"
            )

            return map.get(nimonic)!!
        }

        fun jump(nimonic: String): String {
            return when (nimonic) {
                "JGT" -> "001"
                "JEQ" -> "010"
                "JGE" -> "011"
                "JLT" -> "100"
                "JNE" -> "101"
                "JLE" -> "110"
                "JMP" -> "111"
                else -> "000"
            }
        }

    }
}

Main.kt

import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter


fun main(args: Array<String>) {
    // 入力
    val parser = Parser("/nand2tetris/projects/06/rect/Rect.asm")

    // 出力
    val fil = FileWriter("/nand2tetris/projects/06/rect/Rect.hack")

    // シンボルテーブル
    val symbolTable = SymbolTable()

    // 1. 変数保存
    while (parser.hasMoreCommands()) {
        parser.advance()
        if (parser.commandType() == Parser.COMMAND_TYPE.L_COMMAND) {
            // 次の行のbinaryIndexを保存
            symbolTable.addEntry(parser.symbol(), parser.binaryIndex + 1)
        } else {
            // L命令ではないので、機械語として出力される=>機械語の行数をカウントアップ
            parser.binaryIndex++
        }
    }

    parser.reset()

    // 2. 変換出力
    val pw = PrintWriter(BufferedWriter(fil))

    while (parser.hasMoreCommands()) {
        parser.advance()
        if (parser.commandType() === Parser.COMMAND_TYPE.NONE) {
            continue
        }

        if (parser.commandType() === Parser.COMMAND_TYPE.L_COMMAND) {
            continue
        }

        if (parser.commandType() === Parser.COMMAND_TYPE.A_COMMAND) {

            val symbol = parser.symbol()

            // シンボルに文字が含まれる場合
            val address = if (symbol.contains(Regex("""\D+"""))) {

                // シンボルテーブルに含まれていない場合
                if (!symbolTable.contains(symbol)) {
                    // 新たに追加
                    symbolTable.addEntry(symbol)
                }

                // シンボルテーブルからアドレスを解決
                symbolTable.getAddress(symbol)
            } else {
                // シンボルが数値の場合
                Integer.parseInt(symbol)
            }

            pw.println("%016d".format(Integer.toBinaryString(address).toLong()))
            continue
        }

        // C命令
        val binaryString = "111" + Code.comp(parser.comp()) + Code.dest(parser.dest()) + Code.jump(parser.jump())
        pw.println(binaryString)
    }

    pw.close()
}


エントリーポイント。ファイルを二回なめている。

commandIndexで、アセンブリ言語の行数をカウントアップしているが、機械語の行数とは一致しないので注意。途中で気づいて、 binaryIndexを生やしてカバーリング。👇

    // 1. 変数保存
    while (parser.hasMoreCommands()) {
        parser.advance()
        if (parser.commandType() == Parser.COMMAND_TYPE.L_COMMAND) {
            // 次の行のbinaryIndexを保存
            symbolTable.addEntry(parser.symbol(), parser.binaryIndex + 1)
        } else {
            // L命令ではないので、機械語として出力される=>機械語の行数をカウントアップ
            parser.binaryIndex++
        }
    }

SymbolTableモジュール

class SymbolTable() {
    var table = mutableMapOf<String, Int>()

    init {
        table.putAll(mapOf(
                "R0" to 0,
                "R1" to 1,
                "R2" to 2,
                "R3" to 3,
                "R4" to 4,
                "R5" to 5,
                "R6" to 6,
                "R7" to 7,
                "R8" to 8,
                "R9" to 9,
                "R10" to 10,
                "R11" to 11,
                "R12" to 12,
                "R13" to 13,
                "R14" to 14,
                "R15" to 15,
                "SP" to 0,
                "LCL" to 1,
                "ARG" to 2,
                "THIS" to 3,
                "THAT" to 4,
                "SCREEN" to 16384,
                "KBD" to 24576
        ))
    }

    // 保存開始アドレス
    var index = 16

    fun addEntry(symbol: String, address: Int) {
        table[symbol] = address
    }

    fun addEntry(symbol: String) {
        table[symbol] = this.index++
    }

    fun contains(symbol: String): Boolean {
        return table.contains(symbol)
    }

    fun getAddress(symbol: String): Int {
        return table.get(symbol)!!
    }
}

愚直に実装。特に面白い点なし。

Pong.hack

30000行近くのアセンブリ機械語に変換して実行。何気に面白い。

f:id:Kouchannel55:20190123103231p:plain
Pong.hackの起動画面。右上がゲーム画面

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装5章)

こんにちは。ようやくコンピュータの核の部分を実装する章までやってきた。。

地味に長かった。がなんとか終わりそう

www.oreilly.co.jp

記録用git

vol.1

vol.2

vol.3

vol.4

vol.5 いまここ

5章 コンピュータアーキテクチャ

理論(一般的なコンピュータ)

ノイマンアーキテクチャ

CPUが中央にあり、メモリ・入力デバイス・出力デバイス等と通信する。

メモリ

データメモリ・命令メモリの2種類に分けられる。アーキテクチャによっては別のメモリユニットになり得る。

どちらにせよバイナリで保存される

データメモリ

プログラムで利用するデータを保存することができる

3章で学んだメモリのように、読み込み・書き込みができる

命令メモリ

読み込みのみである

命令メモリとCPUは、以下のようなやりとりを行う

  1. 命令メモリを一つフェッチ
  2. CPUが命令をデコード・実行・次のアドレス計算
  3. 1に戻る(次のアドレスに対して)

CPU

CPUは、ALUとレジスタと制御ユニットで構成されている

ALU

3章で作ったもの。汎用的な演算装置

レジスタ

データメモリよりも高速にデータの保存・取得ができる領域

ただしデータメモリに比べはるかにサイズが小さい

データレジスタ=はやくて便利なメモリ?

アドレスレジスタ=読み書きに利用するためのアドレスを保存するレジスタ

プログラムカウンタレジスタ=現在実行している命令メモリのアドレスを保存するレジスタ

制御ユニット

CPUが命令をデコードするために必要なもの。

命令メモリを一つフェッチCPUが命令をデコード・実行・次のアドレス計算

ここまでを踏まえて、CPUの処理は、以下のように分割できる

  1. 命令メモリを一つフェッチ
  2. 制御ユニットが命令をデコード
  3. 命令を実行
  4. 次の命令メモリアドレスを計算

入力出力デバイス

全ての入出力デバイスはメモリマップドI/Oという仕様に従っている

キーボード=ベースアドレスが決められており、キーボードは常に入力値をベースアドレスに格納している。したがってCPUはベースアドレスを取得するだけでよい

スクリーン=ベースアドレスから描画領域分のアドレスの領域のメモリの値が画面に描画されると決められており、CPUはその領域のメモリの値を変更するだけで良い

理論(Hackハードウェア)

A命令

前章で紹介したが、

0xxxxxxxxxxxxxxxというフォーマットに沿う。

xxxxxxxxxxxxxxxの値がアドレスレジスタに保存される

C命令

前章で紹介したが、

111accccccdddjjjというフォーマットに沿う。

このフォーマットはALUのフォーマットと非常に近い。それに気づけるかどうか少し難しいところ。

Hackハードウェアの構成

  • 命令メモリ
  • CPU
  • データメモリ
    • RAM
    • メモリマップ(スクリーン)
    • メモリマップ(キーボード)

実装

データメモリ

データメモリは、RAMとスクリーンとキーボードで構成されている。

キモは、入力アドレスaddress[15]の値が、RAMかスクリーン(16384~24575)か24576(キーボード)かの判定部分。3パターンなのでDMux4Wayが使える

    // アドレス13,14で判断
    // address[14]=1 かつ address[13]=1 はキーボード
    // address[14]=1 かつ address[13]=0 はスクリーン
    // それ以外はRAM
    DMux4Way(in=load, sel[0]=address[13], sel[1]=address[14], a=isload-ram1, b=isload-ram2, c=isload-screen, d=isload-keyboard);

👇以下全文

CHIP Memory {
    IN in[16], load, address[15];
    OUT out[16];

    PARTS:

    // アドレス13,14で判断
    // address[14]=1 かつ address[13]=1 はキーボード
    // address[14]=1 かつ address[13]=0 はスクリーン
    // それ以外はRAM
    DMux4Way(in=load, sel[0]=address[13], sel[1]=address[14], a=isload-ram1, b=isload-ram2, c=isload-screen, d=isload-keyboard);

    Or(a=isload-ram1, b=isload-ram2, out=isload-ram);

    // RAM
    RAM16K(in=in, load=isload-ram, address=address[0..13], out=out-ram);

    // スクリーン
    Screen(in=in, load=isload-screen, address=address[0..12], out=out-screen);

    // キーボード
    Keyboard(out=out-keyboard);

    Mux4Way16(a=out-ram, b=out-ram, c=out-screen, d=out-keyboard, sel[0]=address[13], sel[1]=address[14], out=out);
}

CPU

最初はスイスイ行ったけどところどころハマった。👇以下全文

CHIP CPU {

    IN  inM[16],         // M value input  (M = contents of RAM[A])
        instruction[16], // Instruction for execution
        reset;           // Signals whether to re-start the current
                         // program (reset==1) or continue executing
                         // the current program (reset==0).

    OUT outM[16],        // M value output
        writeM,          // Write to M? 
        addressM[15],    // Address in data memory (of M)
        pc[15];          // address of next instruction

    PARTS:

    // CPU制御ユニット
    // a命令かc命令か
    // a-instruction
    // c-instruction
    Or(a=instruction[15], b=instruction[15], out=c-instruction);
    Not(in=c-instruction, out=a-instruction);

    // Dレジスタの保存はC命令の場合のみ
    And(a=instruction[4], b=c-instruction, out=isload-d);

    // instruction[4]はd2=>Dに保存するかどうか
    DRegister(in=alu-out, load=isload-d, out=out-d);

    // Aレジスタが保存するのは、A命令時の定数かC命令時の計算結果
    Mux16(a=alu-out, b=instruction, sel=a-instruction, out=in-a);

    // A命令でもC命令でも、結局のところAレジスタをloadするかどうか
    Or(a=instruction[5], b=a-instruction, out=isload-a);

    // instruction[5]はd1=>Aに保存するかどうか
    // outを切り取るのは問題ない??
    ARegister(in=in-a, load=isload-a, out[0..15]=out-a, out[0..14]=addressM);

    // instruction[12]はa。ALUのyはa=1ならM,a=0ならA
    // ALUにMを使うかAを使うか
    Mux16(a=out-a, b=inM, sel=instruction[12], out=aluin-y);

    // メモリの演算
    // 命令がそのまま流れる?
    ALU(x=out-d, y=aluin-y, zx=instruction[11], nx=instruction[10], zy=instruction[9], ny=instruction[8], f=instruction[7], no=instruction[6], out=alu-out, out[0..7]=alu-out1, out[8..15]=alu-out2, out[15]=alu-minus, out=outM, zr=alu-zr, ng=alu-ng);


    // Mに書き込み行うかどうか
    // CPUの領域外?
    // C命令の時のみ出力
    And(a=instruction[3], b=c-instruction, out=writeM);
    
    // jmp判定
    // 負の数はa-out[15]=1
    // 正の数はa-out[15]=0
    // 以下の変数を計算
    // alu-minus
    // alu-plus
    // alu-iszero
    Not(in=alu-minus, out=alu-plusorzero); //正の数または0
    Or8Way(in=alu-out1, out=temp1); // 0判定その1
    Or8Way(in=alu-out2, out=temp2); // 0判定その2
    Or(a=temp1, b=temp2, out=temp3); // 0判定その3
    Not(in=temp3, out=alu-iszero);
    And(a=alu-plusorzero, b=temp3, out=alu-plus);

    // カウンタ
    // C命令の場合のみ飛ぶのを忘れずに
    // j1, j2, j3の計算
    And(a=alu-minus, b=instruction[2], out=is-j1);
    And(a=alu-iszero, b=instruction[1], out=is-j2);
    And(a=alu-plus, b=instruction[0], out=is-j3);
    // 条件に合致するか
    Or8Way(in[0]=is-j1, in[1]=is-j2, in[2]=is-j3, out=is-jump);
    // なおかつ、C命令か
    And(a=is-jump, b=c-instruction, out=can-jump);
    // PC処理
    // incは基本true
    PC(in=out-a, load=can-jump, inc=true, reset=reset, out[0..14]=pc);
}

ハマったこと

j1, j2, j3の判定

alu-out(aluの出力結果)が[15]=trueの場合、alu-outが負の数と判断できる

=>Notをとれば、正の数かどうか判断できる

==> Not(in=alu-out[15], out=alu-plus)

これは正しくなく、alut-out[15]のNotを取ると、正の数か0かになります。なので、

Not(in=alu-out[15], out=alu-plusorzero)として

And(a=alu-plusorzero, b=temp3, out=alu-plus)で正の数と判定しないといけないので注意

DRegister/ARegisterのload

DRegisterloadは、 instruction[4]を見がちですが、A命令の場合も考慮して、以下のようにしましょう

    // Dレジスタの保存はC命令の場合のみ
    And(a=instruction[4], b=c-instruction, out=isload-d);

    // instruction[4]はd2=>Dに保存するかどうか
    DRegister(in=alu-out, load=isload-d, out=out-d);

ARegisterもだいたい同じ。isloadの条件が少し違うので注意

    // A命令でもC命令でも、結局のところAレジスタをloadするかどうか
    Or(a=instruction[5], b=a-instruction, out=isload-a);

    // instruction[5]はd1=>Aに保存するかどうか
    // outを切り取るのは問題ない??
    ARegister(in=in-a, load=isload-a, out[0..15]=out-a, out[0..14]=addressM);

Sub bus of an internal node may not be used

ALU(... , out=alu-out, out=outM, zr=alu-zr, ng=alu-ng);として

Or8Way(in=alu-out[0..7], out=temp1); // 0判定その1
Or8Way(in=alu-out[8..15], out=temp2); // 0判定その2
Or(a=temp1, b=temp2, out=temp3); // 0判定その3

とすると変数が使われてないかもよと怒られる。変数ちゃんと使ってるのにと思ったら同様のエラーの質問発見。

http://nand2tetris-questions-and-answers-forum.32033.n3.nabble.com/Sub-bus-of-an-internal-node-may-not-be-used-td4031198.html

正しくは、ALU(... , out=alu-out1, out[0..7]=alu-out1, out[8..15]=alu-out2, out=outM, zr=alu-zr, ng=alu-ng);として

    Or8Way(in=alu-out1, out=temp1); // 0判定その1
    Or8Way(in=alu-out2, out=temp2); // 0判定その2
    Or(a=temp1, b=temp2, out=temp3); // 0判定その3

としないといけない

PCのincはtrue?

ハマったわけではないけど、PCのincは常にtrueにしてるがこれでいいものか。。。

   // PC処理
    // incは基本true
    PC(in=out-a, load=can-jump, inc=true, reset=reset, out[0..14]=pc);