각도 컴파일러는 무엇을 "컴파일"합니까?


88

오늘 그 질문을 받았는데 올바른 대답을 할 수 없었습니다.

Typescript는 JS로 트랜스 파일됩니다. 그런 다음 트리 흔들림, "적음"(선택 사항) 및 배포 프로세스에서 다른 작업이 있습니다. 그러나 그와 같은 (afaik)은 "컴파일"과 관련이 없습니다. 모든 것이 번들로 제공되고 고도로 최적화되지만 실제로 컴파일되지는 않습니다.

실제로 눈에 띄는 작업을 수행하는 "미리 시간"컴파일러도 있습니다. 내가 뭘 그리워?

Javascript 자체는 여전히 해석됩니다.


6
나는 "실제로 컴파일하지 않음"에 동의합니다. 본질적으로 컴파일 정의의 문제입니다 . 어떤 사람들 은 TypeScript에서 JavaScript 로의 변환을 표시하기 위해 transpilation 이라는 단어를 사용하는 것을 선호합니다 . 하지만 그래, 본질적으로, 타이프 스크립트 컴파일러의 역할은 단지 타이프에서 자바 스크립트를 생성 할 수 있습니다.
Pac0

6
@ Pac0 여기에서 뭔가 오해 할 수도 있지만 TypeScript에서 JavaScript 로의 트랜스 파일이라면 GCC는 C to 기계 코드 트랜스 파일러가 아닐까요? 트랜스 파일러와 컴파일러의 차이점을 어떻게 정의 하시겠습니까?
11684 10.10.

24
트랜스 파일러는 컴파일러입니다
user253751

5
사람들이 "트랜스 파일러"라고 말하는 것은 무엇을 의미합니까?를 참조하십시오 . (그리고 "My first 15 compilers" ) ( 컴파일러 에서 일하는 사람의), "컴파일러"가 이와 같은 일에 사용하기에 좋은 단어라고 주장합니다.
ShreevatsaR

2
자바 스크립트 자체는 여전히 해석되어 있지 않습니까? - 더 이상, 그것은 V8 엔진에 의해 즉석에서 기계어 코드로 컴파일 아니에요
최대 Koretskyi

답변:


91

컴파일은 소스 코드를 가져와 기계 코드, 저수준 코드 등을 생성하는 것을 의미한다고 가정하고 있습니다. 그러나 실제로 컴파일은 하나의 소스 코드를 다른 소스 코드로 변환하는 것을 의미합니다. 따라서 Typescript를 가져와 JavaScript를 생성하는 것이 컴파일의 한 형태 라고 말하는 것이 합리적 입니다. 예를 들어 C #이 IL 언어로 컴파일 될 때 수행하는 작업과 다르지 않습니다.

즉, 이에 대한 더 나은 단어는 Transpiling 입니다. Typescript 컴파일러가 Transpiler로 더 잘 설명되어 있다고 제안합니다 .

차이점은 미묘하며 트랜스 파일러는 컴파일러의 한 유형으로 생각할 수 있습니다. 그러나 (순수한) 컴파일 된 언어는 C # 예제와 같이 (보통) 고수준 언어를 (기계 코드에 가까운) 저수준 언어로 바꾸는 것입니다. 트랜스 파일러는 높은 수준의 언어를 유사한 수준 (추상화) 언어 (또한 높은 수준)로 변환합니다. *

컴파일 된 코드의 결과는 일반적으로 사용자가 직접 작성하는 언어가 아닙니다 . 트랜스 파일러의 결과는 또 다른 고급 언어입니다. 이론적으로 IL (예제)을 작성할 수 있지만 실제로는 컴파일러에 의해 생성되도록 설계되었으며이를 수행하기위한 도구 나 지원이 없으며 C # / vb.net 만 컴파일하여 IL을 생성합니다. Javascript는 그 자체로 사용 가능한 (그리고 사용되는) 프로그래밍 언어입니다.

*이 단어의 정의와 사용법이 매우 모호하므로주의해야 할 사항이 많습니다.


12
JavaScript는 TypeScript보다 엄격하게 낮은 수준이 아닙니까?
Bergi

