사용 가능한 JVM 언어 생성: 개요

게시 됨: 2022-03-11

언어를 만드는 데에는 몇 가지 가능한 이유가 있으며 그 중 일부는 즉시 명확하지 않습니다. 기존 도구를 최대한 활용하여 JVM(Java Virtual Machine)용 언어를 만드는 접근 방식과 함께 제시하고자 합니다. 이러한 방식으로 우리는 개발 노력을 줄이고 사용자에게 친숙한 도구 체인을 제공하여 새로운 프로그래밍 언어를 더 쉽게 채택할 수 있도록 할 것입니다.

사용 가능한 JVM 언어 생성: 개요

시리즈의 첫 번째 기사인 이 기사에서는 JVM을 위한 자체 프로그래밍 언어를 만드는 데 관련된 전략과 다양한 도구에 대한 개요를 제시합니다. 향후 기사에서 구현 세부 사항에 대해 자세히 알아볼 것입니다.

JVM 언어를 만드는 이유는 무엇입니까?

이미 무한한 수의 프로그래밍 언어가 있습니다. 그렇다면 왜 귀찮게 새로 만들까요? 이에 대한 가능한 답변이 많이 있습니다.

우선, 다양한 종류의 언어가 있습니다. 범용 프로그래밍 언어(GPL)를 생성하시겠습니까? 아니면 도메인 전용 프로그래밍 언어를 생성하시겠습니까? 첫 번째 종류에는 Java 또는 Scala와 같은 언어가 포함됩니다. 많은 문제에 대한 적절한 솔루션을 작성하기 위한 언어입니다. DSL(Domain Specific Languages)은 대신 ​​특정 문제 집합을 매우 잘 해결하는 데 중점을 둡니다. HTML 또는 Latex를 생각해 보십시오. 화면에 그림을 그리거나 Java로 문서를 생성할 수 있지만 번거롭습니다. 대신 이러한 DSL을 사용하면 문서를 매우 쉽게 생성할 수 있지만 특정 도메인으로 제한됩니다.

따라서 매우 자주 작업하고 DSL을 만드는 것이 합리적일 수 있는 일련의 문제가 있을 수 있습니다. 같은 종류의 문제를 계속해서 푸는 동안 생산성을 높일 수 있는 언어입니다.

예를 들어 관계를 일급 시민으로 나타내거나 컨텍스트를 나타내는 것과 같은 몇 가지 새로운 아이디어가 있었기 때문에 대신 GPL을 만들고 싶을 수도 있습니다.

마지막으로, 재미있고 멋지고 그 과정에서 많은 것을 배울 것이기 때문에 새로운 언어를 만들고 싶을 수도 있습니다.

사실은 JVM을 대상으로 하는 경우 적은 노력으로 사용 가능한 언어를 얻을 수 있다는 것입니다. 그 이유는 다음과 같습니다.

  • 바이트코드를 생성하기만 하면 JVM이 있는 모든 플랫폼에서 코드를 사용할 수 있습니다.
  • JVM용으로 존재하는 모든 라이브러리와 프레임워크를 활용할 수 있습니다.

따라서 JVM에서 언어 개발 비용이 크게 감소하고 JVM 외부에서 비경제적인 시나리오에서 새 언어를 만드는 것이 합리적일 수 있습니다.

사용할 수 있게 하려면 무엇이 필요합니까?

언어를 사용하는 데 절대적으로 필요한 몇 가지 도구가 있습니다. 이러한 도구에는 파서와 컴파일러(또는 인터프리터)가 있습니다. 그러나 이것으로 충분하지 않습니다. 실제로 사용할 수 있는 언어를 만들려면 기존 도구와 통합할 수 있는 도구 체인의 다른 많은 구성 요소를 제공해야 합니다.

이상적으로는 다음을 수행할 수 있습니다.

  • 다른 언어에서 JVM용으로 컴파일된 코드에 대한 참조 관리
  • 구문 강조 표시, 오류 식별 및 자동 완성 기능을 사용하여 즐겨 사용하는 IDE에서 소스 파일 편집
  • 선호하는 빌드 시스템(maven, gradle 또는 기타)을 사용하여 파일을 컴파일할 수 있기를 원합니다.
  • 지속적 통합 솔루션의 일부로 테스트를 작성하고 실행할 수 있기를 원합니다.

