<<

Модерни езици за програмира за JVM JRuby, Groovy, Scala и . Що е ?

Програмен език

Виртуална машина

Стандартна библиотека The beating heart of Java is not the Java programming language - it is the JVM and the entire infrastructure built around it...

Maximus Decimus Meridius, Roman General & Java Programmer Езикът Java

Създаден да замени С++

Интегрира някои добри идеи от Lisp

Характеризира се с консервативен, но практичен дизайн Проблемите на езикът Java

Не е чист обектно-ориентиран език

Няма никаква поддръжка за функционален стил на програмиране

Не е особено експресивен

Развитието му е ограничено от изискванията за обратна съвместимост Че то алтернативи има ли?

1996 - Java 1.0

1997 - 40 езика вече имат версия за JVM

2004 - 169 са JVM compatible

2011 - приблизително 300 езика се целят в JVM Причината?

The JVM is rock solid and heart touching at the same time.

Отлична производителност и прекрасен optimizer

Огромна база съществуващ Java код

Купища страхотни иструменти Не всичко е ток и жица

Java (< 7) нямаше поддръжка за динамичен метод dispatching

JVM не е оптимизиран за функционален стил на програмиране

JVM пали относително бавно

JVM имплементациите на някои езици (като Python) не са съвсем съвместими с native () имплементациите им Двете страни на Силата

Езици портнати към JVM

Езици създадени специално за JVM Претендентите

JRuby

Jython

Fantom

Groovy

Scala

Clojure Ruby

динамичен език за програмиране

компактен и елегантен синтаксис

създаден да направи програмистите щастливи made in Japan Преди Ruby Сега (като Ruby програмист) Hello, Ruby

# Output "I love Ruby" say = "I love Ruby" puts say

# Output "I *LOVE* RUBY" say['love'] = "*love*" puts say.upcase

# Output "I *love* Ruby" # five times 5.times { puts say } JRuby - Java & Ruby sitting in a tree

Ruby е елегантен език с бавен runtime

JVM е много бърз runtime

JRuby дава възможност на Java програмистите да използват технологии като Rails

JRuby дава възможност на Ruby програмистите да ползват Java библиотеки Загрявка в jirb jruby-1.6.1 :001 > puts "Hello, JRuby" Hello, JRuby => nil jruby-1.6.1 :002 > arr = ["Chuck", "Sarah", "Morgan", "Casey"] => ["Chuck", "Sarah", "Morgan", "Casey"] jruby-1.6.1 :003 > arr.length => 4 jruby-1.6.1 :004 > arr.size => 4 jruby-1.6.1 :005 > arr.size() => 4 jruby-1.6.1 :006 > arr.each { |name| puts name } Chuck Sarah Morgan Casey => ["Chuck", "Sarah", "Morgan", "Casey"] jruby-1.6.1 :007 > arr.each_with_index { |name, index| puts "##{index}: #{name}"} 0: Chuck 1: Sarah 2: Morgan 3: Casey => ["Chuck", "Sarah", "Morgan", "Casey"] Ако прилича на патица... class Duck def walk puts "The duck walks" end

def quack puts "The duck quacks" end end

class Dog def walk puts "The dog walks" end

def quack puts "The dog quacks" end end

def test_animal(animal) animal.walk animal.quack end

test_animal(Duck.new) test_animal(Dog.new) Java от Ruby require 'java' java_import 'java.lang.System' java_import 'java.util.ArrayList' java_import 'javax.swing.JOptionPane'

System.out.println("Feel the power of JRuby")

## using snake_names for Java method names puts System.current_time_millis ## regular names work as well puts System.currentTimeMillis array_list = ArrayList.new

## the array list supports some common Ruby idioms array_list << 1 array_list.add 2 array_list << 3 puts "List length is ##{array_list.length}" array_list.each { |elem| puts elem }

## a glimpse of Swing JOptionPane.show_message_dialog(nil, "This is a message from the future of Ruby!") Ruby от Java

import org.jruby.embed.InvokeFailedException; import org.jruby.embed.ScriptingContainer; public class RubyFromJava { public static void main(String[] args) { ScriptingContainer container = new ScriptingContainer(); container.runScriptlet("puts 'Ruby bridge established successfully'" ); } } Стана ми интересно, къде да науча повече?