3
이 답변의 모든 것이 정확하고 도움이되지만 특히 컴파일의 정의는 항상 혼란 스럽기 때문에 제목의 질문에 대답하지는 않습니다. 이 답변은 TypeScript에 대해서만 이야기하고 질문은 Angular에 관한 것입니다. 그 차이는 엄청납니다. TS가 어떤 것인지 알지 못해도 Angular를 사용할 수 있습니다. 이 답변이 받아 들여 져서 놀랐습니다.
Pedro A

3
컴파일러는 다른 프로그램을 생성하기 위해 기본적으로 전체 프로그램을 이해해야합니다 (일반적으로 동일한 작업을 수행하지만 다른 언어로-여기에는 기계 코드 포함).
Thorbjørn Ravn Andersen

8
나는 그것을 두 번 읽었고 여기에서 질문에 대한 답을 찾을 수 없었습니다. 답은 바로 아래에 있습니다.
kuncevic.dev

5
OP가 묻고있는 암시적인 질문은 "Angular 컴파일러를 컴파일러라고 부르는 것이 맞습니까?"였습니다. -이것이 바로이 답변의 답입니다. 그래서 나에게서 +1. 사람들이 "트랜스 파일러"라고 말하는 것은 무엇을 의미합니까?를 참조하십시오 . 그리고 후속 “내 첫 15 명의 컴파일러” .
ShreevatsaR

70

한 번에 세 가지 질문을하는 것 같습니다.

  • 컴파일러와 트랜스 파일러의 차이점은 무엇입니까?
  • Angular와 TypeScript는 컴파일러 나 트랜스 파일러를 구현합니까?
  • 별도의 Angular 컴파일러가 있습니까? 무엇을 컴파일합니까?

컴파일러와 트랜스 파일러의 차이점은 무엇입니까?

@ JörgWMittag 이 질문에 대한 아주 좋은 대답제공했습니다 .

Angular와 TypeScript는 컴파일러 나 트랜스 파일러를 구현합니까?

TS와 Angular는 모두 실제 컴파일러를 구현 합니다. 어셈블리 코드를 생성하는 C / C ++ 컴파일러와 동일한 어휘 분석, 구문 분석, 의미 분석 및 코드 생성 단계를 따릅니다 (최적화 제외). 클래스 / 폴더의 이름이 AngularTS 모두에서 "compiler"인 것을 볼 수 있습니다 .

각도 컴파일러는 실제로 TypeScript 컴파일러와 관련이 없습니다. 이들은 매우 다른 컴파일러입니다.

별도의 Angular 컴파일러가 있습니까? 무엇을 컴파일합니까?

Angular에는 두 개의 컴파일러가 있습니다.

  • 컴파일러보기
  • 모듈 컴파일러

뷰 컴파일러의 역할은 컴포넌트 템플릿에 대해 지정한 템플릿을 뷰 인스턴스 를 인스턴스화하는 데 사용되는 뷰 팩토리 인 컴포넌트의 내부 표현으로 변환하는 것입니다 .

템플릿 변환 외에, 뷰 컴파일러는 장식 등의 형태로 다양한 메타 데이터 정보를 컴파일 @HostBinding, @ViewChild등등

다음과 같이 구성 요소와 해당 템플릿을 정의한다고 가정합니다.

@Component({
  selector: 'a-comp',
  template: '<span>A Component</span>'
})
class AComponent {}

이 데이터를 사용하여 컴파일러는 다음과 같이 약간 단순화 된 구성 요소 팩토리를 생성합니다.