당신이 그렇게 할 수 있다면, 당신의 언어를 채택하는 것이 훨씬 쉬울 것입니다.

그렇다면 어떻게 달성할 수 있습니까? 포스트의 나머지 부분에서 우리는 이것을 가능하게 하는 데 필요한 다른 부분들을 조사합니다.

구문 분석 및 컴파일

프로그램에서 소스 파일을 변환하기 위해 가장 먼저 해야 할 일은 파일을 구문 분석하여 코드에 포함된 정보의 AST(Abstract-Syntax-Tree) 표현을 얻는 것입니다. 이 시점에서 코드의 유효성을 검사해야 합니다. 구문 오류가 있습니까? 의미 오류? 모두 찾아서 사용자에게 보고해야 합니다. 모든 것이 순조롭게 진행된다면 여전히 기호를 해결해야 합니다. 예를 들어, "목록"은 java.util.List 또는 java.awt.List 를 참조합니까? 오버로드된 메서드를 호출할 때 어떤 메서드를 호출하고 있습니까? 마지막으로 프로그램에 대한 바이트 코드를 생성해야 합니다.

따라서 소스 코드에서 컴파일된 바이트 코드까지 세 가지 주요 단계가 있습니다.

  1. AST 구축
  2. AST 분석 및 변형
  3. AST에서 바이트코드 생성

해당 단계를 자세히 살펴보겠습니다.

AST 구축 : 파싱은 일종의 해결된 문제입니다. 많은 프레임워크가 있지만 ANTLR을 사용하는 것이 좋습니다. 잘 알려져 있고 잘 유지 관리되며 문법을 쉽게 지정할 수 있는 몇 가지 기능이 있습니다.

AST 분석 및 변환 : 유형 시스템 작성, 유효성 검사 및 기호 확인은 어려울 수 있으며 많은 작업이 필요할 수 있습니다. 이 주제만으로도 별도의 게시물이 필요합니다. 지금은 이것이 대부분의 노력을 기울일 컴파일러의 일부라고 생각하십시오.

AST에서 바이트코드 생성 : 이 마지막 단계는 실제로 그렇게 어렵지 않습니다. 기본적으로 변환된 AST의 단일 노드를 하나 또는 몇 개의 바이트코드 명령어로 변환할 수 있도록 이전 단계에서 기호를 해석하고 지형을 준비해야 합니다. 제어 구조는 for-loop, 스위치, if 등을 일련의 조건부 및 무조건 점프로 번역하기 때문에 약간의 추가 작업이 필요할 수 있습니다(예, 아름다운 언어 아래에는 여전히 많은 goto가 있을 것입니다). JVM이 내부적으로 어떻게 작동하는지 배워야 하지만 실제 구현은 그렇게 어렵지 않습니다.

다른 언어와의 통합

당신의 언어에 대한 세계 지배권을 얻게 되면 모든 코드는 그 언어를 사용하여 작성될 것입니다. 그러나 중간 단계로 귀하의 언어는 다른 JVM 언어와 함께 사용될 것입니다. 아마도 누군가는 더 큰 프로젝트 내에서 당신의 언어로 몇 개의 클래스나 작은 모듈을 작성하기 시작할 것입니다. 여러 JVM 언어를 혼합할 수 있을 것으로 예상하는 것이 합리적입니다. 그렇다면 이것이 언어 도구에 어떤 영향을 미칩니까?

두 가지 시나리오를 고려해야 합니다.

  • 귀하의 언어와 다른 언어는 별도로 컴파일된 모듈에 있습니다.
  • 귀하의 언어와 다른 언어는 동일한 모듈에 살고 함께 컴파일됩니다.

