protocol-buffers-schema
.proto
파일을 jsonSchema transpile하는 도구를 작업하려고하면서
protobuf를 규칙에따라 js object형으로 파싱하는 것이 필요해졌다.
protocol-buffers-schema는 protobuf schema를 전환해주는 API를 js JSON객체 처럼 제공하고있어 이를 선택하게되었고, 이를 통해 proto의 구조를 좀더 구체적으로 파악해보고자한다.
var parse = function
parse(fs.readFileSync(filename, 'utf-8'))
우선 parse는 Buffer file을 인자로 받는다.
tokenize
tokenize(buf.toString())
buffer를 tokenize하는 과정을 갖게되는데
sch
.replace(/"(\\"|[^"\n])*?"|'(\\'|[^'\n])*?'/gm, removeQuotedLines(replacements))
.replace(/([;,{}()=:[\]<>]|\/\*|\*\/)/g, ' $1 ')
.split(/\n/)
.map(trim)
.filter(Boolean)
.map(noComments)
.map(trim)
.filter(Boolean)
.join('\n')
.split(/\s+|\n+/gm)
.filter(noMultilineComments())
.map(restoreQuotedLines(replacements))
Quoted 요소에 대해서 replacements 배열에 담아두고 trim, 주석제거, 개행제거 과정을 거친후 Quoted요소를 재조합하여 구문들을 token단위로 나누어 배열을 반환한다.
schema
var schema = {
syntax: 3,
package: null,
imports: [],
enums: [],
messages: [],
options: {},
extends: []
}
protocol-buffers-schema는 proto3 를 default syntax
로 이용하고있다.
proto3가면서의 변경점
https://www.crankuptheamps.com/blog/posts/2017/10/12/protobuf-battle-of-the-syntaxes/#
https://github.com/protocolbuffers/protobuf/releases/tag/v3.0.0
proto의 root에 들어갈 수 있는 기본적인 형태의 문법을 확인할수있다.
read tokens
while
을 통해 tokens를 전체 탐색과정을 갖는데 switch문을 통해 구문의 operation을 파악하고 해당 구문의 내용을 tokens.shift()
를 통해 인자를 인출해주어 원하는 정보를 schema에 담는다.
case 'package'
var onpackagename = function (tokens) {
tokens.shift()
var name = tokens.shift()
if (tokens[0] !== ';') throw new Error('Expected ; but found ' + tokens[0])
tokens.shift()
return name
}
package는 프로젝트 이름을 기반으로하는 고유한 이름을 가져가며,
가능하면 protocol buffer type 정의를 포함하는 파일 경로를 기반하는게 좋다.
case 'syntax'
proto의 버전을 명시.
var onsyntaxversion = function (tokens) {
...
switch (version) {
case '"proto2"':
version = 2
break
case '"proto3"':
version = 3
break
default:
throw new Error('Expected protobuf syntax version but found ' + version)
}
...
return version
}
case 'message'
message필드는 하위에 계속하여 root에서와 동일한 field들을 포함하며, message name에 대해서만 추가적인 형태이다.
따라서 onmessagebody
를 재귀적으로 호출하며 msg를 읽어나가게 된다.
var onmessage = function (tokens) {
tokens.shift()
var lvl = 1
var body = []
var msg = {
name: tokens.shift(),
options: {},
enums: [],
extends: [],
messages: [],
fields: []
}
if (tokens[0] !== '{') throw new Error('Expected { but found ' + tokens[0])
tokens.shift()
while (tokens.length) {
if (tokens[0] === '{') lvl++
else if (tokens[0] === '}') lvl--
if (!lvl) {
tokens.shift()
body = onmessagebody(body)
msg.enums = body.enums
msg.messages = body.messages
msg.fields = body.fields
msg.extends = body.extends
msg.extensions = body.extensions
msg.options = body.options
return msg
}
body.push(tokens.shift())
}
if (lvl) throw new Error('No closing tag for message')
}
메시지 이름은 CamelCase를 사용하며 필드이름들에는 underscore_separated_names을 사용해야합니다.
필드 이름에 숫자가 포함된 경우에는 숫자는 밑줄을 치지않고 문자 뒤표기합니다.
예를 들어 song_name_1 대신 song_name1을 사용해줍니다.
case 'enum'
enum필드의 구분자는 쉼표가 아닌 세미콜론으로 이를 기준으로 확인.
var onenum = function (tokens) {
tokens.shift()
var options = {}
var e = {
name: tokens.shift(),
values: {},
options: {}
}
if (tokens[0] !== '{') throw new Error('Expected { but found ' + tokens[0])
tokens.shift()
while (tokens.length) {
if (tokens[0] === '}') {
tokens.shift()
// there goes optional semicolon after the enclosing "}"
if (tokens[0] === ';') tokens.shift()
return e
}
if (tokens[0] === 'option') {
options = onoption(tokens)
e.options[options.name] = options.value
continue
}
var val = onenumvalue(tokens)
if (val !== null) {
e.values[val.name] = val.val
}
}
throw new Error('No closing tag for enum')
}
enum은 여러 값을 갖는 필드들을 갖기에 onenumvalue
를 각 필드내에서 호출하며 각 필드별로 option
필드를 지녀 onoption
을 호출한다.
enum name은 CamelCase를 사용하며 값에는 CAPITALS_WITH_UNDERSCORES를 활용한다.
- case 'option'
- case 'import'
- case 'extend'
- case 'service'
1일 1포스트를 지향하기에 남은 타입은 내일 이어서 쓰려고한다.
'개발' 카테고리의 다른 글
userinyerface를 통한 고통 맛보기 Page 2 (3) (2) | 2022.04.20 |
---|---|
userinyerface를 통한 고통 맛보기 Page 2 (2) (0) | 2022.04.19 |
userinyerface를 통한 고통 맛보기 Page 2 (1) (0) | 2022.04.18 |
userinyerface를 통한 고통 맛보기 Page 1 (1) | 2022.04.17 |
protocol-buffers-schema 로 보는 proto 구조 이해하기(2) (0) | 2022.04.16 |