function View_AComponent {
  return jit_viewDef1(0,[
      elementDef2(0,null,null,1,'span',...),
      jit_textDef3(null,['My name is ',...])
    ]

구성 요소보기의 구조를 설명하며 구성 요소를 인스턴스화 할 때 사용됩니다. 첫 번째 노드는 요소 정의이고 두 번째 노드는 텍스트 정의입니다. 각 노드는 매개 변수 목록을 통해 인스턴스화 될 때 필요한 정보를 얻는 것을 볼 수 있습니다. 필요한 모든 종속성을 해결하고 런타임에 제공하는 것은 컴파일러의 역할입니다.

다음 기사를 읽는 것이 좋습니다.

또한 Angular AOT와 JIT 컴파일러의 차이점은 무엇입니까?에 대한 답변을 참조하십시오 .

모듈 컴파일러의 역할은 기본적으로 공급자의 병합 된 정의를 포함하는 모듈 팩토리를 만드는 것입니다.

자세한 내용은 다음을 참조하십시오.


1
@codepleb, Bergi 의이 답변
Max Koretskyi

1
@codepleb GCC와 다른 많은 컴파일러는 기계 코드를 전혀 생성하지 않습니다. 실제로 GCC는 시스템을 자동으로 호출하여 기계 코드를 생성하지만 외부 도움말없이 생성하는 코드는 어셈블리 일 뿐이며 외부 어셈블러에 전달됩니다.
prosfilaes

7
@codepleb이 답변은 훨씬 우수하며 실제로 귀하의 질문에 대한 답변입니다. 원래의 판단을 재고 할 시간이 아직 있습니다.
async

3
@codepleb "트랜스 파일러"라는 용어가 존재하거나 존재할 이유가 없습니다. 오해를 불러 일으키기 만하면됩니다.
Leushenko 2017-10-11

2
@stom, 죄송합니다. 질문이 너무 광범위합니다. 가장 upvoted 대답은 꽤 좋은 비록입니다
최대 Koretskyi

54

Typescript는 JS로 전송됩니다. 그런 다음 트리 흔들림, "적음"(선택 사항) 및 배포 프로세스에서 다른 작업이 있습니다. 그러나 그런 (afaik)은 "컴파일"과 관련이 없습니다. 모든 것이 번들로 묶여지고 고도로 최적화되지만 실제로 컴파일되지는 않습니다.

컴파일 이란 언어 A 로 작성된 프로그램을 언어 B로 작성된 의미 상 동등한 프로그램으로 변환 하여 컴파일 된 프로그램을 언어 B 의 규칙에 따라 평가 (예 : B에 대한 인터프리터로 해석 )하면 동일한 결과를 산출하고 언어 A 의 규칙에 따라 원래 프로그램을 평가하는 것과 동일한 부작용 (예 : A 의 통역사와 함께 해석 ).

컴파일이란 단순히 프로그램을 A 언어에서 B 언어로 번역하는 것을 의미 합니다. 그게 전부입니다. (또한 AB 가 같은 언어 일 수도 있습니다.)

어떤 경우에는 AB 가 무엇인지, 컴파일러가 하는 일에 따라 특정 종류의 컴파일러에 대해 더 전문화 된 이름 이 있습니다.

  • 경우 A는 어셈블리 언어로 인식되고 B는 기계 언어로 인식되고, 우리는 그것에게 전화 어셈블러 ,
  • 경우 A는 기계 언어로 인식되고 B는 어셈블리 언어로 인식되고, 우리가 부르는 디스어셈블러 ,
  • 경우 A는 보다 낮은 수준으로 인식 B , 우리가 부르는 디 컴파일러 ,
  • 경우 와 B는 같은 언어이며, 그 결과 프로그램이 어떤 방식으로 빠르거나 가볍다, 우리는 그것을 호출 최적화 ,
  • 경우 와 B는 같은 언어이며, 그 결과 프로그램이 작은, 우리가 부르는 minifier ,
  • 경우 와 B는 같은 언어이며, 그 결과 프로그램이 덜 읽을 수있는, 우리가 그것에게 전화를 난독 ,
  • 만약 및 B는 추상화 대략 동일한 레벨로 인식되고, 우리는 호출 transpiler
  • 만약 및 B는 추상화 거의 같은 수준에있는 것으로 인식하고, 생성 된 프로그램의 보존은 코멘트를 포맷하고, 프로그래머 의도 그러한되어 그러므로 우리는 호출 원 프로그램과 동일한 방식으로 생성 된 프로그램을 유지하는 것이 가능하다는 것을 그것을 재 설계 도구 .

또한 이전 소스에서는 "컴파일"및 "컴파일러"대신 "번역"및 "번역자"라는 용어를 사용할 수 있습니다. 예를 들어 C는 "번역 단위"에 대해 말합니다.

"언어 프로세서"라는 용어를 우연히 발견 할 수도 있습니다. 이는 정의에 따라 컴파일러, 인터프리터 또는 컴파일러와 인터프리터 모두를 의미 할 수 있습니다.

Javascript 자체는 여전히 해석됩니다.

JavaScript는 언어입니다. 언어는 논리적 규칙 및 제한의 집합입니다. 언어는 해석되거나 컴파일되지 않습니다. 언어 .

컴파일과 해석은 컴파일러 나 인터프리터의 특성입니다 (duh!). 모든 언어는 컴파일러로 구현할 수 있으며 모든 언어는 인터프리터로 구현할 수 있습니다. 많은 언어에는 컴파일러와 인터프리터가 모두 있습니다. 많은 최신 고성능 실행 엔진에는 하나 이상의 컴파일러와 하나 이상의 인터프리터가 모두 있습니다.

이 두 용어는 서로 다른 추상화 계층에 속합니다. 영어가 입력 된 언어 인 경우 "interpreted-language"는 유형 오류입니다.

일부 언어에는 인터프리터 나 컴파일러가 없습니다. 전혀 구현되지 않은 언어가 있습니다. 그럼에도 불구하고 그것들은 언어이며 프로그램을 작성할 수 있습니다. 당신은 그들을 실행할 수 없습니다.

또한 모든 것이 어느 시점 에서 해석된다는 점에 유의 하십시오. 무언가를 실행 하려면 해석 해야 합니다. 컴파일은 한 언어에서 다른 언어로 코드를 번역합니다. 그것을 실행하지 않습니다. 통역 이 그것을 실행합니다. (때때로 인터프리터가 하드웨어로 구현 될 때이를 "CPU"라고 부르지 만 여전히 인터프리터입니다.)

적절한 사례 : 현재 존재하는 모든 주류 JavaScript 구현에는 컴파일러가 있습니다.

V8은 순수한 컴파일러로 시작했습니다. 자바 스크립트를 적당히 최적화 된 네이티브 머신 코드로 직접 컴파일했습니다. 나중에 두 번째 컴파일러가 추가되었습니다. 이제 두 가지 컴파일러가 있습니다. 어느 정도 최적화 된 코드를 생성하는 경량 컴파일러이지만 컴파일러 자체는 매우 빠르며 RAM을 거의 사용하지 않습니다. 이 컴파일러는 또한 프로파일 링 코드를 컴파일 된 코드에 삽입합니다. 두 번째 컴파일러는 더 무겁고 느리고 비용이 많이 드는 컴파일러이지만 훨씬 더 단단하고 훨씬 빠른 코드를 생성합니다. 또한 첫 번째 컴파일러가 주입 한 프로파일 링 코드의 결과를 사용하여 동적 최적화 결정을 내립니다. 또한 두 번째 컴파일러를 사용하여 재 컴파일 할 코드는 해당 프로파일 링 정보를 기반으로 결정됩니다. 통역사가 관여하지 않습니다. V8은 해석하지 않고 항상 컴파일합니다. 그것은 통역사도 포함합니다. (사실 요즘에는 처음 두 번의 반복을 설명하고 있다고 생각합니다.)

SpiderMonkey는 JavaScript를 SpiderMonkey 바이트 코드로 컴파일 한 다음 해석합니다. 인터프리터는 또한 코드를 프로파일 링 한 다음 가장 자주 실행되는 코드는 컴파일러에 의해 원시 기계 코드로 컴파일됩니다. 따라서 SpiderMonkey에는 두 개의 컴파일러 가 포함되어 있습니다 . 하나는 JavaScript에서 SpiderMonkey 바이트 코드로, 다른 하나는 SpiderMonkey 바이트 코드에서 원시 기계 코드로.

거의 모든 JavaScript 실행 엔진 (V8 제외)은 JavaScript를 바이트 코드로 컴파일하는이 AOT 컴파일러 모델과 해당 바이트 코드의 해석과 컴파일 사이를 전환하는 혼합 모드 엔진을 따릅니다.

댓글에 다음과 같이 썼습니다.

기계 코드가 어딘가에 관련되어 있다고 생각했습니다.

"머신 코드"는 무엇을 의미합니까?

한 사람의 기계어가 다른 사람의 중급 어이고 그 반대의 경우는 무엇입니까? 예를 들어 기본적으로 JVM 바이트 코드를 실행할 수있는 CPU가 있습니다. 이러한 CPU에서 JVM 바이트 코드 기본 기계 코드입니다. 그리고 x86 기계어 코드를 실행하면 해당 x86 기계어 코드 바이트 코드 해석되는 인터프리터가 있습니다.

Java로 작성된 JPC라는 x86 인터프리터가 있습니다. 네이티브 JVM CPU에서 실행되는 JPC에서 x86 기계어 코드를 실행하면… 바이트 코드와 네이티브 코드는 무엇입니까? x86 기계어 코드를 JavaScript로 컴파일하고 (예,이를 수행 할 수있는 도구가 있습니다.) 내 폰 (ARM CPU가있는)의 브라우저에서 실행하면 바이트 코드이고 기본 기계어 코드는 무엇입니까? 내가 컴파일하는 프로그램이 SPARC 에뮬레이터이고이 프로그램을 사용하여 SPARC 코드를 실행하면 어떻게됩니까?

참고 모든 언어는 추상적 인 기계를 유도하고, 그 기계에 대한 기계 언어입니다. 따라서 모든 언어 (매우 고급 언어 포함)는 기본 기계 코드입니다. 또한 모든 언어에 대한 통역사를 작성할 수 있습니다. 따라서 모든 언어 (x86 기계어 코드 포함)는 네이티브가 아닙니다.


4
편집의 개념에 대한 자세한 설명은 +1, 가능하면 해당 글 머리 기호에 대한 +1을 추가합니다. 매우 유용합니다.
Pedro A

1
비록 기술적으로 이것은 제목의 질문에 대한 답이 아닙니다 ... 그래도 나에게 +1을받을만한 가치가 있습니다!
Pedro A

나는 그것이 암시 적이라는 데 동의하지만 제목의 질문에 대한 대답은 "OP 가 컴파일아닌 것으로 나열하는 모든 것이 앵귤러 컴파일이 무엇인지"입니다.
Jörg W Mittag

이것이 실질적인 차이가 아닌 명명 규칙에 대한 실제적인 방법에 대한 정말 좋은 설명입니다. 마이크로 코드를 언급하면 ​​개선 될 수 있습니다. 기계 코드 수준에서도 '금속'이 아님을 지적하기 위해 ...
AakashM

1
나는 어떻게 든 컴파일러가 무엇인지 배우는 것을 기억합니다. 그 당시 누군가가 나에게 "컴파일러"가 "코드 용 번역기"의 동의어라고 말했다면, 그것이 무엇을위한 것인지, 왜 우리가 필요로 하는지를 얻는 것이 훨씬 쉬웠을 것입니다. 물론, 이것은 오늘날의 관점에서 우스꽝스럽게 들리지만, 이것은 단지 그에게 무언가를 가르 칠 적절한 사람을 갖는 것으로부터 얼마나 많은 이익을 얻을 수 있는지 다시 한번 말해줍니다. 감사합니다. :)
codepleb

18

브라우저에서 실행하기 위해 작성한 코드를 얻으려면 다음 두 가지가 필요합니다.

1) Typescript를 JavaScript로 트랜스 파일 . 이것은 일종의 해결 된 문제입니다. 나는 그들이 웹팩을 사용한다고 생각합니다.