http://batsov.com/articles/2011/05/18/jvm- langs-jruby/ It’s a Groovy kind of love... Хвала на Groovy

Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, builders and dynamic typing. If you are a developer, tester or script guru, you have to love Groovy. def name='World'; println "Hello $name!"

class Greet { def name Greet(who) { name = who[0].toUpperCase() + who[1..-1] } def salute() { println "Hello $name!" } }

g = new Greet('world') // create object g.salute() // output "Hello World!" import static org.apache.commons.lang.WordUtils.* class Greeter extends Greet { Greeter(who) { name = capitalize(who) } } new Greeter('world').salute()

groovy -e "println 'Hello ' + args[0]" World Groovy e...

динамичен

изцяло обектно-ориентиран

вдъхновен от Ruby, Python и Smalltalk

със синтаксис много близък до този на Java

създаден да улесни живота на Java програмистите Ключовите моменти

closures

attributes

duck typing

аритметика базирана на BigDecimal

улеснена работа с XML, SQL, Swing, etc Groovy & Java

Groovy програмите се компилират до

Същите низове, същите регулярни изрази и т.н.

Същите API

Същия модел за сигурност, същия нишков модел

Същите ОО концепции // old school Java code, but also valid Groovy code System.out.println("Hello, world!");

// idiomatic Groovy println "Hello, world!"

// dynamic variable definition def name = "Bozhidar"

// GString featuring string interpolation println "Hello, $name" // => "Hello, Bozhidar"

// statically typed variable String songName = "Coding in the Name of" println "Now playing - $songName"

String multiline = """this is a multiline string. There is not need to embed newline characters in it""" println multiline

// method definition def greet(name) { println "Hello, $name!" }

// method invocation greet "Bozhidar" greet("Bozhidar") showSize([1, 2, 3]) // this is the important part showSize(null)

// a list def beers = ["Zagorka", "Bolyarka", "Shumensko", "Ariana"]

// list access println "My favourite beer is ${beers[1]}" beers.each { beer -> println beer }

// imports can appear anywhere and support the creation of aliases import static java.util.Calendar.getInstance as now import java.sql.Date as SDate println now() // java.util package is automatically imported in Groovy so this is java.util.Date println new Date() println new SDate(2011, 5, 5) // language support for regular expressions if ("Hello, Groovy" =~ /\w+,\s\w+/) { println "It matches" }

// range filtering with higher-order functions (1..10).findAll { n -> n % 2 == 0}.each { n -> println n }

// map def capitols = [Bulgaria: "Sofia", USA: "Washington", England:"London", France:"Paris"] println capitols["Bulgaria"] // => Sofia println capitols["France"] // => Paris

// class definition class Person { def name def age

Person(name, age) { this.name = name this.age = age }

@Override String toString() { return "Name {$name}, age {$age}" } } def me = new Person("Bozhidar", 26) println me JDBC подобрения

import groovy.sql.Sql sql = Sql.newInstance("jdbc:mysql://host/db", "username", "password", "com.mysql.jdbc.Driver") sql.eachRow("select * from tableName", { println it.id + " -- ${it.firstName} --"} )

Изход от програмата

1 -- Bozhidar -- 2 -- Jim -- 3 -- Jack -- 4 -- Valentine -- XML

Dune Dune Messiah Children of Dune A Game of Thrones

def books = new XmlSlurper().parse("books.xml") books.book.each { println "Title = ${it.title}, Author: ${it.author.@firstname} ${it.author.@lastname}" } Builders