첫 번째 시나리오에서 코드는 다른 언어로 작성된 컴파일된 코드만 사용하면 됩니다. 예를 들어 Guava 또는 동일한 프로젝트의 모듈과 같은 일부 종속성은 별도로 컴파일할 수 있습니다. 이러한 종류의 통합에는 두 가지가 필요합니다. 첫째, 다른 언어에서 생성된 클래스 파일을 해석하여 해당 클래스에 대한 기호를 해석하고 해당 클래스를 호출하기 위한 바이트코드를 생성할 수 있어야 합니다. 두 번째 요점은 첫 번째 요점에 대해 반사적입니다. 다른 모듈은 컴파일된 후 사용자 언어로 작성된 코드를 재사용하기를 원할 수 있습니다. 이제 Java는 대부분의 클래스 파일과 상호 작용할 수 있기 때문에 일반적으로 문제가 되지 않습니다. 그러나 JVM에 유효하지만 Java에서 호출할 수 없는 클래스 파일을 작성하는 것은 여전히 ​​관리할 수 있습니다(예: Java에서 유효하지 않은 식별자를 사용하기 때문에).

두 번째 시나리오는 더 복잡합니다. Java 코드로 정의된 클래스 A와 사용자 언어로 작성된 클래스 B가 있다고 가정합니다. 두 클래스가 서로를 참조한다고 가정합니다(예를 들어 A는 B를 확장할 수 있고 B는 A를 동일한 메소드에 대한 매개변수로 받아들일 수 있음). 이제 요점은 Java 컴파일러가 사용자 언어의 코드를 처리할 수 없으므로 클래스 B에 대한 클래스 파일을 제공해야 한다는 것입니다. 그러나 클래스 B를 컴파일하려면 클래스 A에 대한 참조를 삽입해야 합니다. 따라서 해야 할 일은 자바 소스 파일이 주어지면 이를 해석하고 클래스 B를 컴파일하는 데 사용할 수 있는 모델을 생성할 수 있는 일종의 부분 자바 컴파일러를 갖습니다. JavaParser와 같은 것) 및 기호를 해결합니다. 어디서부터 시작해야 할지 모르겠다면 java-symbol-solver를 살펴보십시오.

도구: Gradle, Maven, 테스트 프레임워크, CI

좋은 소식은 gradle 또는 maven용 플러그인을 개발하여 그들이 당신의 언어로 작성된 모듈을 사용하고 있다는 사실을 사용자에게 완전히 투명하게 만들 수 있다는 것입니다. 빌드 시스템에 프로그래밍 언어로 파일을 컴파일하도록 지시할 수 있습니다. 사용자는 mvn compile 또는 gradle assemble을 계속 실행하고 차이를 느끼지 못할 것입니다.

나쁜 소식은 Maven 플러그인을 작성하는 것이 쉽지 않다는 것입니다. 문서가 매우 열악하고 이해하기 어려우며 대부분 구식이거나 단순히 잘못 되었습니다. 예, 위로가 되지 않습니다. 아직 gradle 플러그인을 작성하지 않았지만 훨씬 쉬워 보입니다.

빌드 시스템을 사용하여 테스트를 실행할 수 있는 방법도 고려해야 합니다. 테스트를 지원하려면 단위 테스트를 위한 매우 기본적인 프레임워크를 생각해야 하고 빌드 시스템과 통합해야 합니다. 그러면 실행 중인 maven 테스트가 사용자 언어로 된 테스트를 찾고 컴파일하고 실행하여 사용자에게 출력을 보고할 수 있습니다.

제 조언은 사용 가능한 예제를 살펴보는 것입니다. 그 중 하나는 Turin 프로그래밍 언어용 Maven 플러그인입니다.

일단 구현하면 모든 사람이 귀하의 언어로 작성된 소스 파일을 쉽게 컴파일하고 Travis와 같은 지속적인 통합 서비스에서 사용할 수 있어야 합니다.

IDE 플러그인

IDE용 플러그인은 사용자에게 가장 눈에 띄는 도구이며 언어 인식에 큰 영향을 미칠 것입니다. 좋은 플러그인은 스마트 자동 완성, 컨텍스트 오류 및 제안된 리팩토링을 제공하여 사용자가 언어를 배우는 데 도움이 될 수 있습니다.