2) 각도 추상화를 JavaScript로 컴파일합니다 . 컴포넌트, 파이프, 디렉티브, 템플릿 등을 의미합니다. 이것이 앵귤러 코어 팀이 작업하는 것입니다.

두 번째 비트 인 각도 컴파일러에 정말로 관심이 있다면 컴파일러 작성자 인 Tobias Bosch가 AngularConnect 2016에서 Angular 컴파일러에 대해 설명하는 것을보십시오 .

여기서 트랜스 파일과 컴파일 사이에 약간의 혼란이 있다고 생각합니다. 그것은 중요하지 않으며 개인적인 취향의 문제입니다. 둘 다 코드 표현 사이의 변형 일뿐입니다. 그러나 제가 개인적으로 사용 하는 정의 는 유사한 추상화 수준 (예 : 타이프 스크립트에서 자바 스크립트로)에서 서로 다른 두 언어간에 번역 이 이루어 지지만 컴파일 은 추상화 수준에서 한 단계 내려 가야 한다는 입니다 . 템플릿, 구성 요소, 파이프, 지시문 등에서 자바 스크립트에 이르기까지 추상화 사다리의 한 단계 아래로 내려가는 것이 컴파일러라고 불리는 이유입니다.


1

각도 컴파일러

Angular 4에서 5로 가장 중요한 변경 사항 중 하나는 컴파일러가 더 빠르고 철저하게 다시 작성되었다는 것입니다. 과거에 Angular 애플리케이션은 JIT (Just-in-Time) 컴파일을 사용했습니다. 여기서 애플리케이션은 실행하기 전에 브라우저에서 런타임에 컴파일되었습니다. Angular 5의 컴파일러 업데이트는 AOT로 이동하여 앱을 실행할 때 컴파일을 적게 수행하므로 앱이 더 빠르게 실행됩니다. AOT는 Angular CLI 1.5 버전 이후 모든 프로덕션 빌드에서 기본적으로 활성화됩니다.

