659 lines
21 KiB
HTML
659 lines
21 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
<meta name="description" content="">
|
|
<meta name="author" content="Eelco Visser">
|
|
<meta name="generator" content="Jekyll v3.8.5">
|
|
<title>Lab 8: Simple Transformation</title>
|
|
<!-- <base href="/2021"> -->
|
|
|
|
<!--link rel="canonical" href="https://getbootstrap.com/docs/4.3/examples/starter-template/"-->
|
|
|
|
<link rel="icon" href="../img/logo/pl_ico2_2B3_icon.ico" type="image/x-icon">
|
|
|
|
<!-- Bootstrap core CSS -->
|
|
<!--link href="https://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"-->
|
|
|
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
|
|
|
|
|
|
|
<style>
|
|
.bd-placeholder-img {
|
|
font-size: 1.125rem;
|
|
text-anchor: middle;
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.bd-placeholder-img-lg {
|
|
font-size: 3.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
<!-- Custom styles for this template -->
|
|
<link href="../css/main.css" rel="stylesheet">
|
|
<link href="../css/borders-responsive.css" rel="stylesheet">
|
|
|
|
<link rel="stylesheet" href="../css/pl.css">
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
|
|
|
<a class="navbar-brand" href="../index.html">
|
|
TU Delft | CS4200
|
|
</a>
|
|
|
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
|
|
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
|
|
<ul class="navbar-nav mr-auto">
|
|
|
|
|
|
|
|
|
|
|
|
<li class="nav-item active">
|
|
<a class="nav-link" href="../lectures/index.html" tabindex="-1" aria-disabled="true">Lectures</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="nav-item active">
|
|
<a class="nav-link" href="../homework/index.html" tabindex="-1" aria-disabled="true">Homework</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="nav-item active">
|
|
<a class="nav-link" href="../project/index.html" tabindex="-1" aria-disabled="true">Project</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="nav-item active">
|
|
<a class="nav-link" href="../news/index.html" tabindex="-1" aria-disabled="true">News</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="nav-item active">
|
|
<a class="nav-link" href="../blog/index.html" tabindex="-1" aria-disabled="true">Blog</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
</nav>
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-12">
|
|
|
|
|
|
<div class="mt-3 mb-3 pt-3 pb-3 text-dark border-top border-bottom border-grey">
|
|
<div class="text-dark font-weight-bold">
|
|
<h1>
|
|
|
|
Lab 8: Simple Transformation
|
|
|
|
</h1>
|
|
</div>
|
|
<div>
|
|
|
|
|
|
</div>
|
|
<div>
|
|
|
|
|
|
Project
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
<div>
|
|
November 12, 2021
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-12 col-lg-9 border-lg-right border-grey">
|
|
<p>During this lab get to know Stratego by making the <a href="https://weblab.tudelft.nl/cs4200/2021-2022/assignment/88076/view">homework assignments for Week 1</a> either in Spoofax 3 or in WebLab.</p>
|
|
|
|
<!--
|
|
|
|
This lab is your first encounter with Stratego.
|
|
You add an outline view and a desugaring transformation to an initial editor provided by us.
|
|
|
|
|
|
### Objectives
|
|
|
|
1. Specify rewrite rules `to-outline-label` which map AST nodes to labels in an outline view. You should include:
|
|
* classes (class name and, if available, parent class name),
|
|
* fields (field name and type),
|
|
* methods (method name, parameter types, return type) and
|
|
* local variables (variable name and type).
|
|
2. Define rewrite rules `desugar` which desugar
|
|
* unary expressions into terms of the form `UnExp(op, exp)`,
|
|
* binary expressions into terms of the form `BinExp(op, exp1, exp2)` and
|
|
3. Integrate `desugar` into a strategy `desugar-all` which desugars subtrees in an AST.
|
|
|
|
For grading, it is required to comply with all constructor names, rule names, and strategy names literally.
|
|
|
|
<!-- ### Submission
|
|
|
|
You need to submit your ChocoPy project with a merge request against branch `assignment-3-submission` on GitLab.
|
|
The [Git documentation](/project/lab1d) explains how to file such a request. -->
|
|
|
|
<!-- ### Early Feedback
|
|
|
|
We provide early feedback for the correctness of your outline and desugarings.
|
|
This feedback is a score out of 90 points; the strategy choice (10 points) is graded manually.
|
|
You have 3 early feedback attempts. -->
|
|
|
|
<!--
|
|
### Preliminaries
|
|
|
|
#### GitLab Repository
|
|
|
|
We provide you with a template for this assignment.
|
|
See the [Git documentation](/2021/lab/1d) on how to check out this branch.
|
|
Make sure to read the `README.md` in the root of the repository, as it contains further instructions that are specific to this template.
|
|
|
|
#### Anatomy of a Spoofax Project
|
|
|
|
Until now, you mainly worked on files in the `syntax` folder of your project.
|
|
During this lab you will also edit files in the `trans` folder.
|
|
So, this is a good point to talk about the general structure of a Spoofax project.
|
|
Note: this general structure does not apply to the template we've given you for the project, considering it contains a compiled version of the front-end already.
|
|
|
|
First of all, every Spoofax project may be part of an Eclipse plug-in project.
|
|
This allows you to deploy your editor as a plugin using the Eclipse update site mechanism.
|
|
Users do not need to have Spoofax installed for using your editor.
|
|
|
|
The actual language definition is spread over three folders:
|
|
|
|
* `syntax` contains all syntax definition files, including the main file `chocopy.sdf3`.
|
|
* `trans` contains all transformation files, including the main file `chocopy.str`.
|
|
* `editor` contains editor service definition files, including the main file `Main.esv`.
|
|
|
|
In the `src-gen` folder, you will find files which were generated from your syntax definition.
|
|
For each file `<name>.sdf3`, there are generated files:
|
|
|
|
* `syntax/<name>.sdf`: an SDF2 definition which is equivalent to the SDF3 definition.
|
|
* `completions/<name>-cp.str`: abstract placeholder expansions for syntactic code completion derived from SDF3 templates.
|
|
* `completions/colorer/<name>-cc-esv.esv`: instructions to color placeholders for syntactic completions in gray.
|
|
* `pp/<name>-pp.str`: pretty-printing strategies derived from SDF3 templates.
|
|
* `signatures/<name>-sig.str`: signatures derived from SDF3 templates.
|
|
|
|
You can find more generated files in `src-gen` and `target/metaborg` folders:
|
|
|
|
* `src-gen/syntax/ChocoPy.def`: your complete syntax definition in SDF2.
|
|
* `src-gen/syntax/ChocoPy-Permissive.def`: a permissive version of the syntax definition, which supports error recovery.
|
|
* `target/metaborg/ChocoPy.tbl`: the parse table of your language.
|
|
* `src-gen/pp/ChocoPy-parenthesize.str`: strategies to add parentheses to an AST according to the priorities of your language.
|
|
* `target/metaborg/stratego.ctree` and/or `target/metaborg/stratego.jar`: compiled Stratego code of your language.
|
|
|
|
#### Initial Editor Project
|
|
|
|
We provide you with an initial ChocoPy project in the branch `milestone-3-template`.
|
|
This project is a common starting point for all of you.
|
|
It includes:
|
|
|
|
* A compiled version of a front-end ChocoPy project.
|
|
* corresponding signatures `reference/src-gen/signatures/*-sig.str`,
|
|
* pretty-printing definitions `reference/src-gen/pp/*-pp.str`, and a
|
|
* content-completion definitions `reference/src-gen/completion/*-cp.str` (errors in the completion files can be ignored).
|
|
|
|
During the building of the project the contents of `reference` will be copied over to `src-gen`.
|
|
So, when importing any module from the reference, you can just use the 'local' variant.
|
|
I.e. instead of importing `reference/src-gen/signatures/chocopy-sig`, please import `signatures/chocopy-sig`.
|
|
|
|
#### Signature
|
|
|
|
Signatures declare sorts and constructors for terms.
|
|
In Spoofax, terms are used to represent abstract syntax trees.
|
|
The corresponding signature is generated from the constructors in a syntax definition.
|
|
You can find the signature for ChocoPy in the files `reference/src-gen/signatures/*-sig.str`.
|
|
The signature was generated from a syntax definition, which itself is not included in the initial project.
|
|
If you write your own syntax definition, the generated signatures can be found in `src-gen/signatures/`.
|
|
|
|
### Outline View
|
|
|
|
#### Rewrite Rules
|
|
|
|
An outline view can be specified by rewrite rules `to-outline-label` in `trans/outline.str`.
|
|
These rules should rewrite AST nodes to their label in an outline view.
|
|
For example, the following rule rewrites a variable declaration to its name, which will be used as a label.
|
|
|
|
```
|
|
rules
|
|
|
|
to-outline-label: Var(t, v) -> v
|
|
```
|
|
|
|
On the left-hand side, the rule matches a variable declaration.
|
|
During the match, variables `t` and `v` are bound to actual terms.
|
|
On the right-hand side, the rule instantiates a label.
|
|
During the instantiation, variable `v` is replaced with the term it is bound to.
|
|
You can extend `to-outline-label` to provide labels for
|
|
|
|
* class declarations,
|
|
* field declarations and
|
|
* method declarations.
|
|
|
|
When you build the project and open a ChocoPy file, you will get an outline of this program in the outline view.
|
|
In case you do not see any outline view, you can select it in *Show View* from Eclipse's *Window* menu.
|
|
|
|
#### Naming Conventions
|
|
|
|
In Stratego, we use the following naming conventions:
|
|
|
|
* constructor and sort names: camel case, starting with an upper case (e.g. `Add`, `BinExp`)
|
|
* rule names, strategy names, variable names: lower case, multiple words separated by `-` (e.g. `e1`, `project-path`)
|
|
|
|
#### String Interpolation
|
|
|
|
In many cases, you want to provide more information than just the name.
|
|
For example, you might want to show not only a variable's name, but also it's type.
|
|
The following rule achieves this:
|
|
|
|
```
|
|
to-outline-label:
|
|
Var(t, v) -> label
|
|
where
|
|
t' := <pp-partial-ChocoPy-string> t
|
|
; label := <concat-strings> [v, ": ", t']
|
|
```
|
|
|
|
On its right-hand side, it produces a `label`, which is bound in the `where` clause.
|
|
First, the term bound to `t` is turned into a string bound to `t'` by applying a strategy `pp-partial-ChocoPy-string`.
|
|
This strategy is defined in `ChocoPy/trans/pp.str` and uses the pretty-printing rules generated from our SDF3 grammar for ChocoPy.
|
|
|
|
Next, the label is bound to the concatenation of
|
|
the string bound to `v`,
|
|
a constant string `": "`,
|
|
and the string bound to `t'`.
|
|
|
|
String concatenation is not very intuitive.
|
|
Instead, you can also use string interpolation:
|
|
|
|
```
|
|
to-outline-label:
|
|
Var(t, v) -> $[[v]: [t']]
|
|
where
|
|
t' := <pp-partial-ChocoPy-string> t
|
|
```
|
|
|
|
String interpolation allows you to combine text with variables.
|
|
Text is enclosed in `$[` and `]`, while variables inside the text are enclosed in `[` and `]`.
|
|
These variables need to be bound to strings.
|
|
|
|
You should provide the following information in your outline labels:
|
|
|
|
* class name and super class name
|
|
* field name and type
|
|
* method name, parameter types (not parameter names), return type
|
|
* variable name and type
|
|
|
|
For parameter types, you need to turn a list of parameters into a string.
|
|
You can do this with a recursive strategy:
|
|
|
|
```
|
|
pp-params: // empty parameter list
|
|
[] -> ...
|
|
|
|
pp-params: // single parameter
|
|
[TypedVar(p, t)] -> ...
|
|
|
|
pp-params: // at least two parameters
|
|
[TypedVar(p, t), param | params] -> ...
|
|
where
|
|
// do something on first parameter
|
|
...
|
|
// recursive call on remaining parameters
|
|
... := <pp-params> [param | params]
|
|
```
|
|
|
|
For grading, it is required to define a strategy `pp-params` that always rewrites a list of `TypedVar`s to a *string*.
|
|
{: .notice .notice-warning}
|
|
|
|
Your current outline view is missing a root node.
|
|
You can add a root node by providing a label for programs.
|
|
|
|
#### Annotations
|
|
|
|
In Stratego, terms can be annotated with additional information.
|
|
The Spoofax outline view uses annotations to determine the icon of a node.
|
|
You can specify the icon to use in an annotation:
|
|
|
|
```
|
|
to-outline-label:
|
|
Var(t, v) -> label{icon}
|
|
where
|
|
t' := <pp-partial-ChocoPy-string> t
|
|
; label := $[[v]: [t']]
|
|
; icon := "icons/var.gif"
|
|
```
|
|
|
|
We do not require you to use icons and you will not earn any points with them.
|
|
If you want to use them anyway, you should put the icons into the folder `icons`
|
|
and place a proper attribution or license file next to them.
|
|
|
|
#### Challenge
|
|
|
|
Challenges are meant to distinguish excellent solutions from good solutions.
|
|
Typically, they are less guided and require more investigation or higher programming skills.
|
|
{: .notice .notice-success}
|
|
|
|
|
|
1. Provide the file name as the root node label. In `outline.str`: import, command/control click and study `libspoofax/term/origin` for a suitable strategy.
|
|
|
|
2. Outline the main method as a subnode of the main class.
|
|
You need to change this strategy in the `outline.str` file:
|
|
|
|
```
|
|
outline := <custom-label-outline(to-outline-label, to-outline-node)> ast
|
|
```
|
|
|
|
Also import, command/control click, and investigate `libspoofax/editor/outline` for inspiration.
|
|
|
|
3. Use one of the library strategies for folding to implement `pp-params`. You can find various folding strategies in the [API docs](http://releases.strategoxt.org/docs/api/libstratego-lib/stable/docs/).
|
|
|
|
### Desugaring
|
|
|
|
A uniform representation of unary and binary expressions eases static analysis and code generation.
|
|
To get such a uniform representation, you need to desugar abstract syntax trees during the analysis phase.
|
|
|
|
#### Signature
|
|
|
|
Before you can implement a desugaring,
|
|
you need to define a signature for the uniform representation of expressions in `trans/desugar.str`:
|
|
|
|
1. Identify unary and binary expressions in ChocoPy.
|
|
A unary expression has one subexpression and an operator.
|
|
A binary expression has two subexpressions and an operator.
|
|
There are more than one kind of unary expressions and more than three kinds of binary expressions in ChocoPy.
|
|
2. Specify new constants for unary and binary operators in a signature.
|
|
Use `UnOp` and `BinOp` as types of these operators.
|
|
Again, you should use names based on the semantics of an operator, not on its syntax.
|
|
Reading an expression aloud might help you to find suitable constructor names.
|
|
3. Define constructors `UnExp` and `BinExp`,
|
|
which combine an operator and an expression (respectively two expressions) to an expression.
|
|
|
|
#### Rewrite Rules
|
|
|
|
The following rewrite rule defines a rule to desugar an addition:
|
|
|
|
```
|
|
rules
|
|
|
|
desugar: Add(e1, e2) -> BinExp(Plus(), e1, e2)
|
|
```
|
|
|
|
This rewrite rule is named `desugar`.
|
|
On the left-hand side, the rule matches an addition.
|
|
During the match, variables `e1` and `e2` are bound to actual terms.
|
|
On the right-hand side, the rule instantiates a binary expression (in a uniform representation).
|
|
During the instantiation, variables `e1` and `e2` are replaced with the terms they are bound to.
|
|
You can extend `desugar` to replace the different unary and binary expressions in the abstract syntax tree
|
|
with a uniform representation of these expressions.
|
|
Define a rewrite rule `desugar` in `trans/desugar.str` for every unary or binary operator,
|
|
which transforms the original expression into a uniform representation.
|
|
|
|
#### Editor Integration
|
|
|
|
To test your transformation, you need to define a builder.
|
|
This is done similar to the builder for pretty-printing.
|
|
First, import the `desugar` module (defined in `trans/desugar.str`) into the main `chocopy` module.
|
|
Then, add the following rewrite rule to `trans/chocopy.str`:
|
|
|
|
```
|
|
editor-desugar:
|
|
(_, _, ast, path, project-path) -> (filename, text)
|
|
where
|
|
filename := <guarantee-extension(|"desugared.aterm")> path ;
|
|
text := <desugar-all> ast
|
|
```
|
|
|
|
This rule follows Spoofax' convention for strategies which implement editor services.
|
|
On the left-hand site, it matches a tuple of
|
|
|
|
* the first two parts are ignored
|
|
* the `path` of the current file and
|
|
* the `project path`.
|
|
|
|
On the right-hand site, it instantiates a pair, consisting of a `filename` and the designated `text` of the file.
|
|
Both variables are bound in the `where` clause.
|
|
The file name is derived from the path of the current file,
|
|
while the content of the file is a desugared version of the selected AST node.
|
|
You also need to hook your strategy into the editor, making desugaring available in the *Syntax* menu.
|
|
You can do this in `editor/Syntax.esv` under the *Show Parsed AST* action:
|
|
|
|
```
|
|
action : "Show desugared syntax" = editor-desugar (source)
|
|
```
|
|
|
|
This rule defines
|
|
|
|
* a builder,
|
|
* its label in the *Syntax* menu, and
|
|
* its implementation strategy `editor-desugar`.
|
|
|
|
Annotations can be used for different variants of builders:
|
|
|
|
* `(openeditor)` from the Syntax menu ensures that a new editor window is opened for the result.
|
|
* Finally, `(source)` tells Spoofax to run the builder on an unanalysed (and also not desugared) AST.
|
|
|
|
Note that the `editor-desugar` rule uses `desugar-all`. You should implement this strategy as explained in the next section. Until you do, the menu option will not work.
|
|
{: .notice .notice-warning}
|
|
|
|
|
|
#### Strategies
|
|
|
|
Rewrite rules typically define local transformations inside an AST.
|
|
Rewrite rules with the same name define a strategy of this name.
|
|
Furthermore, strategies can be defined to orchestrate rewrite rules to complex transformations of complete ASTs.
|
|
A strategy consists of a name and a definition, which is typically a combination of strategy applications.
|
|
For example, the following strategy orchestrates local desugarings to a desugaring of complete ASTs:
|
|
|
|
```
|
|
strategies
|
|
|
|
desugar-all = innermost(desugar)
|
|
```
|
|
|
|
This strategy is named `desugar-all`.
|
|
It applies local `desugar` rules.
|
|
The application is guided by a generic traversal strategy `innermost`,
|
|
which tries to apply its parameter inside a tree, starting at the leaves (bottom-up, left-to-right).
|
|
Whenever an application is successful, the result is traversed again.
|
|
|
|
Same results can be achieved with different generic traversals.
|
|
You should try different traversals in `trans/desugar.str`:
|
|
|
|
* `desugar-all = innermost(desugar)`
|
|
* `desugar-all = topdown(desugar)`
|
|
* `desugar-all = topdown(try(desugar))`
|
|
* `desugar-all = bottomup(desugar)`
|
|
* `desugar-all = bottomup(try(desugar))`
|
|
* `desugar-all = alltd(desugar)`
|
|
|
|
Try to understand what is going on and decide for a suitable one.
|
|
You can use the library strategy `debug` to print the currently visited node.
|
|
For example, `innermost(debug; desugar)` will `debug` all nodes before it tries to `desugar` them.
|
|
|
|
As preparation for the exam, provide an explanation of
|
|
1. the choice you made,
|
|
2. why this choice is suitable for this project, and
|
|
3. why other choices would be less suitable.
|
|
|
|
Try changing the `editor-desugar` rule to use `desugar-all` instead of `desugar`. If you chose a suitable strategy, the builder should succeed even when no node is selected in the ChocoPy program.
|
|
|
|
-->
|
|
|
|
|
|
</div>
|
|
<div class="col-sm-12 col-md-12 col-lg-3 col-xl-3 " >
|
|
<div class="sticky-top top70 d-none d-lg-block d-xl-block">
|
|
<div class="pb4 mb3 border-bottom border-grey ">
|
|
<nav>
|
|
<ul class="pagination justify-content-left">
|
|
|
|
<li class="page-item">
|
|
<a class="page-link" href="../milestone/2.html">
|
|
«
|
|
</a>
|
|
</li>
|
|
|
|
<li class="page-item">
|
|
<a class="page-link" href="../project/index.html">
|
|
^
|
|
</a>
|
|
</li>
|
|
|
|
<li class="page-item">
|
|
<a class="page-link" href="9.html">»</a>
|
|
</li>
|
|
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="border-top border-bottom border-grey mt-3 pt-3">
|
|
<nav>
|
|
<ul class="pagination justify-content-center">
|
|
|
|
<li class="page-item">
|
|
<a class="page-link" href="../milestone/2.html">
|
|
Previous
|
|
</a>
|
|
</li>
|
|
|
|
|
|
<li class="page-item">
|
|
<a class="page-link" href="9.html">Next</a>
|
|
</li>
|
|
|
|
<li class="page-item">
|
|
<a class="page-link" href="../project/index.html">
|
|
Index
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Optional JavaScript -->
|
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
|
|
|
</body>
|
|
</html>
|