import groovy.xml.* def page = new MarkupBuilder() Hello, Groovy!</ page.html { title> head { title 'Hello, Groovy!' } </head> body { <body> div { <div> 3.times { <p>Groovy power!</p> p "Groovy power!" <p>Groovy power!</p> } <p>Groovy power!</p> } </div> } </body> } </html> Swing import java.awt.FlowLayout builder = new groovy.swing.SwingBuilder() langs = ["Groovy", "Scala", "Clojure"] gui = builder.frame(size: [290, 100], title: 'Groovy Swing') { panel(layout: new FlowLayout()) { panel(layout: new FlowLayout()) { for (lang in langs) { radioButton(text: lang) } } button(text: 'Perform Magic', actionPerformed: { builder.optionPane(message: "Feel the power of Groovy!"). createDialog(null, 'Message').show() }) button(text: 'Quit', actionPerformed: {System.exit(0)}) } } gui.show() Инструментите на занаята groovy - интерпретатор groovysh - конзола groovyc - компилатор groovyconsole - графична конзола Приложения убийци (killer apps)</p><p>Grails - модерна платформа за разработка на уеб приложения, вдъхновена от <a href="/tags/Ruby_on_Rails/" rel="tag">Ruby on Rails</a></p><p>Gradle - могъщ build tool, създаден да наследи Maven</p><p>Griffon - модерна платформа за разработка на Swing приложения IDE-та, нещо?</p><p>IntelliJ IDEA - Bozhidar’s Choice</p><p>Eclipse</p><p>NetBeans Where do we go now?</p><p> http://batsov.com/articles/2011/05/06/jvm- langs-groovy/ Ride the eSCALAtor</p><p>If I were to pick a language to use today other than Java, it would be Scala...</p><p>James Gosling, father of Java Отмъщението на статично типизираните езици</p><p>Scala е статично типизиран език (като Java)</p><p>Scala използва type inference механизъм, който сериозно намалява типовите декларации</p><p>Кодът написан на Scala е толкова сигурен и бърз, колкото този написан на Java ООП и ФП могат да съжителстват в мир и любов</p><p>Scala е чисто обектно-ориентиран език</p><p>Scala включва в себе си много елементи от функционалното програмиране</p><p> higher order functions</p><p> function objects</p><p> pattern matching</p><p> tail recursion Expressive</p><p> scala> val romanToArabic = Map("I" -> 1, "II" -> 2, "III" -> 3, "IV" -> 4, "V" -> 5) romanToArabic: scala.collection.immutable.Map[java.lang.String,Int] = Map((II,2), (IV,4), (I,1), (V,5), (III,3)) scala> romanToArabic("I") res2: Int = 1 scala> romanToArabic("II") res3: Int = 2 Компактен код, без излишна церемония public boolean hasUpperCase(String word) { if (word == null) { return false; } Java int len = word.length(); for (int i = 0; i < len; i++) { if (Character.isUpperCase(word.charAt(i))) { return true; } } return false; } Scala def hasUppercase(word: String) = if (word != null) word.exists(_.isUpperCase) else false Оптимизиран за Java мързели class Person { private String name; private int age;</p><p>Person(String name, int age) { this.name = name; this.age = age; }</p><p> public String getName() { Scala return name; } class Person(var name: String, var age: Int)</p><p> public void setName(String name) { this.name = name; }</p><p> public int getAge() { return age; }</p><p> public void setAge(int age) { this.age = age; } } Актьорско майсторство</p><p> import scala.actors.Actor._</p><p> case class Add(x: Int, y: Int) case class Sub(x: Int, y: Int)</p><p> val mathService = actor { loop { receive { case Add(x, y) => reply(x + y) case Sub(x, y) => reply(x - y) } } }</p><p> mathService !? Add(1, 3) // returns 4 mathService !? Sub(5, 2) // returns 3 Патоците на власт!</p><p> class Duck { def quack = println("The duck quacks") def walk = println("The duck walks") }</p><p> class Dog { def quack = println("The dog quacks (barks)") def walk = println("The dog walks") }</p><p> def testDuckTyping(animal: { def quack; def walk }) = { animal.quack animal.walk }</p><p> scala> testDuckTyping(new Duck) The duck quacks The duck walks</p><p> scala> testDuckTyping(new Dog) The dog quacks (barks) The dog walks Pimp my <a href="/tags/Library_(computing)/" rel="tag">library</a></p><p> scala> implicit def intarray2sum(x: Array[Int]) = x.reduceLeft(_ + _) intarray2sum: (x: Array[Int])Int scala> val x = Array(1, 2, 3) x: Array[Int] = Array(1, 2, 3) scala> val y = Array(4, 5, 6) y: Array[Int] = Array(4, 5, 6) scala> val z = x + y z: Int = 21 Малко повече екшън scala> println("Hello, Scala") Hello, Scala scala> val name = "Bozhidar" name: java.lang.String = Bozhidar scala> Predef.println("My name is "+name) My name is Bozhidar scala> var someNumber: Int = 5 someNumber: Int = 5 scala> var names = Array("Superman", "Batman", "The Flash", "Bozhidar") names: Array[java.lang.String] = Array(Superman, Batman, The Flash, Bozhidar) scala> names.filter(name => name.startsWith("B")) res6: Array[java.lang.String] = Array(Batman, Bozhidar) scala> names.length res7: Int = 4 scala> name.length() res8: Int = 8 ... scala> import java.util.Date import java.util.Date scala> var currentDate = new Date currentDate: java.util.Date = Wed May 11 15:03:20 EEST 2011 scala> println("Now is " + currentDate) Now is Wed May 11 15:03:20 EEST 2011 scala> currentDate.toString res10: java.lang.String = Wed May 11 15:03:20 EEST 2011 scala> currentDate.toString() res11: java.lang.String = Wed May 11 15:03:20 EEST 2011 scala> currentDate toString res12: java.lang.String = Wed May 11 15:03:20 EEST 2011 Closures</p><p> scala> var x = 10 x: Int = 10 scala> val addToX = (y: Int) => x + y addToX: (Int) => Int = <function1> scala> addToX(2) res0: Int = 12 scala> addToX(6) res1: Int = 16 scala> x = 5 x: Int = 5 scala> addToX(10) res2: Int = 15 Свързани списъци scala> 1 :: 2 :: 3 :: 4 :: 5 :: Nil res3: List[Int] = List(1, 2, 3, 4, 5) scala> val names = List("Neo", "Trinity", "Morpheus", "Tank", "Dozer") names: List[java.lang.String] = List(Neo, Trinity, Morpheus, Tank, Dozer) scala> names.length res4: Int = 5 scala> names.foreach(println) Neo Trinity Morpheus Tank Dozer scala> names.map(_.toUpperCase) res6: List[java.lang.String] = List(NEO, TRINITY, MORPHEUS, TANK, DOZER) scala> names.forall(_.length > 5) res7: Boolean = false scala> names.forall(_.length > 2) res8: Boolean = true ...</p><p> scala> names.filter(_.startsWith("T")) res9: List[java.lang.String] = List(Trinity, Tank) scala> names.exists(_.length == 3) res10: Boolean = true scala> names.drop(2) res11: List[java.lang.String] = List(Morpheus, Tank, Dozer) scala> names.reverse res12: List[java.lang.String] = List(Dozer, Tank, Morpheus, Trinity, Neo) scala> names.sortBy(_.length) res13: List[java.lang.String] = List(Neo, Tank, Dozer, Trinity, Morpheus) scala> names.sort(_ > _) res14: List[java.lang.String] = List(Trinity, Tank, Neo, Morpheus, Dozer) scala> names.slice(2, 4) res16: List[java.lang.String] = List(Morpheus, Tank) Pattern matching scala> def testMatching(something: Any) = something match { | case 1 => "one" | case "two" => 2 | case x: Int => "an integer number" | case x: String => "some string" | case <xmltag>{content}</xmltag> => content | case head :: tail => head | case _ => "something else entirely" | } testMatching: (something: Any)Any scala> testMatching(1) res18: Any = one scala> testMatching("two") res19: Any = 2 scala> testMatching(2) res20: Any = an integer number scala> testMatching("matrix") res21: Any = some string scala> testMatching(<xmltag>this is in the tag</xmltag>) res22: Any = this is in the tag scala> testMatching(List(1, 2, 3)) res23: Any = 1 scala> testMatching(3.9) res24: Any = something else entirely Plain recursion</p><p> def length(list: List[Any]): Int = list match { case head :: tail => 1 + length(tail) case Nil => 0 }</p><p>Tail recursion def length(list: List[Any]): Int = { def lengthrec(list: List[Any], result: Int): Int = list match { case head :: tail => lengthrec(tail, result + 1) case Nil => result }</p><p> lengthrec(list, 0) } Инвентара</p><p> scala - конзола/интерпретатор scalac - компилатор fsc - fast scala compiler IDE-тата</p><p>IntelliJ IDEA - Bozhidar’s Choice</p><p>Eclipse - Official Scala IDE</p><p>NetBeans - на тоя етап е бран бостан Убийците</p><p>Play! Framework</p><p>Lift</p><p>SBT (Simple Build Tool)</p><p>Akka The full disclosure on Clojure “Clojure feels like a general-purpose language beamed back from the near future. Its support for functional programming and software trans- actional memory is well beyond current practice and is well suited for multicore hardware. At the same time, Clojure is well grounded in the past and the present. It brings together Lisp and the <a href="/tags/Java_virtual_machine/" rel="tag">Java Virtual Machine</a>. Lisp brings wisdom spanning most of the history of programming, and Java brings the robustness, extensive libraries, and tooling of the dominant platform available today.”</p><p>What happens when an unstoppable force meets an immutable object?</p><p>Clojure is dynamic</p><p>Clojure is functional</p><p>Clojure is a Lisp(1)</p><p>Clojure is designed for concurrency</p><p>Clojure is fighting accidental complexity Стил</p><p> public boolean hasUpperCase(String word) { if (word == null) { return false; } int len = word.length(); for (int i = 0; i < len; i++) { if (Character.isUpperCase(word.charAt(i))) { return true; } } return false; }</p><p>(defn has-uppercase? [string] (some #(Character/isUpperCase %) string)) Компактност class Person { private String name; private int age;</p><p>Person(String name, int age) { this.name = name; this.age = age; }</p><p> public String getName() { return name; (defrecord person [name age]) }</p><p> public void setName(String name) { this.name = name; }</p><p> public int getAge() { return age; }</p><p> public void setAge(int age) { this.age = age; } } Силата е на ваша страна</p><p>(defmacro and "Evaluates exprs one at a time, from left to right. If a form returns logical false (nil or false), and returns that value and doesn't evaluate any of the other expressions, otherwise it returns the value of the last expr. (and) returns true." {:added "1.0"} ([] true) ([x] x) ([x & next] `(let [and# ~x] (if and# (and ~@next) and#)))) ;;; Lists ;; list creation user> (list 1 2 3) Сърцето на Clojure (1 2 3) ;; quoted list creation user> (def a-list '(1 2 3 4 5 6 7 8 9 10)) #'user/a-list ;; find the size of a list user> (count a-list) 10 user> (first a-list) 1 user> (rest a-list) (2 3 4 5 6 7 8 9 10) user> (last a-list) 10 ;; find the elements of the list matching a predicate(boolean function) user> (filter even? a-list) (2 4 6 8 10) user> (filter odd? a-list) (1 3 5 7 9) ;; map an anonymous(lambda) function to all elements of the list user> (map #(* % 2) a-list) (2 4 6 8 10 12 14 16 18 20) ;; add an element to the beginning of the list user> (cons 0 a-list) (0 1 2 3 4 5 6 7 8 9 10) ;; cons in a list specific function, conj is a general purpose one and ;; works on all collection (but in a different manner) user> (conj a-list 0) (0 1 2 3 4 5 6 7 8 9 10) ;; retrieve the first five items in a list user> (take 5 a-list) ... (1 2 3 4 5) ;; retrieve all but the first five items in a list user> (drop 5 a-list) (6 7 8 9 10) user> (take-while #(< % 3) a-list) (1 2) user> (drop-while #(> % 3) a-list) (1 2 3 4 5 6 7 8 9 10) user> (drop-while #(< % 3) a-list) (3 4 5 6 7 8 9 10)</p><p>;;; Sets user> (set '(1 2 3 4 5 1 2 3 4)) #{1 2 3 4 5} user> (def a-set #{1 2 3 4 5}) #'user/a-set user> (contains? a-set 3) true user> (contains? a-set 7) false user> (conj a-set 5) #{1 2 3 4 5} user> (conj a-set 6) #{1 2 3 4 5 6} user> (disj a-set 1) #{2 3 4 5} user> (get a-set 1) 1 user> (get a-set 7) nil Хешове</p><p>;;; Maps user> (hash-map :Bozhidar :Batsov :Bruce :Wayne :Selina :Kyle) {:Selina :Kyle, :Bozhidar :Batsov, :Bruce :Wayne} user> (def a-map {:Bozhidar :Batsov, :Bruce :Wayne, :Selina :Kyle}) #'user/a-map user> a-map {:Bozhidar :Batsov, :Bruce :Wayne, :Selina :Kyle} user> (get a-map :Bozhidar) :Batsov user> (contains? a-map :Bozhidar) true user> (contains? a-map :Clark) false user> (:Bozhidar a-map) :Batsov user> (assoc a-map :Lois :Lane) {:Lois :Lane, :Bozhidar :Batsov, :Bruce :Wayne, :Selina :Kyle} user> (keys a-map) (:Bozhidar :Bruce :Selina) user> (vals a-map) (:Batsov :Wayne :Kyle) user> (dissoc a-map :Bruce) {:Bozhidar :Batsov, :Selina :Kyle} user> (merge a-map {:Alia :Atreides, :Arya :Stark}) {:Arya :Stark, :Alia :Atreides, :Bozhidar :Batsov, :Bruce :Wayne, :Selina :Kyle} Вектори</p><p>;;; Vectors user> (vector 1 2 3 4) [1 2 3 4] user> [1 2 3 4] [1 2 3 4] user> (def a-vector [1 2 3 4 5]) #'user/a-vector user> (count a-vector) 5 user> (conj a-vector 13) [1 2 3 4 5 13] ;; random access is a constant time operation in vectors user> (nth a-vector 3) 4 user> (pop a-vector) [1 2 3 4] user> (peek a-vector) 5 Програмиране с refs</p><p>(def picked-numbers (ref #{})</p><p>(def secret-num (.nextInt (java.util.Random.) 10))</p><p>(defn guess-number [n] (print "Enter a guess between 1 and 10: ") (flush) (let [guess (java.lang.Integer/parseInt (read-line)) ] (cond (= guess n) (println "You guessed correctly") (contains? (deref picked-numbers) n) (println "Pick another number! You already tried that one.") :else (dosync (alter picked-numbers conj guess))))) user=> (guess-number secret-num) Enter a guess between 1 and 10: 1 #{1} user=> (guess-number secret-num) Enter a guess between 1 and 10: 3 #{1 3} user=> (guess-number secret-num) Enter a guess between 1 and 10: 5 #{1 3 5} Атоми</p><p>(def picked-numbers (atom #{})</p><p>(def secret-num (.nextInt (java.util.Random.) 10))</p><p>(defn guess-number [n] (print "Enter a guess between 1 and 10: ") (flush) (let [guess (java.lang.Integer/parseInt (read-line)) ] (cond (= guess n) (println "You guessed correctly") (contains? (deref picked-numbers) n) (println "Pick another number! You already tried that one.") :else (swap! picked-numbers conj guess)))) user=> (guess-number secret-num) Enter a guess between 1 and 10: 1 #{1} user=> (guess-number secret-num) Enter a guess between 1 and 10: 3 #{1 3} user=> (guess-number secret-num) Enter a guess between 1 and 10: 5 #{1 3 5} ООП по Лиспаджийски</p><p>(defmulti my-add (fn [x y] (and (string? x) (string? y))))</p><p>(defmethod my-add true [x y] (str x y))</p><p>(defmethod my-add false [x y] (+ x y)) user=> (my-add 3 4) ; => 7 user=> (my-add "3" "4") ; => "34" Екстремист съм, какво ми трябва?</p><p>Обичайните заподозрени</p><p>Eclipse</p><p>IntelliJ</p><p>NetBeans</p><p>Emacs + SLIME = Bozhidar’s Choice Повече инфо, моля!</p><p> http://batsov.com/articles/2011/05/12/jvm- langs-clojure/ Stay hungry, stay foolish! FIN</p> </div> </div> </div> </div> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js" integrity="sha512-aVKKRRi/Q/YV+4mjoKBsE4x3H+BkegoM/em46NNlCqNTmUYADjBbeNefNxYV7giUp0VxICtqdrbqU7iVaeZNXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="/js/details118.16.js"></script> <script> var sc_project = 11552861; var sc_invisible = 1; var sc_security = "b956b151"; </script> <script src="https://www.statcounter.com/counter/counter.js" async></script> <noscript><div class="statcounter"><a title="Web Analytics" href="http://statcounter.com/" target="_blank"><img class="statcounter" src="//c.statcounter.com/11552861/0/b956b151/1/" alt="Web Analytics"></a></div></noscript> </body> </html><script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>