├── .gitignore ├── LICENSE ├── README.md ├── benchmark-server.sh ├── build.sbt ├── compile-plugin.sh ├── docs ├── .gitignore ├── Makefile └── source │ ├── .gitignore │ ├── SbtStub.scala │ ├── conf.py │ ├── gen.sh │ ├── index.rst │ ├── scala-src │ ├── Installation.scala │ ├── Introduction.scala │ ├── Overview.scala │ ├── Stuff.scala │ ├── Syntax.scala │ └── Usage.scala~ │ └── style.css ├── eclipse ├── EvaluateSpecs.launch └── ParserSpecs.launch ├── project └── plugins.sbt ├── src ├── main │ ├── ls │ │ └── 0.5.2.json │ ├── resources │ │ └── scalac-plugin.xml │ └── scala │ │ └── net │ │ └── virtualvoid │ │ └── string │ │ ├── AST.scala │ │ ├── ESParser.scala │ │ ├── EnhanceStringsAnnotation.scala │ │ ├── EnhancedStrings.scala │ │ ├── EnhancedStringsPlugin.scala │ │ ├── ObjectFormatter.scala │ │ └── TypeHelper.scala └── test │ └── scala │ └── net │ └── virtualvoid │ └── string │ ├── ESParserSpecs.scala │ └── EvaluateSpecs.scala ├── usage ├── .gitignore ├── project │ ├── build.properties │ └── build │ │ └── Project.scala └── src │ └── main │ └── scala │ └── Test.scala └── with-plugin.sh /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.swp 3 | /.manager 4 | /.project 5 | /.classpath 6 | /.settings 7 | /*.class 8 | project/boot/ 9 | lib_managed/ 10 | /.idea 11 | *.iml 12 | *~ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 - 2011 Johannes Rudolph 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. The name of the author may not be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Scala Enhanced Strings - A Scala compiler plugin for string interpolation and more 2 | ================================ 3 | 4 | **Note: this project has been superseded by Scala's [string interpolation support](http://docs.scala-lang.org/overviews/core/string-interpolation.html)** 5 | 6 | 7 | See http://jrudolph.github.com/scala-enhanced-strings for the old documentation. 8 | -------------------------------------------------------------------------------- /benchmark-server.sh: -------------------------------------------------------------------------------- 1 | java -cp /home/johannes/.m2/repository/asm/asm/3.1/asm-3.1.jar:/home/johannes/.m2/repository/junit/junit/4.4/junit-4.4.jar:/home/johannes/.m2/repository/net/virtualvoid/bytecode/bytecode/2.1-SNAPSHOT/bytecode-2.1-SNAPSHOT.jar:/home/johannes/.m2/repository/org/hamcrest/hamcrest-core/1.1/hamcrest-core-1.1.jar:/home/johannes/.m2/repository/org/hamcrest/hamcrest-library/1.1/hamcrest-library-1.1.jar:/home/johannes/.m2/repository/org/jmock/jmock/2.4.0/jmock-2.4.0.jar:/home/johannes/.m2/repository/org/scala-lang/scala-library/2.7.3/scala-library-2.7.3.jar:/home/johannes/.m2/repository/org/scalacheck/scalacheck/1.3/scalacheck-1.3.jar:/home/johannes/.m2/repository/org/scalatest/scalatest/0.9.3/scalatest-0.9.3.jar:/home/johannes/.m2/repository/org/specs/specs/1.3.1/specs-1.3.1.jar:target/test-classes:target/classes -server -XX:CICompilerCount=1 -XX:+PrintCompilation -verbose:gc net.virtualvoid.string.Benchmark $* 2 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | organization := "net.virtualvoid" 2 | 3 | name := "scala-enhanced-strings" 4 | 5 | version := "0.5.2" 6 | 7 | homepage := Some(url("http://jrudolph.github.com/scala-enhanced-strings")) 8 | 9 | licenses in GlobalScope += "BSD" -> url("https://raw.github.com/jrudolph/scala-enhanced-strings/master/LICENSE") 10 | 11 | libraryDependencies ++= 12 | Seq("org.scala-tools.testing" % "specs_2.9.0-1" % "1.6.8" % "test", 13 | "org.scala-lang" % "scala-compiler" % "2.9.1") 14 | 15 | scalacOptions ++= Seq("-unchecked", "-deprecation") 16 | 17 | scalacOptions in (Compile, console) <+= (packageBin in Compile) map { bin => 18 | "-Xplugin:"+bin.absolutePath 19 | } 20 | 21 | seq(lsSettings :_*) 22 | 23 | (LsKeys.tags in LsKeys.lsync) := Seq("compiler-plugin", "string", "syntax", "string-interpolation") 24 | 25 | (LsKeys.docsUrl in LsKeys.lsync) <<= homepage 26 | 27 | (externalResolvers in LsKeys.lsync) := Seq( 28 | "Virtual-Void repository" at "http://mvn.virtual-void.net") 29 | 30 | (description in LsKeys.lsync) := 31 | "A scalac compiler plugin that adds string interpolation and more to scala." 32 | -------------------------------------------------------------------------------- /compile-plugin.sh: -------------------------------------------------------------------------------- 1 | mvn package && 2 | zip target/objectformatter-1.0-SNAPSHOT.jar scalac-plugin.xml && 3 | ./with-plugin.sh 4 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | 15 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " pickle to make pickle files" 22 | @echo " json to make JSON files" 23 | @echo " htmlhelp to make HTML files and a HTML help project" 24 | @echo " qthelp to make HTML files and a qthelp project" 25 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 26 | @echo " changes to make an overview of all changed/added/deprecated items" 27 | @echo " linkcheck to check all external links for integrity" 28 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 29 | 30 | clean: 31 | -rm -rf $(BUILDDIR)/* 32 | 33 | html: 34 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 35 | @echo 36 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 37 | 38 | dirhtml: 39 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 40 | @echo 41 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 42 | 43 | pickle: 44 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 45 | @echo 46 | @echo "Build finished; now you can process the pickle files." 47 | 48 | json: 49 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 50 | @echo 51 | @echo "Build finished; now you can process the JSON files." 52 | 53 | htmlhelp: 54 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 55 | @echo 56 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 57 | ".hhp project file in $(BUILDDIR)/htmlhelp." 58 | 59 | qthelp: 60 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 61 | @echo 62 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 63 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 64 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ScalaEnhancedStrings.qhcp" 65 | @echo "To view the help file:" 66 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ScalaEnhancedStrings.qhc" 67 | 68 | latex: 69 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 70 | @echo 71 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 72 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 73 | "run these through (pdf)latex." 74 | 75 | changes: 76 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 77 | @echo 78 | @echo "The overview file is in $(BUILDDIR)/changes." 79 | 80 | linkcheck: 81 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 82 | @echo 83 | @echo "Link check complete; look for any errors in the above output " \ 84 | "or in $(BUILDDIR)/linkcheck/output.txt." 85 | 86 | doctest: 87 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 88 | @echo "Testing of doctests in the sources finished, look at the " \ 89 | "results in $(BUILDDIR)/doctest/output.txt." 90 | -------------------------------------------------------------------------------- /docs/source/.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | /..sxr 3 | *~ 4 | -------------------------------------------------------------------------------- /docs/source/SbtStub.scala: -------------------------------------------------------------------------------- 1 | package sbt { 2 | trait ProjectInfo 3 | class DefaultProject(info: ProjectInfo) 4 | trait AutoCompilerPlugins { 5 | def compilerPlugin(str: String) = null 6 | } 7 | } 8 | 9 | package object sbt { 10 | implicit def strHasPercentOps(str: String) = new { 11 | def %(next: String): String = next 12 | def %%(next: String): String = next 13 | def at(next: String): String = next 14 | def :=(a: Any) = null 15 | } 16 | 17 | var resolvers = "" 18 | def autoCompilerPlugins = strHasPercentOps(null) 19 | def addCompilerPlugin(str: String){} 20 | } 21 | 22 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Scala Enhanced Strings documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Jun 7 15:28:07 2010. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.append(os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # Add any Sphinx extension module names here, as strings. They can be extensions 24 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 25 | extensions = [] 26 | 27 | # Add any paths that contain templates here, relative to this directory. 28 | templates_path = ['_templates'] 29 | 30 | # The suffix of source filenames. 31 | source_suffix = '.rst' 32 | 33 | # The encoding of source files. 34 | #source_encoding = 'utf-8' 35 | 36 | # The master toctree document. 37 | master_doc = 'index' 38 | 39 | # General information about the project. 40 | project = u'Scala Enhanced Strings' 41 | copyright = u'2010, Johannes Rudolph' 42 | 43 | # The version info for the project you're documenting, acts as replacement for 44 | # |version| and |release|, also used in various other places throughout the 45 | # built documents. 46 | # 47 | # The short X.Y version. 48 | version = '0.5' 49 | # The full version, including alpha/beta/rc tags. 50 | release = '0.5.1' 51 | 52 | # The language for content autogenerated by Sphinx. Refer to documentation 53 | # for a list of supported languages. 54 | #language = None 55 | 56 | # There are two options for replacing |today|: either, you set today to some 57 | # non-false value, then it is used: 58 | #today = '' 59 | # Else, today_fmt is used as the format for a strftime call. 60 | #today_fmt = '%B %d, %Y' 61 | 62 | # List of documents that shouldn't be included in the build. 63 | #unused_docs = [] 64 | 65 | # List of directories, relative to source directory, that shouldn't be searched 66 | # for source files. 67 | exclude_trees = [] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. Major themes that come with 93 | # Sphinx are currently 'default' and 'sphinxdoc'. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_use_modindex = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, an OpenSearch description file will be output, and all pages will 153 | # contain a tag referring to it. The value of this option must be the 154 | # base URL from which the finished HTML is served. 155 | #html_use_opensearch = '' 156 | 157 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 158 | #html_file_suffix = '' 159 | 160 | # Output file base name for HTML help builder. 161 | htmlhelp_basename = 'ScalaEnhancedStringsdoc' 162 | 163 | 164 | # -- Options for LaTeX output -------------------------------------------------- 165 | 166 | # The paper size ('letter' or 'a4'). 167 | #latex_paper_size = 'letter' 168 | 169 | # The font size ('10pt', '11pt' or '12pt'). 170 | #latex_font_size = '10pt' 171 | 172 | # Grouping the document tree into LaTeX files. List of tuples 173 | # (source start file, target name, title, author, documentclass [howto/manual]). 174 | latex_documents = [ 175 | ('index', 'ScalaEnhancedStrings.tex', u'Scala Enhanced Strings Documentation', 176 | u'Johannes Rudolph', 'manual'), 177 | ] 178 | 179 | # The name of an image file (relative to this directory) to place at the top of 180 | # the title page. 181 | #latex_logo = None 182 | 183 | # For "manual" documents, if this is true, then toplevel headings are parts, 184 | # not chapters. 185 | #latex_use_parts = False 186 | 187 | # Additional stuff for the LaTeX preamble. 188 | #latex_preamble = '' 189 | 190 | # Documents to append as an appendix to all manuals. 191 | #latex_appendices = [] 192 | 193 | # If false, no module index is generated. 194 | #latex_use_modindex = True 195 | -------------------------------------------------------------------------------- /docs/source/gen.sh: -------------------------------------------------------------------------------- 1 | SXR=/home/johannes/git/opensource/sxr/target/scala_2.9.1/sxr_2.9.1-0.2.5-SNAPSHOT.jar 2 | ES=../../target/scala_2.9.1/scala-enhanced-strings_2.9.1-0.5.2.jar 3 | #SBT=../../project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/sbt_2.7.7-0.7.4.jar 4 | 5 | scalac \ 6 | -Xplugin:$ES \ 7 | -Xplugin:$SXR \ 8 | -P:sxr:base-directory:scala-src \ 9 | -Xprint-pos \ 10 | -Xprint:enhanced-strings \ 11 | -Ylog:sxr \ 12 | scala-src/*.scala && 13 | # -Ystop:pickler \ 14 | #-Yrangepos \ 15 | # -Xprint:enhanced-strings \ 16 | # -Yshow-trees \ 17 | 18 | cp style.css ..sxr && 19 | 20 | gnome-open ..sxr/Overview.scala.html 21 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Scala Enhanced Strings 2 | ====================== 3 | 4 | Scala Enhanced Strings reinforces Scala's string constants with 5 | `variable interpolation`_ as known from other modern languages like 6 | Ruby, Perl, PHP or Shell scripting languages. It is implemented as a 7 | Scala compiler plugin and accurately configurable for single syntactic scopes. 8 | 9 | Installation 10 | ------------ 11 | 12 | Usage 13 | ----- 14 | 15 | Syntax 16 | ------ 17 | 18 | Scala Enhanced Strings define a pluggable parser concept. This makes it possible 19 | to change the syntax while still staying backwards-compatible. 20 | 21 | Regardless of the exact syntax, an enhanced string is a sequence of different elements. 22 | 23 | * Literal strings 24 | * Expressions which are converted to strings: 25 | * By calling 'toString' 26 | * By using a special formatter, right now there are formatters for: 27 | * java.util.Date using a SimpleDateFormatter behind the scenes 28 | * Conditional formatting for Boolean and scala.Option values 29 | * Expansion and subformatting of Iterable values 30 | 31 | Known Issues 32 | ~~~~~~~~~~~~ 33 | 34 | * Probably some parser issues 35 | * Inaccurate positions in error messages 36 | * No possibility to switch ES off in some scopes 37 | -------------------------------------------------------------------------------- /docs/source/scala-src/Installation.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.strings 2 | 3 | object Installation extends ScalaEnhancedStrings 4 | with StandaloneUsage 5 | with UseWithSBT 6 | 7 | trait StandaloneUsage { 8 | /* 9 | - Grab the latest jar file from http://github.com/jrudolph/scala-enhanced-strings/downloads. 10 | - Call scalac with -Xplugin: 11 | - That's it. 12 | */ 13 | } 14 | 15 | trait UseWithSBT { 16 | if ("sbt.version" == 0.7) { 17 | /* See the usage example at http://github.com/jrudolph/scala-enhanced-strings/tree/master/usage 18 | or here: */ 19 | 20 | /* Starting with a normal project descriptor... */ 21 | import sbt._ 22 | 23 | class ESUsageExample(info: ProjectInfo) 24 | extends DefaultProject(info) 25 | /* ... extend your project descriptor */ 26 | with AutoCompilerPlugins { 27 | 28 | val virtualVoid = "Virtual-Void repository" at "http://mvn.virtual-void.net" 29 | 30 | val es = compilerPlugin("net.virtualvoid" %% "scala-enhanced-strings" % "0.5.2") 31 | } 32 | } else if ("sbt.version" >= "0.10") { 33 | import sbt._ 34 | /* Adding this to your build configuration should work: */ 35 | 36 | resolvers += "Virtual-Void repository" at "http://mvn.virtual-void.net" 37 | 38 | autoCompilerPlugins := true 39 | addCompilerPlugin("net.virtualvoid" %% "scala-enhanced-strings" % "0.5.2") 40 | } 41 | } 42 | 43 | object Usage extends ScalaEnhancedStrings { 44 | /* Make sure you have installed the jar as described in */ Installation /* 45 | At the scope where you want to use enhanced strings put a marker annotation: */ 46 | 47 | @EnhanceStrings // enhance strings in this scope 48 | trait Example1 { 49 | val x = 5 50 | val str = "Inner string arithmetics: #{{ x * x + 12 }}" 51 | } 52 | 53 | /* The current enhanced string syntax is not fixed. Actually, it is 54 | just a particular flavor. In the future there may be other 55 | flavors. You can specify which version you programmed against by 56 | specifying it in the @EnhancedStrings annotation. So your program 57 | is compatible even with */ new ScalaEnhancedStrings /* versions */ 58 | 59 | @EnhanceStrings(syntax= "poros", version = 1) // poros is the default syntax flavor 60 | trait Example2 { 61 | val x = "The class of my parent object is: #Usage.getClass" 62 | } 63 | 64 | } 65 | 66 | object AtRuntime { 67 | /* After compiling with scala-enhanced-strings the plugins leaves 68 | no traces of itself. No additional dependency jars are needed. */ 69 | 70 | /* Next chapter: */ Syntax 71 | } 72 | 73 | 74 | /* SeS - Scala Enhanced Strings 75 | * Copyright 2008 - 2011 Johannes Rudolph 76 | * Visit http://github.com/jrudolph/scala-enhanced-strings 77 | * File bugs at http://github.com/jrudolph/scala-enhanced-strings/issues 78 | * Mailing list at http://groups.google.com/group/scala-enhanced-strings 79 | */ 80 | 81 | 82 | -------------------------------------------------------------------------------- /docs/source/scala-src/Introduction.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.strings 2 | 3 | object Introduction extends ScalaEnhancedStrings { 4 | } 5 | -------------------------------------------------------------------------------- /docs/source/scala-src/Overview.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.strings 2 | 3 | /* Welcome! This is the documentation for the 4 | Scala Enhanced Strings (SeS) compiler plugin. */ 5 | 6 | class ScalaEnhancedStrings { 7 | /* Scala Enhanced Strings reinforces Scala's string constants with 8 | "variable interpolation" as known from other modern languages like 9 | Ruby, Perl, PHP or Shell scripting languages. It is implemented as a 10 | Scala compiler plugin and configurable on a scope-by-scope basis */ 11 | 12 | val chapters = Array( 13 | Installation, 14 | Usage, 15 | Syntax, 16 | Stuff 17 | ) 18 | } 19 | 20 | /* SeS - Scala Enhanced Strings 21 | * Copyright 2008 - 2011 Johannes Rudolph 22 | * Visit http://github.com/jrudolph/scala-enhanced-strings 23 | * File bugs at http://github.com/jrudolph/scala-enhanced-strings/issues 24 | * Mailing list at http://groups.google.com/group/scala-enhanced-strings 25 | */ 26 | -------------------------------------------------------------------------------- /docs/source/scala-src/Stuff.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.strings 2 | 3 | object Stuff extends ScalaEnhancedStrings { 4 | val license = "BSD License" 5 | 6 | trait Facts { 7 | class EnhancedStringsPlugin { 8 | val runsAfter = "parser" 9 | } 10 | 11 | /* 12 | - To see the code the plugin generates use this command line 13 | while compiling: 14 | 15 | scalac -Xplugin:... -Xprint:enhanced-strings 16 | 17 | - Most of the plugin is a spin-off of work I've done 18 | before. When the topic of variable interpolation was 19 | mentioned on the mailing list in summer 2009 I could quickly 20 | hack together a basically working version because I've 21 | already had a parser available. Literal Scala expressions 22 | aside, you can use the formatter without the plugin 23 | standalone, doing all the parsing at runtime. There's a 24 | currently unmaintained module which compiles a formatting 25 | string to bytecodes at runtime, as well. */ 26 | } 27 | trait IDEIntegration { 28 | /* Syntax highlighting doesn't work in IDEs. However, at least in ensime 29 | the common features are working: error reporting, showing types, inspecting 30 | variables. (Only, if you make ensime compile with the plugin enabled, which 31 | I managed to do only by enabling it directly in ensime's sources.) 32 | 33 | Some IDEs come with their own presentation compiler and may not know the 34 | @EnhanceStrings annotation. In this case you can add the plugin jar file 35 | as a dependency, it contains the annotation. (Though, this is not needed 36 | for normal compilation with the plugin: The plugin deletes the annotation 37 | from the source code before Scala is even noticing it.) 38 | */ 39 | } 40 | trait KnownIssues { 41 | /* 42 | - The parser may a bit unreliable at the edges. 43 | - More conversions needed 44 | */ 45 | } 46 | trait Contact { 47 | val authorsEmail = "johannes.rudolph@gmail.com" 48 | val mailingList = "http://groups.google.com/group/scala-enhanced-strings" 49 | val bugTracker = "http://github.com/jrudolph/scala-enhanced-strings/issues" 50 | } 51 | 52 | /* This documentation was formatted by Mark Harrah's sxr */ 53 | } 54 | 55 | /* SeS - Scala Enhanced Strings 56 | * Copyright 2008 - 2011 Johannes Rudolph 57 | * Visit http://github.com/jrudolph/scala-enhanced-strings 58 | * File bugs at http://github.com/jrudolph/scala-enhanced-strings/issues 59 | * Mailing list at http://groups.google.com/group/scala-enhanced-strings 60 | */ 61 | -------------------------------------------------------------------------------- /docs/source/scala-src/Syntax.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.strings 2 | 3 | @EnhanceStrings(syntax="poros", version=1) 4 | object Syntax extends ScalaEnhancedStrings { 5 | /* The plugin reinforces string constants with several new possibilities 6 | to add data into a String. 7 | 8 | The easiest enhanced string is just a literal string without any 9 | control characters. */ 10 | val literalString = "This is just a normal String" 11 | 12 | /* For the more interesting features, the two important concepts to 13 | know are */ Expressions; /* and */ Conversions 14 | 15 | object Expressions { 16 | 17 | /* Expressions are introduced by the # (sharp) sign */ 18 | val x = 5 19 | val stringWithNumber = "The number is #x!" 20 | 21 | /* With this syntax, you can access the value of a variable from 22 | the scope. Properties (ie methods with no parameters) 23 | can be accessed with the usual dot notation. */ 24 | val aString = "test" 25 | val info = "aString has the length #aString.length" 26 | 27 | /* To declare unambigiously where an expression ends, put it into 28 | braces */ 29 | val braced = "Length: #{aString.length}.stuff" 30 | 31 | // val unbraced = "Length: #aString.length.stuff" 32 | // error here: "value stuff is not a member of Int" 33 | 34 | /* Up until now, you've just seen simple expressions, the full power 35 | unleashes when you insert arbitrary Scala expressions into a string 36 | using #{{ ... }} */ 37 | val inStringCalculation = "What is x + 5 + 12? #{{ x + 5 + 12 }}" 38 | } 39 | 40 | /* Not all expressions evaluate to a String value. There are some 41 | convenience */ Conversions /* built directly into the syntax */ 42 | 43 | object Conversions { 44 | /* For each expression, a conversion to String is needed. If you 45 | don't define any conversion, the value is converted by calling 46 | the `toString` method of the value. 47 | 48 | Recall the example from the last section: */ 49 | 50 | val aString = "test" 51 | val info = "aString has the length #aString.length" 52 | 53 | aString.length /* is of type Int, so it has to be converted to a String 54 | before it can be appended to the rest of the String. Behind the scenes, 55 | ScalaEnhancedStrings creates code like this: */ 56 | 57 | val infoAfterSeS = "aString has the length "+aString.length.toString 58 | 59 | 60 | /* More interesting is the set of built-in conversions and the 61 | syntax to use them. (Note: the set of conversions is by no means 62 | complete yet. If you are missing something, please say so.) 63 | 64 | First, there is support for conditional string formatting with the 65 | conversion syntax `?[|]` */ 66 | 67 | val isSummer = true 68 | def proclaimTemperature = 69 | println("It is so #isSummer?[hot|cold]") 70 | 71 | /* For */ Option/*-al values the same syntax applies. In the 72 | there is the expression #it or #this available, 73 | which refers to the contents of the */ Option /* value */ 74 | 75 | val optionalBeer: Option[String] = Some("Rothaus") 76 | val thirstyMan = "He's drinking #optionalBeer?[his #it|nothing]" 77 | // evaluates to "He's drinking his Rothaus" 78 | 79 | /* Then, there's support for formatting whole sequences of values (of type Iterator) 80 | by using the `[]{}*` syntax. 81 | Inside of the you can access the current element 82 | by #it or #this. The asterisk '*' at the end in enhanced string syntax 83 | instructs the plugin to expand all these values, format each element 84 | as specified with the inner formatter and then join them together 85 | using the separator */ 86 | 87 | val fruit = List("Apple", "Orange", "Lemon", "Banana") 88 | val breakfast = "Today there's #fruit[the tasty #it]{, }* in my yoghurt" 89 | // == "Today there's the tasty Apple, the tasty Orange ... 90 | 91 | /* The inner part of the last string could be rewritten as */ 92 | fruit.map(it => "the tasty "+it.toString).mkString(", ") 93 | /* in fact, this is exactly what the plugin does. */ 94 | 95 | /* You can omit the in which case #it is assumed. You 96 | can omit the separator, as well, in which case the separator is the empty string. */ 97 | 98 | val allDigits = "#{{0 to 9}}*" // == "0123456789" 99 | 100 | } 101 | 102 | object ControlCharacterEscaping { 103 | /* Control characters '#', '[', ']', and '|' have to be 104 | quoted with '#' if they are meant literally. 105 | (Don't mind the broken syntax highlighting here) */ 106 | 107 | val notAnExpression = "I love C##" 108 | val thisIsABar = "#|" 109 | } 110 | 111 | /* next chapter: */ Stuff 112 | } 113 | 114 | /* SeS - Scala Enhanced Strings 115 | * Copyright 2008 - 2011 Johannes Rudolph 116 | * Visit http://github.com/jrudolph/scala-enhanced-strings 117 | * File bugs at http://github.com/jrudolph/scala-enhanced-strings/issues 118 | * Mailing list at http://groups.google.com/group/scala-enhanced-strings 119 | */ 120 | -------------------------------------------------------------------------------- /docs/source/scala-src/Usage.scala~: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.strings 2 | 3 | object Syntax { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /docs/source/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: black; 3 | color: #DDDDDD; 4 | } 5 | 6 | pre, code { 7 | font-family: Inconsolata, Monaco, Consolas, monospace; 8 | font-size: 16pt; 9 | line-height: 20px; 10 | } 11 | 12 | /* syntax highlighting */ 13 | .keyword { font-weight: bolder; color: #777777; font-variant: small-caps; font-size: smaller;} 14 | .string { color: #BFFFCE; } 15 | .symbol { color: red; font-weight: bolder;} 16 | .char { color: #FF00FF; } 17 | .double { color: blue; } 18 | .float { color: blue; } 19 | .long { color: #008080; } 20 | .int { color: #4200DF; } 21 | .comment { 22 | color: #BFD0FF; 23 | font-size: 14pt; 24 | font-style: italic; 25 | font-family: serif; 26 | line-height: normal; 27 | } 28 | .delimiter { 29 | color: #777777; 30 | } 31 | 32 | a { color: #aa0000; } 33 | a:link { 34 | color: #DF0000; 35 | } 36 | 37 | a:visited { 38 | color: #DF0000; 39 | } 40 | 41 | /* to highlight a reference or definition */ 42 | :target, .highlighted 43 | { 44 | background-color: #444444; 45 | color: #FF910F; 46 | } 47 | 48 | body { 49 | font-family: Helvetica, Arial; 50 | font-size: 12px; 51 | } 52 | 53 | div.tool { 54 | display: none; 55 | position: fixed; 56 | bottom: 0; 57 | background-color: rgb(48, 48, 48); 58 | color: rgb(243, 243, 243); 59 | padding: 5px; 60 | } 61 | 62 | div.tool a, div.tool a:visited, div.tool a:link { 63 | color: white; 64 | font-weight: bold; 65 | } 66 | 67 | div#export-control { right: 0; text-align: right; } 68 | div#export-control a { padding: 0 5px; } 69 | div#export { left: 0; width: 100%; } 70 | div#export code { display: block; padding-right: 60px; } 71 | 72 | div#pop-out { left: 0; width: 100%; } 73 | 74 | div#pop-out input { 75 | margin-right: .75em; 76 | vertical-align: middle; 77 | } 78 | -------------------------------------------------------------------------------- /eclipse/EvaluateSpecs.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /eclipse/ParserSpecs.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | 2 | 3 | resolvers ++= Seq( 4 | "less is" at "http://repo.lessis.me", 5 | "coda" at "http://repo.codahale.com") 6 | 7 | addSbtPlugin("me.lessis" % "ls-sbt" % "0.1.0") 8 | -------------------------------------------------------------------------------- /src/main/ls/0.5.2.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "organization":"net.virtualvoid", 4 | "name":"scala-enhanced-strings", 5 | "version":"0.5.2", 6 | "description":"A scalac compiler plugin that adds string interpolation and more to scala.", 7 | "site":"http://jrudolph.github.com/scala-enhanced-strings", 8 | "tags":["compiler-plugin","string","syntax","string-interpolation"], 9 | "docs":"http://jrudolph.github.com/scala-enhanced-strings", 10 | "licenses": [{ 11 | "name": "BSD", 12 | "url": "https://raw.github.com/jrudolph/scala-enhanced-strings/master/LICENSE" 13 | }], 14 | "resolvers": ["http://mvn.virtual-void.net"], 15 | "dependencies": [], 16 | "scalas": ["2.9.1"], 17 | "sbt": false 18 | } -------------------------------------------------------------------------------- /src/main/resources/scalac-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | enhanced-strings 3 | net.virtualvoid.string.EnhancedStringsPlugin 4 | 5 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/string/AST.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | import scala.util.parsing.input.Positional 8 | 9 | object AST{ 10 | trait FormatElement extends Positional { 11 | def format(o:AnyRef):String 12 | } 13 | case class Literal(str:String) extends FormatElement{ 14 | def chars = str 15 | def format(o:AnyRef):String = str 16 | } 17 | case class ToStringConversion(exp:Exp) extends FormatElement{ 18 | def format(o:AnyRef):String = exp.eval(o).toString 19 | } 20 | case class Conditional(condition:Exp,thenToks:FormatElementList,elseToks:FormatElementList) extends FormatElement{ 21 | def chars ="" 22 | def format(o:AnyRef) = condition.eval(o) match { 23 | case java.lang.Boolean.TRUE => thenToks.format(o) 24 | case java.lang.Boolean.FALSE => elseToks.format(o) 25 | case x: Option[_] => x.asInstanceOf[Option[AnyRef]].map(thenToks.format).getOrElse(elseToks.format(o)) 26 | } 27 | } 28 | case class DateConversion(exp:Exp,format:String) extends FormatElement{ 29 | val df = new java.text.SimpleDateFormat(format) 30 | def format(o:AnyRef) = df.format(exp.eval(o) match { 31 | case cal:java.util.Calendar => cal.getTime 32 | case date:java.util.Date => date 33 | }) 34 | def chars = "" 35 | } 36 | case class Expand(exp:Exp,sep:String,inner:FormatElementList) extends FormatElement{ 37 | //def chars = exp.chars + ":" + sep 38 | def realEval(l:Iterable[AnyRef]):String = l.map(inner.format(_)) mkString sep 39 | import Java.it2it 40 | def format(o:AnyRef) = exp.eval(o) match{ 41 | // array or collection or similar 42 | case l: java.lang.Iterable[_] => realEval(l.asInstanceOf[java.lang.Iterable[AnyRef]]) 43 | case l: Iterable[_] => realEval(l.asInstanceOf[Iterable[AnyRef]]) 44 | case a: Array[AnyRef] => realEval(a) 45 | } 46 | } 47 | 48 | trait Exp extends Positional { 49 | def eval(o: AnyRef): AnyRef 50 | } 51 | case class Ident(identifier:String) extends Exp { 52 | def chars = identifier 53 | 54 | import java.lang.reflect.{Method} 55 | import java.lang.NoSuchMethodException 56 | def findMethod(c:Class[_],name:String):Option[Method] = 57 | try { 58 | val res = c.getMethod(name) 59 | res.setAccessible(true) 60 | Some(res) 61 | }catch{ 62 | case e:NoSuchMethodException => None 63 | } 64 | def realmethod(cl:Class[_]):Method = 65 | Array("get"+capitalize(identifier),identifier) 66 | .flatMap(findMethod(cl,_).toList).headOption 67 | .getOrElse(throw new java.lang.Error("couldn't find method " + identifier + " in class "+cl.getName+" methods: "+cl.getMethods.map(_.getName).mkString(", "))) 68 | var m:Method = null 69 | def method(cl:Class[_]) = { 70 | if (m == null){ 71 | m = realmethod(cl) 72 | } 73 | m 74 | } 75 | def returnType(callingCl:Class[_]):Class[_] = method(callingCl).getReturnType 76 | def genericReturnType(callingCl:Class[_]):java.lang.reflect.Type = method(callingCl).getGenericReturnType 77 | def capitalize(s:String):String = s.substring(0,1).toUpperCase + s.substring(1) 78 | def eval(o:AnyRef) = method(o.getClass).invoke(o) 79 | } 80 | case class ScalaExp(exp: String) extends Exp { 81 | override def eval(o: AnyRef) = throw new UnsupportedOperationException("not supported in interpreter yet") 82 | } 83 | case object ThisExp extends Exp { 84 | override def eval(o:AnyRef) = o 85 | def returnType(callingCl:Class[_]):Class[_] = callingCl 86 | def genericReturnType(callingCl:Class[_]):java.lang.reflect.Type = 87 | throw new java.lang.Error("No generic type information available for "+callingCl.getName+ 88 | " since it is erased. #this can't be used in conditional or expand expressions") 89 | } 90 | case class ParentExp(inner:Exp, parent: String) extends Exp { 91 | override def eval(o:AnyRef) = inner.eval(Ident(parent).eval(o)) 92 | } 93 | 94 | case class FormatElementList(elements:Seq[FormatElement]){ 95 | def format(o:AnyRef):String = elements.map(_.format(o)) mkString "" 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/string/ESParser.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | import scala.util.parsing.input.Reader 8 | import scala.util.parsing.combinator._ 9 | import scala.util.parsing.combinator.lexical._ 10 | import scala.util.parsing.combinator.syntactical._ 11 | import scala.util.parsing.syntax._ 12 | 13 | object Java{ 14 | implicit def it2it[T](it:java.lang.Iterable[T]):Iterable[T] = new Iterable[T] { 15 | def iterator = new Iterator[T] { 16 | val innerIt = it.iterator 17 | def hasNext = innerIt.hasNext 18 | def next = innerIt.next 19 | } 20 | } 21 | } 22 | 23 | class ParseException(msg:String) extends Exception(msg) 24 | 25 | // syntax flavor name and version 26 | case class VersionInfo(flavor: String, version: Int) 27 | 28 | trait ESParser { 29 | def parse(input:String): AST.FormatElementList 30 | def Version: VersionInfo 31 | } 32 | 33 | object ParserFactory { 34 | val defaultVersion: VersionInfo = VersionInfo("poros", 1) 35 | def parser(info: VersionInfo = defaultVersion): Option[ESParser] = info match { 36 | case EnhancedStringFormatParser.Version => Some(EnhancedStringFormatParser) 37 | case _ => None 38 | } 39 | } 40 | 41 | object EnhancedStringFormatParser extends RegexParsers with ESParser { 42 | val Version = VersionInfo("poros", 1) 43 | 44 | import AST._ 45 | 46 | override type Elem = Char 47 | type Tokens = FormatElement 48 | 49 | implicit def extendParser[T](x:Parser[T]):EParser[T] = EParser[T](x) 50 | 51 | def escaped(char:String):Parser[String] = "#" ~ char ^^ (x=>char) 52 | 53 | val expStartChar = '#' 54 | 55 | def char = "[^#\\]|\\[]".r | escaped("[") | escaped("]") | escaped("#") | escaped("|") 56 | def chars: Parser[String] = char ~ rep(char) ^^ {case first ~ rest => first :: rest reduceLeft (_+_)} 57 | 58 | def idChar = "\\w".r 59 | def lit:Parser[Literal] = positioned(chars ^^ {case str => Literal(str)}) 60 | 61 | def idPart:Parser[String] = idChar ~ rep(idChar) ^^ {case first ~ rest => first :: rest mkString ""} 62 | def id:Parser[Exp] = positioned( 63 | "this" ^^ {str => ThisExp} | 64 | idPart ~ opt("." ~> id) ^^ {case str ~ Some(inner) => ParentExp(inner, str) 65 | case str ~ None => Ident(str)}) 66 | 67 | def endOrChars: Parser[String] = not(literal("}}")) ~ char ^^ { case x ~ ch => "" + ch } 68 | def scalaExpBody: Parser[ScalaExp] = endOrChars ~ rep(endOrChars) ^^ { case first ~ rest => ScalaExp(first :: rest mkString "") } 69 | def scalaExp: Parser[ScalaExp] = literal("{{") ~!> positioned(scalaExpBody) <~! literal("}}") 70 | 71 | def exp: Parser[Exp] = expStartChar ~> 72 | (scalaExp | id | extendParser("{") ~!> id <~! "}") 73 | 74 | def expAsString:Parser[FormatElement] = exp ^^ {case exp => ToStringConversion(exp)} 75 | 76 | def sepChars = "[^}]*".r 77 | def expand = exp ~ opt(inners) ~ opt(extendParser('{') ~!> sepChars <~! '}') <~ "*" ^^ {case exp ~ x ~ separator => Expand(exp,separator.getOrElse(""),x.getOrElse(FormatElementList(List(ToStringConversion(ThisExp)))))} 78 | 79 | def dateConversion:Parser[String] = extendParser("->date[") ~!> "[^\\]]*".r <~ "]" 80 | def conversion = exp ~ dateConversion ^^ {case exp ~ format => DateConversion(exp,format)} 81 | 82 | def optionalElseClause: Parser[FormatElementList] = 83 | opt("|" ~> tokens) ^^ (_.getOrElse(FormatElementList(Seq(Literal(""))))) 84 | 85 | def clauses = extendParser("?[") ~!> 86 | (tokens ~ optionalElseClause <~ "]") 87 | 88 | def conditional = exp ~ clauses ^^ {case exp ~ (ifs ~ elses) => Conditional(exp, ifs, elses)} 89 | 90 | def innerExp:Parser[FormatElement] = (expand 91 | | conversion 92 | | conditional 93 | | expAsString 94 | | lit) 95 | def inners = '[' ~> tokens <~ ']' 96 | 97 | def tokens:Parser[FormatElementList] = rep(innerExp) ^^ {case toks => FormatElementList(toks)} 98 | 99 | override val skipWhitespace = false 100 | 101 | case class EParser[T](oldThis:Parser[T]){ 102 | def ~!> [U](p: => Parser[U]): Parser[U] 103 | = OnceParser{ (for(a <- oldThis; b <- commit(p)) yield b).named("~!>") } 104 | 105 | def <~! [U](p: => Parser[U]): Parser[T] 106 | = OnceParser{ (for(a <- oldThis; b <- commit(p)) yield a).named("<~!") } 107 | } 108 | 109 | def parse(input:String):FormatElementList = 110 | phrase(tokens)(new scala.util.parsing.input.CharArrayReader(input.toCharArray)) match { 111 | case Success(res,_) => res 112 | case x:NoSuccess => throw new ParseException(x.msg) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/string/EnhanceStringsAnnotation.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * The annotation is defined explicitly here, so that you can add the 3 | * plugin's jar file to your build dependencies if your IDE doesn't 4 | * support compiler plugins for presentation. 5 | */ 6 | class EnhanceStrings(syntax: String, version: Int) extends scala.annotation.Annotation 7 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/string/EnhancedStrings.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | // The Visible API 8 | 9 | trait IObjectFormatterFactory { 10 | def format[T<:AnyRef](format:String,o:T):String = formatter(o.getClass.asInstanceOf[Class[T]],format).format(o) 11 | def formatter[T<:AnyRef](clazz:Class[T],format:String):IObjectFormatter[T] 12 | } 13 | 14 | trait IObjectFormatter[T<:AnyRef] { 15 | def format(o:T):String 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/string/EnhancedStringsPlugin.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | import scala.tools.nsc 8 | import nsc.Global 9 | import nsc.Phase 10 | import nsc.plugins.Plugin 11 | import nsc.plugins.PluginComponent 12 | import nsc.transform.{ Transform, TypingTransformers } 13 | import nsc.symtab.Flags 14 | 15 | final class OriginalString(final val string: String) extends StaticAnnotation {} 16 | 17 | class EnhancedStringsPlugin(val global: Global) extends Plugin { 18 | import global._ 19 | 20 | val name = "enhanced-strings" 21 | val description = "allows variable interpolation in strings" 22 | val components = List[PluginComponent](Component) 23 | 24 | private object Component extends PluginComponent with Transform with TypingTransformers { 25 | import global._ 26 | import global.definitions._ 27 | 28 | val global = EnhancedStringsPlugin.this.global 29 | override val runsAfter = List("parser") 30 | /** The phase name of the compiler plugin 31 | * @todo Adapt to specific plugin. 32 | */ 33 | val phaseName = "enhanced-strings" 34 | 35 | def newTransformer(unit: CompilationUnit) = new ESTransformer(unit) 36 | 37 | class ESTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { 38 | localTyper = analyzer.newTyper(analyzer.rootContext(unit, EmptyTree, false)) 39 | import unit.error 40 | 41 | def it = ValDef(Modifiers(Flags.PARAM), "it", TypeTree(), EmptyTree) 42 | 43 | def compiled(els: AST.FormatElementList, pos: Position): Tree = { 44 | import scala.util.parsing.input.Positional 45 | def startOf(e: Positional): Position = e.pos match { 46 | // in case, we don't have a real position, this is only an approximation 47 | case scala.util.parsing.input.NoPosition => pos.makeTransparent 48 | case _ => pos.withPoint(pos.startOrPoint + e.pos.column) 49 | } 50 | def positionOf(e: Positional, length: Int) = e.pos match { 51 | case scala.util.parsing.input.NoPosition => pos.makeTransparent 52 | case _ => 53 | val start = pos.startOrPoint + e.pos.column 54 | new scala.tools.nsc.util.RangePosition(pos.source, start, start, start + length) 55 | } 56 | def compile(els: AST.FormatElementList): Tree = compiled(els, pos) 57 | 58 | def compileParentExpressionInner(inner: AST.Exp, outer: Tree): Tree = inner match { 59 | case AST.ParentExp(inner, parent) => atPos(positionOf(inner, parent.length))(compileParentExpressionInner(inner, Select(outer, parent))) 60 | case AST.Ident(id) => atPos(positionOf(inner, id.length))(Select(outer, id)) 61 | } 62 | 63 | def offsetPositionBy(pos: Position, offset: Int) = pos match { 64 | case p: scala.tools.nsc.util.RangePosition => 65 | new scala.tools.nsc.util.RangePosition(unit.source, p.start+offset, p.start+offset, p.end+offset) 66 | case _ => pos.withSource(unit.source, offset) 67 | } 68 | 69 | def fixPos(pos: Position, tree: Tree) = { 70 | object PositionTreeTraverser extends Traverser { 71 | override def traverse(t: Tree) { 72 | t.pos = offsetPositionBy(t.pos, pos.point) 73 | super.traverse(t) 74 | } 75 | } 76 | 77 | PositionTreeTraverser.traverse(tree) 78 | tree 79 | } 80 | def parse(code: String, pos: Position): Tree = { 81 | import nsc.util._ 82 | val un = new CompilationUnit(new ScriptSourceFile(unit.source.asInstanceOf[BatchSourceFile], code.toCharArray, pos.startOrPoint)) 83 | val scanner = new syntaxAnalyzer.UnitParser(un) 84 | scanner.expr() 85 | } 86 | def compileExpression(exp: AST.Exp): Tree = atPos(startOf(exp)) { 87 | exp match { 88 | case AST.ThisExp => atPos(positionOf(exp, 4))(Ident("it")) 89 | case AST.ParentExp(inner, parent) => compileParentExpressionInner(inner, atPos(positionOf(exp, parent.length))(Ident(parent))) 90 | case AST.ScalaExp(scalaExp) => fixPos(startOf(exp), parse(scalaExp, startOf(exp))) 91 | case AST.Ident(identifier) => atPos(positionOf(exp, identifier.length))(Ident(identifier)) 92 | } 93 | } 94 | def compileElement(el: AST.FormatElement): Tree = atPos(startOf(el)){ el match { 95 | case AST.Literal(str) => atPos(positionOf(el, str.length)) (Literal(Constant(str))) 96 | case AST.ToStringConversion(exp) => Select(compileExpression(exp), "toString") 97 | case AST.Expand(exp, sep, inner) => Apply( 98 | Select( 99 | Apply(Select(compileExpression(exp), "map") 100 | , List(Function(List(it), compile(inner)))), "mkString") 101 | , List(Literal(Constant(sep)))) 102 | case AST.Conditional(cond, thenEls, elseEls) => 103 | Match(Typed(compileExpression(cond), Ident("Any".toTypeName)), List( 104 | CaseDef(Apply(Ident("Some"), List(Bind("it", Ident("_")))), compile(thenEls)), 105 | CaseDef(Ident("None"), compile(elseEls)), 106 | CaseDef(Bind("it", Literal(Constant(true))), compile(thenEls)), 107 | CaseDef(Literal(Constant(false)), compile(elseEls)) 108 | )) 109 | }} 110 | 111 | els.elements.size match { 112 | case 0 => Literal(Constant("")) 113 | case 1 => compileElement(els.elements(0)) 114 | case _ => 115 | // the general case: 116 | // compile into new StringBuilder().append(a).append(b).[...].append(z).toString 117 | val createInstance: Tree = Apply(Select(New(Ident("StringBuilder".toTypeName)), nme.CONSTRUCTOR), Nil) 118 | def appendElement(a: Tree, b: Tree) = Apply(Select(a, "append"), List(b)) 119 | 120 | val appender = els.elements.map(compileElement _) 121 | .foldLeft(createInstance)(appendElement) 122 | 123 | Apply(Select(appender, "toString"), Nil) 124 | } 125 | } 126 | 127 | /** Strip off the delimiters of a string constant's position */ 128 | def fixPosition(pos: Position, len: Int): Position = pos match { 129 | case p: scala.tools.nsc.util.RangePosition => 130 | val start = p.start 131 | val end = p.end 132 | val lengthWithDelims = end - start 133 | val delims = (lengthWithDelims - len) / 2 - 1 134 | //println("Found delims of total length "+(lengthWithDelims - len)) 135 | new scala.tools.nsc.util.RangePosition(p.source, start+delims, start+delims, end-delims) 136 | case _ => pos 137 | } 138 | 139 | /** When using postTransform, each node is 140 | * visited after its children. 141 | */ 142 | def postTransform(tree: Tree): Tree = tree match { 143 | case Literal(Constant(str: String)) => 144 | try { 145 | //println(parser.get.Version) 146 | atPos(tree.pos.makeTransparent)(compiled(parser.get.parse(str), fixPosition(tree.pos, str.length))) 147 | } catch { 148 | case p: ParseException => p.printStackTrace; unit.error(tree.pos, p.getMessage); tree 149 | case e: TypeError => localTyper.reportTypeError(tree.pos, e); tree 150 | } 151 | case _ => tree 152 | } 153 | 154 | val ESType = "EnhanceStrings".toTypeName 155 | val SyntaxParam = "syntax".toTermName 156 | val VersionParam = "version".toTermName 157 | 158 | val annotationMatcher: PartialFunction[Tree, List[Tree]] = { case Apply(Select(New(Ident(ESType)), nme.CONSTRUCTOR), args) => args } 159 | 160 | def versionExtractor(cur: VersionInfo, arg: Tree): VersionInfo = arg match { 161 | case AssignOrNamedArg(Ident(SyntaxParam), Literal(Constant(flavor: String))) => cur.copy(flavor = flavor) 162 | case AssignOrNamedArg(Ident(SyntaxParam), c@Literal(Constant(flavor))) => error(c.pos, "The "+SyntaxParam+" attribute of "+ESType+" must be a String value"); cur 163 | 164 | case AssignOrNamedArg(Ident(VersionParam), Literal(Constant(version: Int))) => cur.copy(version = version) 165 | case AssignOrNamedArg(Ident(VersionParam), c@Literal(Constant(version))) => error(c.pos, "The "+VersionParam+" attribute of "+ESType+" must be an integer value"); cur 166 | 167 | case _ => error(arg.pos, "Unknown parameter of "+ESType+": "+arg); cur 168 | } 169 | 170 | def extractVersion(m: Modifiers): (Modifiers, Option[VersionInfo]) = m match { 171 | case Modifiers(a, b, anns, d) => 172 | val (args, rest) = anns.partition(annotationMatcher.isDefinedAt _) 173 | 174 | (Modifiers(a, b, rest, d), args.headOption.map(annotationMatcher).map(args => args.foldLeft(ParserFactory.defaultVersion)(versionExtractor))) 175 | case _ => (m, None) 176 | } 177 | 178 | var parser: Option[ESParser] = None 179 | 180 | def withMods(tree: Tree, newMods: Modifiers): Tree = tree match { 181 | case d: DefDef => d.copy(mods = newMods) 182 | case m: ModuleDef => m.copy(mods = newMods) 183 | case c: ClassDef => c.copy(mods = newMods) 184 | case _ => tree 185 | } 186 | override def transform(tree: Tree): Tree = tree match { 187 | case m: MemberDef => 188 | val (newMods, newVersion) = extractVersion(m.mods) 189 | 190 | newVersion match { 191 | case Some(v) => 192 | val oldParser = parser 193 | 194 | parser = ParserFactory.parser(v) 195 | if (!parser.isDefined) 196 | error(tree.pos, "EnhancedString syntax with version "+v+" not found.") 197 | 198 | //println("Version now " + v) 199 | val res = super.transform(tree) 200 | 201 | parser = oldParser 202 | atPos(tree.pos)(withMods(res, newMods)) 203 | case None => 204 | super.transform(tree) 205 | } 206 | //case Apply(Select(New(Ident(ESType)), nme.CONSTRUCTOR), List(x)) => println(x.getClass); super.transform(tree) 207 | case _ if parser.isDefined => postTransform(super.transform(tree)) 208 | case _ => super.transform(tree) 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/string/ObjectFormatter.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | object ObjectFormatter extends IObjectFormatterFactory{ 8 | val parser = EnhancedStringFormatParser 9 | 10 | def formatter[T<:AnyRef](clazz:Class[T],fm:String):IObjectFormatter[T] = new IObjectFormatter[T]{ 11 | val parsed = parser.parse(fm) 12 | def format(o:T) = parsed.format(o) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/string/TypeHelper.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | object TypeHelper { 8 | import java.lang.reflect._ 9 | 10 | def nullSafe[T](x:T):Option[T] = 11 | if (x != null) Some(x) else None 12 | 13 | def supertype(cl:Class[_]):Option[Type] = 14 | nullSafe(cl.getGenericSuperclass) 15 | 16 | /** 17 | * Search recursively through the type hierarchy of cl (superclasses and interfaces) 18 | * to find the Candidate class. If found return the first type parameter's type value. 19 | * 20 | * In other words: Given a method with a generic result type, 21 | * this function can be used to find out how a superclass or implemented interface is 22 | * parameterized. 23 | * 24 | * For example, a method getList() returns a List[String]. We are not interested 25 | * which type parameter List is applied to but which type parameter 26 | * Iterable, which is a supertype of List, is applied to. 27 | * 28 | * tp is the list of type values applied to type cl. While traversing the type 29 | * hierarchy, the type parameters have to be resolved in the context of the current 30 | * class. 31 | * 32 | * Example: Candidate = Iterable 33 | * cl tp 34 | * ArrayList[String] [] => getGenericInterface 35 | * ParameterizedType(List,Class String) [] => resolved:String 36 | * List [String]=> getGenericInterface 37 | * ParameterizedType(Collection,E) [String]=> resolved: TypeVariable(E,List) => String 38 | * Collection [String]=> getGenericInterface 39 | * ParameterizedType(Iterable,E) [String]=> resolved: TypeVariable(E,Collection) => String 40 | * Iterable [String]=> String 41 | */ 42 | def genericInstanceType(cl: Type, Candidate: Class[_], tp: IndexedSeq[Type]): Option[Type] = { 43 | def resolved(ts: IndexedSeq[Type]): IndexedSeq[Type] = 44 | ts.map { 45 | case cl:Class[_] => cl 46 | case v:TypeVariable[_] => tp(v.getGenericDeclaration.asInstanceOf[Class[_]].getTypeParameters.indexOf(v)) 47 | }.toArray[Type] 48 | 49 | cl match { 50 | case Candidate => Some(tp(0)) 51 | case p:ParameterizedType => 52 | genericInstanceType(p.getRawType,Candidate,resolved(p.getActualTypeArguments)) 53 | case cl:Class[_] => 54 | (supertype(cl).toList ++ cl.getGenericInterfaces) 55 | .flatMap(t => genericInstanceType(t,Candidate,tp).toList) 56 | .headOption 57 | } 58 | } 59 | def main(args:scala.Array[String]):Unit = { 60 | System.out.println(genericInstanceType(classOf[ProcessBuilder].getMethod("command").getGenericReturnType 61 | ,classOf[java.lang.Iterable[_]],scala.Array[Type]())) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/scala/net/virtualvoid/string/ESParserSpecs.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | import _root_.org.specs._ 8 | 9 | object ESParserSpecs extends Specification { 10 | import EnhancedStringFormatParser.parse 11 | import AST._ 12 | 13 | // for legacy reasons 14 | def Exp(str: String) = Ident(str) 15 | 16 | "The parser" should parseCorrectly { 17 | "'test'" in {"test" must beParsedAs(Literal("test"))} 18 | "'#prop'" in {"#prop" must beParsedAs(Exp("prop"))} 19 | "'#{prop}'" in {"#{prop}" must beParsedAs(Exp("prop"))} 20 | "'#{prop.member}'" in {"#{prop.member}" must beParsedAs(ParentExp(Exp("member"),"prop"))} 21 | "'#prop.member'" in {"#prop.member" must beParsedAs(ParentExp(Exp("member"),"prop"))} 22 | "'#listProp*'" in {"#listProp*" must beParsedAs(expand(Exp("listProp"),"",ToStringConversion(ThisExp)))} 23 | "'#listProp{,}*'" in {"#listProp{,}*" must beParsedAs(expand(Exp("listProp"),",",ToStringConversion(ThisExp)))} 24 | "'#{listProp}{,}*'" in {"#{listProp}{,}*" must beParsedAs(expand(Exp("listProp"),",",ToStringConversion(ThisExp)))} 25 | "'#listProp[test]{,}*'" in {"#listProp[test]{,}*" must beParsedAs(expand(Exp("listProp"),",",Literal("test")))} 26 | 27 | "#this" in {"#this" must beParsedAs(ThisExp)} 28 | "#{this}" in {"#{this}" must beParsedAs(ThisExp)} 29 | "#this[]*" in {"#this[]*" must beParsedAs(expand(ThisExp,""))} 30 | 31 | // literal Scala expressions 32 | "#{{test.it.is}}" in {"#{{test.it.is}}" must beParsedAs(ScalaExp("test.it.is"))} 33 | "#{{test.it.is}}*" in {"#{{test.it.is}} *" must beParsedAs(ToStringConversion(ScalaExp("test.it.is")), Literal(" *"))} 34 | "#{{List(5,3,2)}}*" in {"#{{List(5,3,2)}}*" must beParsedAs(expand(ScalaExp("List(5,3,2)"), "", ToStringConversion(ThisExp)))} 35 | 36 | //escaped square brackets 37 | "#[abc#]" in {"#[abc#]" must beParsedAs(Literal("[abc]"))} 38 | 39 | //escaped hash 40 | "##abc" in {"##abc" must beParsedAs(Literal("#abc"))} 41 | 42 | // more complex escape situations 43 | "###] ####blub ###[" in {"###] ####blub ###[" must beParsedAs(Literal("#] ##blub #["))} 44 | 45 | // test weird control combinations 46 | "Dots in normal literals 'This is a sentence.'" in {"This is a sentence." must beParsedAs(Literal("This is a sentence."))} 47 | "Dots after curly braced expressions 'This is a #{exp}.'" in {"This is a #{exp}." must beParsedAs(Literal("This is a "),ToStringConversion(Exp("exp")),Literal("."))} 48 | 49 | "Curly Braces somewhere in between 'This is {braced}'" in {"This is {braced}" must beParsedAs(Literal("This is {braced}"))} 50 | "question mark after expression" in {"Who lives at #address?" must beParsedAs(Literal("Who lives at "),ToStringConversion(Exp("address")),Literal("?"))} 51 | 52 | // conversions 53 | "date conversion" in {"#this->date[dd.MM.yyyy]" must beParsedAs(DateConversion(ThisExp,"dd.MM.yyyy"))} 54 | 55 | // conditionals 56 | "conditionals" in {"#this?[#this|Nope]" must beParsedAs(Conditional(ThisExp,toks(ToStringConversion(ThisExp)),toks(Literal("Nope"))))} 57 | "conditional with empty else clause" in { 58 | "#this?[#this]" must beParsedAs( 59 | Conditional( 60 | ThisExp, 61 | toks(ToStringConversion(ThisExp)), 62 | toks(Literal("")))) 63 | } 64 | 65 | "complex conditionals" in {"#{x.getClass.getMethods}?[#it|None]" must beParsedAs(Conditional( 66 | ParentExp(ParentExp(Exp("getMethods"),"getClass"),"x") 67 | ,toks(ToStringConversion(Exp("it"))),toks(Literal("None"))))} 68 | "conditional and expansion" in {"#x[#it?[#it|Leer]]{,}*" must beParsedAs(Expand(Exp("x"),",",toks(Conditional(Exp("it"),toks(ToStringConversion(Exp("it"))),toks(Literal("Leer"))))))} 69 | } 70 | 71 | // helper methods 72 | 73 | import org.specs.matcher.Matcher 74 | def beParsedAs(ts:FormatElement*) = new Matcher[String]{ 75 | val tokens = toks(ts:_*) 76 | def apply(str: => String) = { 77 | val l = parse(str) 78 | (l.toString == tokens.toString,"equal",l.toString + " is not equal to the expected " + tokens.toString) 79 | } 80 | } 81 | def beParsedAs(ts:Exp):Matcher[String] = beParsedAs(ToStringConversion(ts)) 82 | 83 | def expand(e:Exp,sep:String,inner:FormatElement*) = Expand(e,sep,toks(inner:_*)) 84 | def toks(inner:FormatElement*) = FormatElementList(List(inner:_*)) 85 | 86 | import org.specs.specification.Example 87 | def parseCorrectly = addToSusVerb(" parse correctly") 88 | } 89 | 90 | -------------------------------------------------------------------------------- /src/test/scala/net/virtualvoid/string/EvaluateSpecs.scala: -------------------------------------------------------------------------------- 1 | /* SeS - Scala Enhanced Strings 2 | * Copyright 2008 - 2010 Johannes Rudolph 3 | */ 4 | 5 | package net.virtualvoid.string 6 | 7 | import _root_.org.specs._ 8 | 9 | object EvaluateSpecs extends Specification{ 10 | import java.lang.{String=>jString} 11 | case class Bank(n:String){ 12 | def name():jString = n 13 | } 14 | import java.util.GregorianCalendar 15 | import java.util.Calendar._ 16 | case class Transaction(date:GregorianCalendar,centAmount:Int){ 17 | def isWithdrawal() = centAmount < 0 18 | def amount():java.lang.Integer = math.abs(centAmount) // no primitives for now 19 | } 20 | case class Account(n:String,b:Bank) { 21 | def number():jString = n 22 | def bank() = b 23 | def transactions():Array[Transaction] = Array(Transaction(new GregorianCalendar(2008,OCTOBER,1),5),Transaction(new GregorianCalendar(2008,OCTOBER,3),-4)) 24 | } 25 | class Person{ 26 | def name():java.lang.String = "Joe" 27 | def accountNames():java.util.List[java.lang.String] = java.util.Arrays.asList("a","b") 28 | val sparkasse = Bank("Sparkasse") 29 | def accounts():java.util.List[Account] = java.util.Arrays.asList(Account("78910",sparkasse),Account("12345",Bank("Volksbank"))) 30 | def accs():Array[Account] = accounts().toArray(new Array[Account](0)) 31 | def noAddress:Option[String] = None 32 | def address:Option[String] = Some("Some Street 5") 33 | } 34 | val thePerson = new Person 35 | 36 | def evaluate(factory:IObjectFormatterFactory){ 37 | import matcher.Matcher 38 | def evaluateObjectAs(obj:AnyRef,str:String) = new Matcher[String]{ 39 | def apply(format: =>String) = { 40 | val res = factory.format(format,obj) 41 | (res == str,"evaluates as "+str,"does not evaluate as "+str+" but as "+ res) 42 | } 43 | } 44 | def evaluateAs(str:String) = evaluateObjectAs(thePerson,str) 45 | 46 | "literal" in {"literal" must evaluateAs("literal")} 47 | "property access" in {"#name" must evaluateAs(thePerson.name)} 48 | "string array access" in {"#accountNames*" must evaluateAs("ab")} 49 | "string array access with separator" in {"#accountNames{,}*" must evaluateAs("a,b")} 50 | "object iterable access with inner expression" in {"#accounts[#number]{,}*" must evaluateAs("78910,12345")} 51 | "object array access with inner expression" in {"#accs[#number]{,}*" must evaluateAs("78910,12345")} 52 | "deep property access" in {"#accs[#bank.name]{,}*" must evaluateAs("Sparkasse,Volksbank")} 53 | "format dates properly" in {"#this->date[dd.MM.yyyy]" must evaluateObjectAs(new GregorianCalendar(2008,OCTOBER,1),"01.10.2008")} 54 | "evaluate conditionals true" in {"#this?[yes|no]" must evaluateObjectAs(java.lang.Boolean.valueOf(true),"yes")} 55 | "evaluate conditionals false" in {"#this?[yes|no]" must evaluateObjectAs(java.lang.Boolean.valueOf(false),"no")} 56 | "evaluate conditionals with Some" in {"#address?[Found: #this|no address given]" must evaluateAs("Found: Some Street 5")} 57 | "evaluate conditionals with None" in {"#noAddress?[Found: #this|no address given]" must evaluateAs("no address given")} 58 | } 59 | 60 | "The format interpreter" should { 61 | evaluate(ObjectFormatter) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /usage/.gitignore: -------------------------------------------------------------------------------- 1 | project/boot/ 2 | -------------------------------------------------------------------------------- /usage/project/build.properties: -------------------------------------------------------------------------------- 1 | #Project properties 2 | #Mon Jun 07 10:41:12 CEST 2010 3 | project.organization=net.virtualvoid 4 | project.name=es-usage-example 5 | sbt.version=0.7.7 6 | project.version=1.0 7 | def.scala.version=2.7.7 8 | build.scala.versions=2.9.1 2.9.0-1 9 | project.initialize=false 10 | -------------------------------------------------------------------------------- /usage/project/build/Project.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | class ESUsageExample(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins { 4 | val virtualVoid = "Virtual-Void repository" at "http://mvn.virtual-void.net" 5 | 6 | val es = compilerPlugin("net.virtualvoid" %% "scala-enhanced-strings" % "0.5.2") 7 | } 8 | -------------------------------------------------------------------------------- /usage/src/main/scala/Test.scala: -------------------------------------------------------------------------------- 1 | @EnhanceStrings(syntax="poros") 2 | object Test { 3 | def main(args: Array[String]) { 4 | println("These are the arguments: #args{, }* {{ args(5) }}") 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /with-plugin.sh: -------------------------------------------------------------------------------- 1 | ~/downloads/sources/scala-2.8.0.RC3/bin/scala -Xplugin:target/scala_2.8.0.RC3/scala-enhanced-strings_2.8.0.RC3-1.0.jar 2 | --------------------------------------------------------------------------------