배포 할 애플리케이션을 빌드하고 다음 명령을 실행한다고 가정 해 보겠습니다.

ng build --prod

몇 가지 일이 발생합니다 : 프로덕션 버전, 축소, 자산 번들, 파일 이름 해싱, 트리 흔들기, AOT ... (플래그를 사용하여 활성화 / 비활성화 할 수 있습니다 (예 : aot = false)). 간단히 말해서 prod 플래그는 ngc (Angular 컴파일러)를 사용하여 AOT 컴파일을 수행하여 최적화 된 애플리케이션 번들 을 생성하여 브라우저에 최적화 된 코드를 생성합니다 ( 예, 템플릿을 미리 컴파일합니다 ).

TypeScript 컴파일러

TypeScript 컴파일러 tsc 는 TypeScript 파일 컴파일을 담당합니다. 정적 유형과 같은 TypeScript 기능을 구현하는 것은 컴파일러이며 결과는 TypeScript 키워드 및 표현식이 제거 된 순수한 JavaScript입니다.

TypeScript 컴파일러에는 트랜스 파일러와 유형 검사기라는 두 가지 주요 기능이 있습니다. 컴파일러는 TypeScript를 JavaScript로 변환합니다. 소스 코드에서 다음과 같은 변환을 수행합니다.

  • 모든 유형 주석을 제거하십시오.
  • 이전 버전의 JavaScript를위한 새로운 JavaScript 기능을 컴파일하십시오.
  • 표준 JavaScript가 아닌 TypeScript 기능을 컴파일하십시오.

