20220116のGoに関する記事は3件です。

golangでインタプリンタを作る

概略 今回、go 言語でインタプリンタを作ろうと思った経緯は、筆者は半年ばかり独学で python の勉強をしてきたが、よりプログラミングの理解をより深めるために自作しようと思い立った。 go 言語を選んだ理由としては、静的型付け言語を触りたかったのと、なんとなく流行っているからである。 この記事はGo で作るインタプリンタを参考に進めていく。 本記事は、第一章を読み終えたのでこちらにまとめを書く。 第一章、 字句解析 字句解析とは ソースコードからトークン列に変換することを「字句解析」という let x = 5 + 5; 上記の図のように token 列を生成する 字句解析器 token の定義 token.go type Token struct { Type string Literal string } const ( ILLEGAL = "ILLEGAL" // 未知の文字列 EOF = "EOF" // 終わりを示す IDENT = "IDENT" // 識別子 INT = "INT" // 数値型 ASSIGN = "=" PLUS = "+" [...] ) Lexer(字句解析器) lexer.go type Lexer struct { input string position int // 読み込んでる文字 readPosition int // 次に読み込む文字 ch byte // 検査中の文字 } func New(input string) *Lexer { // Lexerに引数inputをセットしreturn l := &Lexer{input: input} l.readChar() return l } func (l *Lexer) readChar() { // 入力が終わったらchを0に if l.readPosition >= len(l.input) { l.ch = 0 // まだ終わっていない場合readPositionをchにセット } else { l.ch = l.input[l.readPosition] } // positionを次に進める l.position = l.readPosition // readpositonを次に進める l.readPosition += 1 } func (l *Lexer) NextToken() token.Token { var tok token.Token // トークンを生成する switch l.ch { case '=': tok = newToken(token.ASSIGN, l.ch) case '+': tok = newToken(token.PLUS, l.ch) case '-': tok = newToken(token.MINUS, l.ch) [...] l.readChar() return tok } func newToken(tokenType TokenType, ch byte) Token { return Token{Type: tokenType, Literal: string(ch)} } これでトークンの生成ができるようになったが、一文字づつトークンを生成するので let five = 5; let ten = 10; let add = fn(x, y) { x + y; }; let result = add(five, ten): 上記のような文を解析するにはまだ問題点がある 一文字づつしか解析できないので let(キーワード)でエラーを起こす 1 と同様に five(識別子)でエラー 数字の解析 まず初めに1、2の英字かどうかを判断する関数と非英字に到達するまで読み込みを進める関数の実装をする lexer.go // 非英字まで読み込みを進める func (l *Lexer) readIdentifier() string { position := l.position // for文でisLetter関数からfalseが返ってくるまで読み込みを進める for isLetter(l.ch) { l.readChar() } // 最初の文字から非英字まで進めたpositionまでを返す return l.input[position:l.position] } // 判断する関数 func isLetter(ch byte) bool { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' } この二つの関数を使い、NextToken()の switch 文に分岐を足してやると文字列のトークンを生成することに成功する 次に変数宣言にあたる let や関数の fn などを分別する必要がある token.go [...] FUNCTION = "FUNCTION" LET = "LET" var keywords = map[string]TokenType{ "fn": FUNCTION, "let": LET, } func LookupIdent(ident string) TokenType { // readIdentifierで読み込んだ文字列を引数にとり、キーワードに存在する場合は適切なTypeを返す if tok, ok := keywords[ident]; ok { return tok } // 識別子の場合 return IDENT } 数字のトークンだが isLetter と同じように分別する。コードは省略する 第一章の最後に REPL を実装する REPL の実装 REPL とは R Read(読み込み) E Eval(評価) P Print(表示) L Loop(繰り返し) REPL とは上記の略である。インタプリンタ言語では非常に馴染みのあるもので、入力を受け取り、評価し、結果を出力するのを繰り返しおこなう repl.go const PROMPO = ">>" func Start(in io.Reader, out io.Writer) { // scannerの生成 scanner := bufio.NewScanner(in) for { fmt.Printf(PROMPO) scanned := scanner.Scan() if !scanned { return } // 入力を受け取る line := scanner.Text() // 解析器に入力された文字列を入れる l := lexer.New(line) // 文字列を評価し出力、これを繰り返す。 for tok := l.NextToken(); tok.Type != token.EOF; tok = l.NextToken() { fmt.Printf("%+v\n", tok) } } } 以上で第一章を終わります。初めてこういう形で学習したものをまとめて発信することになりましたが、理解が深まりとても有意義な方法だと感じました。 第二章も続づけて更新していく予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】APIサーバの構築 ~GraphQL編~

はじめに APIサーバにGraphQLを導入してみたのでメモを残します。 コード全文は以下です。 GraphQL どこにでも書いてある情報ですが、GraphQLの導入メリットには以下のようなものがあります。 エンドポイントが1つでOK(RESTのようなエンドポイントの肥大化を防げる) クエリを用いて必要なデータのみを取ってくることができる(無駄な通信帯域を消費しない) データ型の定義がされていて、クライアント-サーバ間の食い違いを防げる 他にもGraphQL関連の記事を書いているので、もし興味があればご覧ください。 実装 GoでGraphQLを実装するにあたり、以下のライブラリを使用します。 導入 ライブラリをインストールします。 go get github.com/graphql-go/graphql ルートハンドラーの追加 routes.goにルートハンドラーを追加します。 RESTのときとは違いエンドポイントはこの一つだけでよく、スキーマ定義の追加修正などを行う場合はgraphql.goの中身をさわります。 routes.go router.HandlerFunc(http.MethodPost, "/v1/graphql", app.moviesGraphQL) スキーマ定義 graphql.goにスキーマ定義を記述します。 graphql.go package main import ( "backend/models" "encoding/json" "errors" "fmt" "io" "log" "net/http" "strings" "github.com/graphql-go/graphql" ) var movies []*models.Movie // スキーマ定義 var fields = graphql.Fields{ "movie": &graphql.Field{ Type: movieType, Description: "Get movie by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, ok := p.Args["id"].(int) if ok { for _, movie := range movies { if movie.ID == id { return movie, nil } } } return nil, nil }, }, "list": &graphql.Field{ Type: graphql.NewList(movieType), Description: "Get all movies", Resolve: func(params graphql.ResolveParams) (interface{}, error) { return movies, nil }, }, "search": &graphql.Field{ Type: graphql.NewList(movieType), Description: "Search movies by title", Args: graphql.FieldConfigArgument{ "titleContains": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { var theList []*models.Movie search, ok := params.Args["titleContains"].(string) if ok { for _, currentMovie := range movies { if strings.Contains(currentMovie.Title, search) { log.Println("Found one") theList = append(theList, currentMovie) } } } return theList, nil }, }, } var movieType = graphql.NewObject( graphql.ObjectConfig{ Name: "Movie", Fields: graphql.Fields { "id": &graphql.Field{ Type: graphql.Int, }, "title": &graphql.Field{ Type: graphql.String, }, "description": &graphql.Field{ Type: graphql.String, }, "year": &graphql.Field{ Type: graphql.Int, }, "release_date": &graphql.Field{ Type: graphql.DateTime, }, "runtime": &graphql.Field{ Type: graphql.Int, }, "rating": &graphql.Field{ Type: graphql.Int, }, "mpaa_rating": &graphql.Field{ Type: graphql.String, }, "created_at": &graphql.Field{ Type: graphql.DateTime, }, "updated_at": &graphql.Field{ Type: graphql.DateTime, }, }, }, ) func (app *application) moviesGraphQL(w http.ResponseWriter, r * http.Request) { // DBから全データを取得する movies, _ = app.models.DB.All() // リクエストボディを読み込んでクエリをつくる q, _ := io.ReadAll(r.Body) query := string(q) log.Println(query) // スキーマをつくる rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields} schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)} schema, err := graphql.NewSchema(schemaConfig) if err != nil { app.errorJSON(w, errors.New("failed to create schema")) log.Println(err) return } params := graphql.Params{Schema: schema, RequestString: query} resp := graphql.Do(params) if len(resp.Errors) > 0 { app.errorJSON(w, fmt.Errorf("failed: %+v", resp.Errors)) } j, _ := json.MarshalIndent(resp, "", " ") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(j) } fieldsにmovie list searchのスキーマを定義します。 Typeにはスキーマの型、Argsには引数の型、Resolveにはクエリの結果を記述します。 例えばmovieの場合であれば、Resolve内にArgsで取得したidと一致する結果を返すような処理を記述します。 Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, ok := p.Args["id"].(int) if ok { for _, movie := range movies { if movie.ID == id { return movie, nil } } } return nil, nil }, 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goroutineで並行処理のメモ

goroutineを使ったときのメモです。 channelで待ち合わせせずgoroutineを使う場合の注意 mainのgoroutineが終了すると、他のgoroutineが走っていてもプログラムは終了してしまうので注意しよう。待ち合わせをしたい場合はchannelを使う。 main.go package main import ( "fmt" "time" ) func main() { go func() { fmt.Println("start goroutine") time.Sleep(3 * time.Second) }() // time.Sleep(4 * time.Second) fmt.Println("finished") } 上記のコードを走らせた場合は以下のようになり、goroutineがそもそも開始されていないことがわかる。 $ go run main.go finished channelを使って待ち合わせした場合 main.go package main import ( "fmt" "time" ) func main() { done := make(chan bool) go func() { fmt.Println("start goroutine") time.Sleep(3 * time.Second) fmt.Println("finish goroutine") done <- true }() <-done fmt.Println("finished") } こちらの場合は以下のように出力される。 $ go run main.go start goroutine finish goroutine finished
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む