이제 가장 일반적인 전략은 하나의 IDE(일반적으로 Eclipse 또는 IntelliJ IDEA)를 선택하고 이에 대한 특정 플러그인을 개발하는 것입니다. 이것은 아마도 도구 체인에서 가장 복잡한 부분일 것입니다. 이것은 여러 가지 이유 때문에 그렇습니다. 우선 한 IDE용 플러그인을 다른 IDE용으로 개발하는 데 소비할 작업을 합리적으로 재사용할 수 없습니다. Eclipse와 IntelliJ 플러그인은 완전히 분리됩니다. 두 번째 요점은 IDE 플러그인 개발이 그리 흔하지 않아서 문서가 많지 않고 커뮤니티가 작다는 것입니다. 그것은 당신이 스스로를 알아내는 데 많은 시간을 할애해야 함을 의미합니다. Eclipse 및 IntelliJ IDEA용 플러그인을 개인적으로 개발했습니다. Eclipse 포럼에 대한 내 질문은 몇 달 또는 몇 년 동안 답변이 없는 상태로 유지되었습니다. IntelliJ 포럼에서는 운이 더 좋았고 때때로 개발자로부터 답변을 받았습니다. 그러나 플러그인 개발자의 사용자 기반은 더 작고 API는 매우 비잔틴적입니다. 고통을 준비하십시오.

이 모든 것에 대한 대안이 있으며 Xtext를 사용하는 것입니다. Xtext는 Eclipse, IntelliJ IDEA 및 웹용 플러그인을 개발하기 위한 프레임워크입니다. Eclipse에서 태어 났고 다른 플랫폼을 지원하기 위해 최근에 확장되었으므로 이에 대한 경험이 많지 않지만 고려할 가치가 있는 대안이 될 수 있습니다. 이 점을 분명히 해두겠습니다. 아주 좋은 플러그인을 개발하는 유일한 방법은 각 IDE의 기본 API를 사용하여 개발하는 것입니다. 그러나 Xtext를 사용하면 약간의 노력으로 합리적으로 괜찮은 것을 가질 수 있습니다. 언어의 구문에 제공하기만 하면 구문 오류/완성을 무료로 얻을 수 있습니다. 그래도 심볼 해상도와 어려운 부분을 구현해야 하지만 이것은 매우 흥미로운 출발점입니다. 그러나 하드 비트는 Java 기호를 해결하기 위한 플랫폼별 라이브러리와의 통합이므로 이것이 모든 문제를 실제로 해결하지는 못합니다.

결론

귀하의 언어에 관심을 보인 잠재 사용자를 잃을 수 있는 방법은 여러 가지가 있습니다. 새로운 언어를 채택하는 것은 그것을 배우고 우리의 발달 습관에 적응해야 하기 때문에 어려운 일입니다. 소모를 최대한 줄이고 사용자에게 이미 알려진 생태계를 활용함으로써 사용자가 언어를 배우고 사랑에 빠지기 전에 포기하지 않도록 할 수 있습니다.

이상적인 시나리오에서 사용자는 귀하의 언어로 작성된 간단한 프로젝트를 복제하고 차이를 느끼지 않고 표준 도구(Maven 또는 Gradle)를 사용하여 빌드할 수 있습니다. 그가 프로젝트를 편집하고 싶다면 즐겨찾는 편집기에서 프로젝트를 열 수 있으며 플러그인은 그에게 오류를 지적하고 스마트한 완성 기능을 제공하는 데 도움이 될 것입니다. 이것은 컴파일러를 호출하고 메모장을 사용하여 파일을 편집하는 방법을 알아내야 하는 것과는 매우 다른 시나리오입니다. 당신의 언어를 둘러싼 생태계는 정말로 차이를 만들 수 있으며, 요즘에는 합리적인 노력으로 구축할 수 있습니다.

내 조언은 당신의 언어로 창의적이되, 당신의 도구가 아닌 것입니다. 친숙한 표준을 사용하여 사람들이 귀하의 언어를 채택하기 위해 직면해야 하는 초기 어려움을 줄이십시오.

행복한 언어 디자인!


Toptal 엔지니어링 블로그에 대한 추가 정보:

  • 처음부터 통역사 작성에 접근하는 방법