이를 호출하면 컴파일러가 tsconfig.json에로드 된 구성을 검색합니다 (기본값과 함께 모든 컴파일러 옵션의 자세한 목록은 여기 에서 찾을 수 있음 ).

대부분의 측면에서 TypeScript 컴파일러는 다른 컴파일러처럼 작동합니다. 그러나주의 할 수없는 한 가지 차이점이 있습니다. 기본적으로 컴파일러는 오류가 발생하더라도 JavaScript 코드를 계속 내 보냅니다. 다행히 noEmitOnErrortsconfig.json 파일에서 구성 설정을 true 로 설정하여이 동작을 비활성화 할 수 있습니다 .

참고 : tscngc 는 서로 다른 목적을 가지고 있으며 다른 하나를 선택하는 것이 아닙니다. 이 답변이 흥미로울 수 있습니다 .

이 답변은 다음 책의 내용을 기반으로 작성되었습니다.

  • Cloe, M. (2018). "Angular 5 프로젝트 : 70 개 이상의 프로젝트를 사용하여 단일 페이지 웹 애플리케이션을 구축하는 방법 배우기".

  • Dewey, B., Grossnicklaus, K., Japikse, P. (2017). "Visual Studio 2017로 웹 애플리케이션 빌드 : .NET Core 및 최신 JavaScript 프레임 워크 사용".

  • Freeman, A. (2019). "필수 TypeScript : 초보자부터 전문가까지".

  • Ghiya, P. (2018). "TypeScript 마이크로 서비스".

  • Iskandar, A., Chivukulu, S. (2019). "Angular 및 Bootstrap을 사용한 웹 개발-제 3 판".

  • Hennessy, K., Arora, C. (2018). "예제에 의한 각도 6".

  • Jansen, R., Wolf, I., Vane, V. (2016). "TypeScript : 최신 JavaScript 개발".

  • Mohammed, Z. (2019). "각 프로젝트".

  • Seshadri, S. (2018). "Angular : 가동 및 실행".

  • Wilken, J. (2018). "Angular in Action".

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.