diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 000000000..0c70156df
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,37 @@
+version: 2
+defaults: &defaults
+ working_directory: ~/repo
+ docker:
+ - image: circleci/openjdk:8-jdk
+ environment:
+ JVM_OPTS: -Xmx3200m
+ TERM: dumb
+jobs:
+ build:
+ <<: *defaults
+ steps:
+ - checkout
+ - restore_cache:
+ keys:
+ - v1-dependencies-{{ checksum "build.sbt" }}
+ # fallback to using the latest cache if no exact match is found
+ - v1-dependencies-
+ - run: cat /dev/null | sbt compilerJVM/universal:packageBin
+ - store_artifacts:
+ path: jvm/target/universal/kaitai-struct-compiler-*.zip
+ - save_cache:
+ paths:
+ - ~/.m2
+ key: v1-dependencies--{{ checksum "build.sbt" }}
+ test:
+ <<: *defaults
+ steps:
+ - run: cat /dev/null | sbt compilerJVM/test
+workflows:
+ version: 2
+ build_test_deploy:
+ jobs:
+ - build
+ - test:
+ requires:
+ - build
diff --git a/.gitignore b/.gitignore
index 9f85e31c2..e41b1404d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,4 @@ project/boot/
# Unpacking complete binaries for testing purposes
/runnable
+js/npm
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index 96cc43efa..000000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/hydra.xml b/.idea/hydra.xml
new file mode 100644
index 000000000..66eeb9a09
--- /dev/null
+++ b/.idea/hydra.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 3d7f6d271..d5d79e0ca 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,37 +1,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 0774832f9..61851a77b 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -7,8 +7,6 @@
-
-
diff --git a/.idea/modules/compiler-sources.iml b/.idea/modules/compiler-sources.iml
index af9c5e700..a5bd03e61 100644
--- a/.idea/modules/compiler-sources.iml
+++ b/.idea/modules/compiler-sources.iml
@@ -4,24 +4,28 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/compilerJS-build.iml b/.idea/modules/compilerJS-build.iml
index 95ce319e3..c1b1cf752 100644
--- a/.idea/modules/compilerJS-build.iml
+++ b/.idea/modules/compilerJS-build.iml
@@ -1,6 +1,6 @@
-
-
+
+
@@ -11,157 +11,193 @@
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/.idea/modules/compilerJS.iml b/.idea/modules/compilerJS.iml
index ba43a620c..07c1da5ef 100644
--- a/.idea/modules/compilerJS.iml
+++ b/.idea/modules/compilerJS.iml
@@ -1,34 +1,37 @@
-
-
-
+
+
+
-
-
+
-
-
+
+
+
-
+
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/compilerJVM-build.iml b/.idea/modules/compilerJVM-build.iml
index 169767c13..e8d9ec882 100644
--- a/.idea/modules/compilerJVM-build.iml
+++ b/.idea/modules/compilerJVM-build.iml
@@ -1,6 +1,6 @@
-
-
+
+
@@ -11,157 +11,193 @@
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/.idea/modules/compilerJVM.iml b/.idea/modules/compilerJVM.iml
index fc02511df..356ba34ac 100644
--- a/.idea/modules/compilerJVM.iml
+++ b/.idea/modules/compilerJVM.iml
@@ -1,26 +1,26 @@
-
-
-
+
+
+
-
-
+
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
@@ -28,14 +28,16 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/root-build.iml b/.idea/modules/root-build.iml
index ad68dd6c8..aba4b3e2a 100644
--- a/.idea/modules/root-build.iml
+++ b/.idea/modules/root-build.iml
@@ -1,6 +1,6 @@
-
-
+
+
@@ -12,156 +12,191 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/.idea/modules/root.iml b/.idea/modules/root.iml
index f8234bc36..abf4d75ce 100644
--- a/.idea/modules/root.iml
+++ b/.idea/modules/root.iml
@@ -1,33 +1,26 @@
-
-
-
+
+
+
-
-
+
-
-
+
+
+
-
+
-
+
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/.idea/sbt.xml b/.idea/sbt.xml
index e6a1c1e56..68783b141 100644
--- a/.idea/sbt.xml
+++ b/.idea/sbt.xml
@@ -3,7 +3,6 @@
diff --git a/.idea/scala_compiler.xml b/.idea/scala_compiler.xml
index f6bb54cd5..d0f8d1eea 100644
--- a/.idea/scala_compiler.xml
+++ b/.idea/scala_compiler.xml
@@ -2,10 +2,10 @@
-
-
+
+
-
+
diff --git a/README.md b/README.md
index a59dad9b4..188a05fa6 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,21 @@
+### Submitting bugs / issues / ideas
+
+> We're phasing out "Issues" tab in this repository. Please submit all issues to the [main project's tracker](https://github.com/kaitai-io/kaitai_struct/issues).
+
+---
+
# Kaitai Struct: compiler
This project is an official reference compiler for [Kaitai Struct](https://github.com/kaitai-io/kaitai_struct) project.
-Kaitai Struct is a declarative language used for describe various
+Kaitai Struct is a declarative language used to describe various
binary data structures, laid out in files or in memory: i.e. binary
file formats, network stream packet formats, etc.
The main idea is that a particular format is described in Kaitai
Struct language (`.ksy` files) only once and then can be compiled with
this compiler into source files in one of the supported programming
-languages. These modules will include a generated code for a parser
+languages. These modules will include the generated code for a parser
that can read described data structure from a file / stream and give
access to it in a nice, easy-to-comprehend API.
@@ -65,8 +71,8 @@ source code in repository:
git clone https://github.com/kaitai-io/kaitai_struct_compiler
-See [DEVELOPERS.md](DEVELOPERS.md) for general pointers on how to proceed
-with the source code then.
+See the [developer documentation](http://doc.kaitai.io/developers.html) for
+general pointers on how to proceed with the source code then.
## Usage
@@ -78,8 +84,8 @@ just as full name.
Common options:
* `...` — source files (.ksy)
-* `-t | --target ` — target languages (`cpp_stl`,
- `csharp`, `java`, `javascript`, `perl`, `php`, `python`, `ruby`, `all`)
+* `-t | --target ` — target languages (`graphviz`, `csharp`,
+ `all`, `perl`, `java`, `go`, `cpp_stl`, `php`, `lua`, `python`, `ruby`, `javascript`
* `all` is a special case: it compiles all possible target
languages, creating language-specific directories (as per language
identifiers) inside output directory, and then creating output
@@ -119,7 +125,9 @@ and describes format with ID `foo`:
## Licensing
-Kaitai Struct compiler itself is copyright (C) 2015-2017 Kaitai
+### Main code
+
+Kaitai Struct compiler itself is copyright (C) 2015-2018 Kaitai
Project.
This program is free software: you can redistribute it and/or modify
@@ -135,7 +143,76 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-Note that it applies only to compiler itself, not `.ksy` input files
-that one supplies in normal process of compilation, nor to compiler's
-output files — that consitutes normal usage process and you obviously
-keep copyright to both.
+### FastParse
+
+Portions of Kaitai Struct compiler are loosely based on
+[pythonparse](https://github.com/lihaoyi/fastparse/tree/master/pythonparse/shared/src/main/scala/pythonparse)
+from [FastParse](http://www.lihaoyi.com/fastparse/) and are copyright
+(c) 2014 Li Haoyi (haoyi.sg@gmail.com).
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+### XMLUtils code
+
+Portions of Kaitai Struct compiler are based on `scala/xml/Utility.scala` from [Scala XML](https://github.com/scala/scala-xml).
+
+Copyright (c) 2002-2017 EPFL
+Copyright (c) 2011-2017 Lightbend, Inc.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+* Neither the name of the EPFL nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
+
+### Libraries used
+
+Kaitai Struct compiler depends on the following libraries:
+
+* [scopt](https://github.com/scopt/scopt) — MIT license
+* [fastparse](http://www.lihaoyi.com/fastparse/) — MIT license
+* [snakeyaml](https://bitbucket.org/asomov/snakeyaml) — Apache 2.0 license
+
+---
+
+Note that these clauses only apply only to compiler itself, not `.ksy`
+input files that one supplies in normal process of compilation, nor to
+compiler's output files — that consitutes normal usage process and you
+obviously keep copyright to both.
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index b96e26291..3943451bb 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,3 +1,83 @@
+# 0.8 (TBD)
+
+* New target languages:
+ * Lua (96% tests pass score)
+ * initial support for Go (15% tests pass score)
+* New ksy features:
+ * Switchable default endianness: `meta/endian` can now contain a
+ switch-like structure (with `switch-on` and `cases`), akin to
+ switchable types
+ ([docs](http://doc.kaitai.io/user_guide.html#calc-endian)).
+ * Parametric user-defined types: one can use `type: my_type(arg1,
+ arg2, arg3)` to pass arguments into user type
+ ([docs](http://doc.kaitai.io/user_guide.html#param-types)).
+ * Custom processing types: one can use `process:
+ my_process_name(arg1, arg2, arg3)` to invoke custom processing
+ routine, implemented in imperative language
+ ([docs](http://doc.kaitai.io/user_guide.html#custom-process)).
+ * In repetitions, index of current repetition can be accessed using
+ `_index` in expressions
+ ([docs](http://doc.kaitai.io/user_guide.html#repeat-index)).
+ * Verbose enums: now one can specify documentation and other useful
+ information relevant to enums using verbose enum declaration
+ format
+ ([docs](http://doc.kaitai.io/user_guide.html#verbose-enums)).
+ * `meta/xref` key can be used for adding cross-references of a
+ format specifications (like relevant RFC entries, Wikidata
+ entries, ISO / IEEE / JIS / DIN / GOST standard numbers, PRONOM
+ identifiers, etc).
+* General compilation improvements:
+ * Imports/includes for all languages are now managed properly, no
+ duplicate / unnecessary imports should be added
+ * Python: basic docstring support
+ * More strict ksy precompile checks (less likely to accept ksy that
+ will result in non-compilable code), better error messages
+* CLI options:
+ * Python target now allows to specify package with `--python-package`
+ * Java target now allows custom KaitaiStream implementations and
+ thus allows to specify default implementation for `fromFile(...)`
+ using `--java-from-file-class`.
+* Expression language:
+ * New methods:
+ * floats: `to_i`
+ * arrays: `min`, `max`
+ * Added byte array comparison
+* Packaging / infrastructure improvements:
+ * ksc is now available as
+ [npm package](https://www.npmjs.com/package/kaitai-struct-compiler/),
+ which now a build dependency of a
+ [web IDE](https://ide.kaitai.io/)
+* Runtime API changes:
+ * C++: now requires `KS_STR_ENCODING_ICONV` or
+ `KS_STR_ENCODING_NONE` to be defined to how to handle string
+ encodings
+ * Java: `KaitaiStream` is now an interface, and there are two
+ distinct classes which implement it:
+ * `ByteBufferKaitaiStream` provides KaitaiStream backed
+ `ByteBuffer` (and thus using memory-mapped files)
+ * `RandomAccessFileKaitaiStream` provides KaitaiStream backed by
+ `RandomAccessFile` (and thus uses normal OS read calls, as it
+ was done in older KaitaiStruct circa v0.5)
+ * JavaScript: Error classes are now subclasses of `KaitaiStream` and
+ were renamed in the following way: `KaitaiUnexpectedDataError` ->
+ `KaitaiStream`.`UnexpectedDataError`
+* Major bugfixes:
+ * C++: adjusted to made compatible with OS X and Windows MSVC builds
+ * Fixed broken generation of byte array literals with high 8-bit set
+ in some targets
+ * Fixed float literals parsing, fixed larger integer keys YAML parsing
+ * Fixed inconsistency of debug mode vs non-debug mode behavior for
+ `repeat-*`
+ * Fixed chain of relative imports bug: now all relative imports work
+ always relative to the file being processed, not to current
+ compiler's dir
+ * Many problems with switching: invalid common type inferring,
+ invalid code being generated, added failsafe `if`-based
+ implementations for languages which do not support switching over
+ all possible types.
+ * Fixed most memory leaks in C++ (only exception-related leaks are
+ left now)
+
# 0.7 (2017-03-22)
* New ksy features:
diff --git a/build.sbt b/build.sbt
index 67f0708aa..bf0d21f88 100644
--- a/build.sbt
+++ b/build.sbt
@@ -5,8 +5,8 @@ import sbt.Keys._
resolvers += Resolver.sonatypeRepo("public")
-val VERSION = "0.7"
-val TARGET_LANGS = "C++/STL, C#, Java, JavaScript, Perl, PHP, Python, Ruby"
+val VERSION = "0.8"
+val TARGET_LANGS = "C++/STL, C#, Java, JavaScript, Lua, Perl, PHP, Python, Ruby"
lazy val root = project.in(file(".")).
aggregate(compilerJS, compilerJVM).
@@ -21,21 +21,21 @@ lazy val compiler = crossProject.in(file(".")).
settings(
organization := "io.kaitai",
name := "kaitai-struct-compiler",
- version := VERSION,
+ version := sys.env.getOrElse("KAITAI_STRUCT_VERSION", VERSION),
licenses := Seq(("GPL-3.0", url("https://opensource.org/licenses/GPL-3.0"))),
- scalaVersion := "2.11.7",
+ scalaVersion := "2.12.4",
buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion),
buildInfoPackage := "io.kaitai.struct",
buildInfoOptions += BuildInfoOption.BuildTime,
// Repo publish options
- publishTo <<= version { (v: String) =>
+ publishTo := version { (v: String) =>
val nexus = "https://oss.sonatype.org/"
if (v.trim.endsWith("SNAPSHOT"))
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
- },
+ }.value,
pomExtra :=
http://kaitai.io
@@ -54,15 +54,15 @@ lazy val compiler = crossProject.in(file(".")).
,
libraryDependencies ++= Seq(
- "com.lihaoyi" %%% "fastparse" % "0.4.1",
+ "com.github.scopt" %%% "scopt" % "3.6.0",
+ "com.lihaoyi" %%% "fastparse" % "1.0.0",
"org.yaml" % "snakeyaml" % "1.16"
)
).
jvmSettings(
mainClass in Compile := Some("io.kaitai.struct.JavaMain"),
libraryDependencies ++= Seq(
- "org.scalatest" %% "scalatest" % "2.2.6" % "test",
- "com.github.scopt" %% "scopt" % "3.4.0"
+ "org.scalatest" %% "scalatest" % "3.0.1" % "test"
),
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-u", "target/test_out"),
@@ -111,18 +111,23 @@ lazy val compiler = crossProject.in(file(".")).
}
},
- // Hack: we need /usr/share/kaitai-struct (the format directory) to be
- // created as empty dir and packaged in compiler package, to be filled in
- // with actual repository contents by "kaitai-struct-formats" package.
- // "jvm/src/main/resources" is guaranteed to be an empty directory.
- linuxPackageMappings += LinuxPackageMapping(Map(
- new File("jvm/src/main/resources") -> "/usr/share/kaitai-struct"
- )),
+ // We need /usr/share/kaitai-struct (the format directory) to be created as
+ // empty dir and packaged in compiler package, to be filled in with actual
+ // repository contents by "kaitai-struct-formats" package.
+ linuxPackageMappings += packageTemplateMapping("/usr/share/kaitai-struct")(),
// Remove all "maintainer scripts", such as prerm/postrm/preinst/postinst: default
// implementations create per-package virtual user that we won't use anyway
maintainerScripts in Debian := Map(),
+ // Work around new Debian defaults and sbt-native-packager defaults, which
+ // build .deb packages that appear to be incompatible with older Debian/Ubuntu's
+ // dpkg and are not accepted by BinTray.
+ //
+ // For more information, see
+ // https://github.com/sbt/sbt-native-packager/issues/1067
+ debianNativeBuildOptions in Debian := Seq("-Zgzip", "-z3"),
+
packageSummary in Linux := s"compiler to generate binary data parsers in $TARGET_LANGS",
packageSummary in Windows := "Kaitai Struct compiler",
packageDescription in Linux :=
diff --git a/js/README.md b/js/README.md
new file mode 100644
index 000000000..5ad3c52ad
--- /dev/null
+++ b/js/README.md
@@ -0,0 +1,196 @@
+## Kaitai Struct compiler in JavaScript
+
+This project is a official reference Kaitai Struct compiler, compiled
+for JavaScript environments.
+
+Kaitai Struct is a declarative language used for describe various
+binary data structures, laid out in files or in memory: i.e. binary
+file formats, network stream packet formats, etc.
+
+The main idea is that a particular format is described in Kaitai
+Struct language only once and then can be compiled with into
+source files in one of the supported programming languages. These
+modules will include a generated code for a parser that can read
+described data structure from a file / stream and give access to it in
+a nice, easy-to-comprehend API.
+
+For more info on Kaitai Struct, please refer to http://kaitai.io/
+
+Note that reference Kaitai Struct compiler is written Scala, and thus
+can be compiled for a variety of platforms, such as JVM, JavaScript
+and native binaries. This package is compiled to be run JavaScript
+environments.
+
+Currently, this JavaScript build offers only programmatic API, so it
+is generally suited for developers of tools that use Kaitai Struct
+(i.e. interactive compilers, visualizers, loaders, IDEs that integrate
+the compiler). If you:
+
+* just look for a way to try out Kaitai Struct => try
+ [Kaitai Struct Web IDE](https://ide.kaitai.io/), which uses this
+ compiler internally
+* want to load .ksy file transparently in your JavaScript code
+ (compiling it on the fly) => use
+ [Kaitai Struct loader for JavaScript](https://github.com/kaitai-io/kaitai-struct-loader).
+* want to integrate compiler into your build flow => use a JVM-based
+ desktop compiler, which can be called as a command-line utility
+
+## Installation
+
+We publish two versions of the compiler to npm:
+ - A stable one, this includes the latest stable, released compiler. This is the default ("latest") version.
+ ```
+ npm install kaitai-struct-compiler
+ ```
+ - The other is the latest snapshot version which follows our master branch. This version is tagged as `@next`.
+ ```
+ npm install kaitai-struct-compiler@next
+ ```
+
+### Example project
+
+Our [examples repository](https://github.com/kaitai-io/kaitai_struct_examples) contains a few examples how to use the compiler.
+
+## Plugging in
+
+We publish the compiler as an [UMD module](https://github.com/umdjs/umd), so it works from various environments, including server-side (eg. node) and client-side (eg. web browser) ones.
+
+Note: currently we don't publish the compiler as standard ES module. This will probably change in the future. If you need ES module please comment on [this issue](https://github.com/kaitai-io/kaitai_struct/issues/180).
+
+### node
+
+```javascript
+var KaitaiStructCompiler = require("kaitai-struct-compiler");
+var compiler = new KaitaiStructCompiler();
+```
+
+### browser using script tags
+
+```html
+
+
+```
+
+### browser using AMD loader (eg. require.js)
+
+```html
+
+
+```
+
+## Usage
+
+### Basic usage of compile method
+
+```javascript
+var ksyYaml = fs.readFileSync("zip.ksy"); /* string */
+var ksy = YAML.parse(ksyYaml); /* JS object */
+compiler.compile("javascript", ksy, null, false /* debugMode */).then(function(files) {
+ console.log("Compiled filenames: " + Object.keys(files).join(", "));
+ console.log("Content of Zip.js file: " + files["Zip.js"]);
+});
+```
+
+### Getting compiler information
+
+```javascript
+console.log("Version: " + compiler.version);
+console.log("Build date: " + compiler.buildDate);
+console.log("Supported languages: " + compiler.languages.join(", "));
+```
+
+### Handle imports
+
+```javascript
+var yamlImporter = {
+ importYaml: function(name, mode) {
+ console.log(" -> Import yaml called with name '" + name + "' and mode '" + mode + "'.");
+ var importKsyYaml = fs.readFileSync(name + ".ksy"); /* string */
+ var importKsy = YAML.parse(importKsyYaml); /* JS object */
+ return Promise.resolve(importKsy);
+ }
+};
+
+yamlImporter.importYaml("import_outer.ksy").then(function(ksy) {
+ compiler.compile("javascript", ksy, yamlImporter, false /* debugMode */).then(function(files) {
+ console.log("Compiled filenames: " + Object.keys(files).join(", "));
+ });
+});
+```
+
+### Debug mode
+
+You can compile in debug mode which adds `_debug` property to every object and this `_debug` object contains the start and end offsets of the parsed fields so you can find bytes of the field in the original binary.
+
+## Copyrights and licensing
+
+### Main code
+
+Kaitai Struct compiler itself is copyright (C) 2015-2017 Kaitai
+Project.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+### FastParse
+
+Portions of Kaitai Struct compiler are loosely based on
+[pythonparse](https://github.com/lihaoyi/fastparse/tree/master/pythonparse/shared/src/main/scala/pythonparse)
+from [FastParse](http://www.lihaoyi.com/fastparse/) and are copyright
+(c) 2014 Li Haoyi (haoyi.sg@gmail.com).
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+### Libraries used
+
+Kaitai Struct compiler depends on the following libraries:
+
+* [scopt](https://github.com/scopt/scopt) — MIT license
+* [fastparse](http://www.lihaoyi.com/fastparse/) — MIT license
+* [snakeyaml](https://bitbucket.org/asomov/snakeyaml) — Apache 2.0 license
+
+---
+
+Note that these clauses only apply only to compiler itself, not `.ksy`
+input files that one supplies in normal process of compilation, nor to
+compiler's output files — that consitutes normal usage process and you
+obviously keep copyright to both.
diff --git a/js/package.json b/js/package.json
new file mode 100644
index 000000000..c2c2ae68d
--- /dev/null
+++ b/js/package.json
@@ -0,0 +1,39 @@
+{
+ "author": {
+ "name": "Kaitai team",
+ "url": "https://github.com/orgs/kaitai-io/people"
+ },
+ "bugs": {
+ "url": "https://github.com/kaitai-io/kaitai_struct/issues"
+ },
+ "bundleDependencies": false,
+ "deprecated": false,
+ "description": "Kaitai Struct Compiler",
+ "homepage": "https://github.com/kaitai-io/kaitai_struct_compiler#readme",
+ "keywords": [
+ "kaitai",
+ "struct",
+ "compiler",
+ "binary",
+ "parsing",
+ "stream",
+ "runtime",
+ "file",
+ "format",
+ "structure",
+ "forenics",
+ "reversing",
+ "reverse-engineering"
+ ],
+ "license": "GPL-3.0",
+ "main": "kaitai-struct-compiler.js",
+ "name": "kaitai-struct-compiler",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/kaitai-io/kaitai_struct_compiler.git"
+ },
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "version": "0.8.0-SNAPSHOT.3"
+}
diff --git a/js/src/main/scala/io/kaitai/struct/MainJs.scala b/js/src/main/scala/io/kaitai/struct/MainJs.scala
index 3dffe7d04..2f16766e3 100644
--- a/js/src/main/scala/io/kaitai/struct/MainJs.scala
+++ b/js/src/main/scala/io/kaitai/struct/MainJs.scala
@@ -24,7 +24,7 @@ object MainJs {
val specs = new JavaScriptClassSpecs(importer, firstSpec)
Main.importAndPrecompile(specs, config).map { (_) =>
specs.flatMap({ case (_, spec) =>
- val files = Main.compile(spec, lang, config).files
+ val files = Main.compile(specs, spec, lang, config).files
files.map((x) => x.fileName -> x.contents).toMap
}).toJSDictionary
}.toJSPromise
diff --git a/js/update_npm_package.py b/js/update_npm_package.py
new file mode 100755
index 000000000..2fae1dd65
--- /dev/null
+++ b/js/update_npm_package.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python2
+
+import os
+import errno
+import shutil
+import re
+import subprocess
+import datetime
+
+
+def mkdir_p(path):
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST and os.path.isdir(path):
+ pass
+ else:
+ raise
+
+moduleTemplate = '''
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([], factory);
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = factory();
+ } else {
+ root.KaitaiStructCompiler = factory();
+ }
+}(this, function () {
+
+var exports = {};
+var __ScalaJSEnv = { exportsNamespace: exports };
+
+{{compilerCode}}
+
+return exports.io.kaitai.struct.MainJs;
+
+}));
+'''.lstrip()
+
+with open('target/scala-2.12/kaitai-struct-compiler-fastopt.js','rt') as f: compilerCode = f.read()
+
+moduleCode = moduleTemplate.replace('{{compilerCode}}', compilerCode)
+
+mkdir_p('npm')
+
+with open('npm/kaitai-struct-compiler.js','wt') as f: f.write(moduleCode)
+for fn in ['../LICENSE', 'README.md']:
+ shutil.copy(fn, 'npm/')
+
+gitInfo = subprocess.check_output(['git log -1 --format=%H,%ct'], shell=True).strip().split(',')
+commitId = gitInfo[0]
+commitTs = int(gitInfo[1])
+commitDate = datetime.datetime.fromtimestamp(commitTs).strftime('%Y%m%d.%H%M%S')
+
+with open('package.json','rb') as f: packageJson = f.read()
+packageJson = re.sub(r'("version": "\d+\.\d+\.\d+-SNAPSHOT.)[^"]*"', r'\g<1>%s"' % commitDate, packageJson)
+with open('npm/package.json','wb') as f: f.write(packageJson)
diff --git a/jvm/src/main/scala/io/kaitai/struct/JavaMain.scala b/jvm/src/main/scala/io/kaitai/struct/JavaMain.scala
index e66307dae..712b3a549 100644
--- a/jvm/src/main/scala/io/kaitai/struct/JavaMain.scala
+++ b/jvm/src/main/scala/io/kaitai/struct/JavaMain.scala
@@ -8,6 +8,7 @@ import io.kaitai.struct.JavaMain.CLIConfig
import io.kaitai.struct.format.{ClassSpec, ClassSpecs, KSVersion, YAMLParseException}
import io.kaitai.struct.formats.JavaKSYParser
import io.kaitai.struct.languages.components.LanguageCompilerStatic
+import io.kaitai.struct.precompile.ErrorInInput
object JavaMain {
KSVersion.current = BuildInfo.version
@@ -50,7 +51,7 @@ object JavaMain {
if (VALID_LANGS.contains(x)) {
success
} else {
- failure(s"'${x}' is not a valid target language; valid ones are: ${VALID_LANGS.mkString(", ")}")
+ failure(s"'$x' is not a valid target language; valid ones are: ${VALID_LANGS.mkString(", ")}")
}
}
@@ -63,10 +64,18 @@ object JavaMain {
c.copy(importPaths = c.importPaths ++ x.split(File.pathSeparatorChar))
} text(".ksy library search path(s) for imports (see also KSPATH env variable)")
+ opt[String]("go-package") valueName("") action { (x, c) =>
+ c.copy(runtime = c.runtime.copy(goPackage = x))
+ } text("Go package (Go only, default: none)")
+
opt[String]("java-package") valueName("") action { (x, c) =>
c.copy(runtime = c.runtime.copy(javaPackage = x))
} text("Java package (Java only, default: root package)")
+ opt[String]("java-from-file-class") valueName("") action { (x, c) =>
+ c.copy(runtime = c.runtime.copy(javaFromFileClass = x))
+ } text(s"Java class to be invoked in fromFile() helper (default: ${RuntimeConfig().javaFromFileClass})")
+
opt[String]("dotnet-namespace") valueName("") action { (x, c) =>
c.copy(runtime = c.runtime.copy(dotNetNamespace = x))
} text(".NET Namespace (.NET only, default: Kaitai)")
@@ -75,6 +84,10 @@ object JavaMain {
c.copy(runtime = c.runtime.copy(phpNamespace = x))
} text("PHP Namespace (PHP only, default: root package)")
+ opt[String]("python-package") valueName("") action { (x, c) =>
+ c.copy(runtime = c.runtime.copy(pythonPackage = x))
+ } text("Python package (Python only, default: root package)")
+
opt[Boolean]("opaque-types") action { (x, c) =>
c.copy(runtime = c.runtime.copy(opaqueTypes = x))
} text("opaque types allowed, default: false")
@@ -207,10 +220,18 @@ class JavaMain(config: CLIConfig) {
}
srcFile.toString -> log
}.toMap
- if (config.jsonOutput)
+
+ if (config.jsonOutput) {
Console.println(JSON.mapToJson(logs))
+ } else {
+ if (logsHaveErrors(logs))
+ System.exit(2)
+ }
}
+ private def logsHaveErrors(logs: Map[String, InputEntry]): Boolean =
+ logs.values.map(_.hasErrors).max
+
private def compileOneInput(srcFile: String) = {
Log.fileOps.info(() => s"parsing $srcFile...")
val specs = JavaKSYParser.localFileToSpecs(srcFile, config)
@@ -241,9 +262,11 @@ class JavaMain(config: CLIConfig) {
val lang = LanguageCompilerStatic.byString(langStr)
specs.map { case (_, classSpec) =>
val res = try {
- compileSpecAndWriteToFile(classSpec, lang, outDir)
+ compileSpecAndWriteToFile(specs, classSpec, lang, outDir)
} catch {
case ex: Throwable =>
+ if (config.throwExceptions)
+ ex.printStackTrace()
SpecFailure(List(exceptionToCompileError(ex, classSpec.nameAsStr)))
}
classSpec.nameAsStr -> res
@@ -251,11 +274,12 @@ class JavaMain(config: CLIConfig) {
}
def compileSpecAndWriteToFile(
+ specs: ClassSpecs,
spec: ClassSpec,
lang: LanguageCompilerStatic,
outDir: String
): SpecSuccess = {
- val res = Main.compile(spec, lang, config.runtime)
+ val res = Main.compile(specs, spec, lang, config.runtime)
res.files.foreach { (file) =>
Log.fileOps.info(() => s".... writing ${file.fileName}")
@@ -278,6 +302,13 @@ class JavaMain(config: CLIConfig) {
ex match {
case ype: YAMLParseException =>
CompileError("(main)", ype.path, ype.msg)
+ case e: ErrorInInput =>
+ val file = e.file.getOrElse(srcFile)
+ val msg = Option(e.getCause) match {
+ case Some(cause) => cause.getMessage
+ case None => e.getMessage
+ }
+ CompileError(file, e.path, msg)
case _ =>
CompileError(srcFile, List(), ex.getMessage)
}
diff --git a/jvm/src/main/scala/io/kaitai/struct/formats/JavaKSYParser.scala b/jvm/src/main/scala/io/kaitai/struct/formats/JavaKSYParser.scala
index 4a4c33f0b..57e371564 100644
--- a/jvm/src/main/scala/io/kaitai/struct/formats/JavaKSYParser.scala
+++ b/jvm/src/main/scala/io/kaitai/struct/formats/JavaKSYParser.scala
@@ -55,6 +55,10 @@ object JavaKSYParser {
src
case javaInt: java.lang.Integer =>
javaInt.intValue
+ case javaLong: java.lang.Long =>
+ javaLong.longValue
+ case _: java.math.BigInteger =>
+ src.toString
case null =>
// may be not the very best idea, but these nulls
// should be handled by real parsing code, i.e. where
diff --git a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala
index 2c07db612..5e0fe54e3 100644
--- a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala
+++ b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala
@@ -48,6 +48,34 @@ class ExpressionsSpec extends FunSpec {
Expressions.parse("0b1010_1_010") should be (IntNum(0xaa))
}
+ it("parses simple float") {
+ Expressions.parse("1.2345") should be (FloatNum(1.2345))
+ }
+
+ it("parses float with positive exponent") {
+ Expressions.parse("123e4") should be (FloatNum(123e4))
+ }
+
+ it("parses float with positive exponent with plus sign") {
+ Expressions.parse("123e+4") should be (FloatNum(123e4))
+ }
+
+ it("parses float with negative exponent") {
+ Expressions.parse("123e-7") should be (FloatNum(123e-7))
+ }
+
+ it("parses float + non-integral part with positive exponent") {
+ Expressions.parse("1.2345e7") should be (FloatNum(1.2345e7))
+ }
+
+ it("parses float + non-integral part with positive exponent with plus sign") {
+ Expressions.parse("123.45e+7") should be (FloatNum(123.45e7))
+ }
+
+ it("parses float + non-integral part with negative exponent") {
+ Expressions.parse("123.45e-7") should be (FloatNum(123.45e-7))
+ }
+
it("parses 1 + 2") {
Expressions.parse("1 + 2") should be (BinOp(IntNum(1), Add, IntNum(2)))
}
diff --git a/jvm/src/test/scala/io/kaitai/struct/translators/TranslatorSpec.scala b/jvm/src/test/scala/io/kaitai/struct/translators/TranslatorSpec.scala
index 31765aa87..70a6efa1b 100644
--- a/jvm/src/test/scala/io/kaitai/struct/translators/TranslatorSpec.scala
+++ b/jvm/src/test/scala/io/kaitai/struct/translators/TranslatorSpec.scala
@@ -1,413 +1,454 @@
package io.kaitai.struct.translators
-import io.kaitai.struct.{GraphvizClassCompiler, RuntimeConfig}
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.{Ast, Expressions}
import io.kaitai.struct.format.ClassSpec
import io.kaitai.struct.languages._
import io.kaitai.struct.languages.components.LanguageCompilerStatic
+import io.kaitai.struct.{ImportList, RuntimeConfig}
import org.scalatest.FunSuite
import org.scalatest.Matchers._
-import org.scalatest.prop.TableDrivenPropertyChecks
-
-class TranslatorSpec extends FunSuite with TableDrivenPropertyChecks {
- val tests = Table(
- ("src", "srcType", "expType", "expOut"),
-
- // Integer literals + unary minus
- everybody("123", "123", Int1Type(true)),
- everybody("223", "223", Int1Type(false)),
- everybody("1234", "1234"),
- everybody("-456", "-456"),
- everybody("0x1234", "4660"),
- // less and more than 32 Bit signed int
- everybody("1000000000", "1000000000"),
- everybodyExcept("100000000000", "100000000000", Map[LanguageCompilerStatic, String](
- JavaCompiler -> "100000000000L"
- )),
-
- // Float literals
- everybody("1.0", "1.0", CalcFloatType),
- everybody("123.456", "123.456", CalcFloatType),
- everybody("-123.456", "-123.456", CalcFloatType),
-
- // Simple integer operations
- everybody("1 + 2", "(1 + 2)"),
-
- everybodyExcept("3 / 2", "(3 / 2)", Map(
- JavaScriptCompiler -> "Math.floor(3 / 2)",
- PerlCompiler -> "int(3 / 2)",
- PHPCompiler -> "intval(3 / 2)",
- PythonCompiler -> "3 // 2"
- )),
-
- everybody("1 + 2 + 5", "((1 + 2) + 5)"),
-
- everybodyExcept("(1 + 2) / (7 * 8)", "((1 + 2) / (7 * 8))", Map(
- JavaScriptCompiler -> "Math.floor((1 + 2) / (7 * 8))",
- PerlCompiler -> "int((1 + 2) / (7 * 8))",
- PHPCompiler -> "intval((1 + 2) / (7 * 8))",
- PythonCompiler -> "(1 + 2) // (7 * 8)"
- )),
-
- everybody("1 < 2", "1 < 2", CalcBooleanType),
-
- everybody("1 == 2", "1 == 2", CalcBooleanType),
-
- full("2 < 3 ? \"foo\" : \"bar\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "(2 < 3) ? (std::string(\"foo\")) : (std::string(\"bar\"))",
- CSharpCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
- JavaCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
- JavaScriptCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
- PerlCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
- PHPCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
- PythonCompiler -> "u\"foo\" if 2 < 3 else u\"bar\"",
- RubyCompiler -> "2 < 3 ? \"foo\" : \"bar\""
- )),
-
- everybody("~777", "~777"),
- everybody("~(7+3)", "~(7 + 3)"),
-
- // Simple float operations
- everybody("1.2 + 3.4", "(1.2 + 3.4)", CalcFloatType),
- everybody("1.2 + 3", "(1.2 + 3)", CalcFloatType),
- everybody("1 + 3.4", "(1 + 3.4)", CalcFloatType),
-
- everybody("1.0 < 2", "1.0 < 2", CalcBooleanType),
-
- everybody("3 / 2.0", "(3 / 2.0)", CalcFloatType),
-
- everybody("(1 + 2) / (7 * 8.1)", "((1 + 2) / (7 * 8.1))", CalcFloatType),
-
- // Boolean literals
- full("true", CalcBooleanType, CalcBooleanType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "true",
- CSharpCompiler -> "true",
- JavaCompiler -> "true",
- JavaScriptCompiler -> "true",
- PerlCompiler -> "1",
- PHPCompiler -> "true",
- PythonCompiler -> "True",
- RubyCompiler -> "true"
- )),
-
- full("false", CalcBooleanType, CalcBooleanType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "false",
- CSharpCompiler -> "false",
- JavaCompiler -> "false",
- JavaScriptCompiler -> "false",
- PerlCompiler -> "0",
- PHPCompiler -> "false",
- PythonCompiler -> "False",
- RubyCompiler -> "false"
- )),
-
- full("some_bool.to_i", CalcBooleanType, CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "some_bool()",
- CSharpCompiler -> "(SomeBool ? 1 : 0)",
- JavaCompiler -> "(someBool() ? 1 : 0)",
- JavaScriptCompiler -> "(this.someBool | 0)",
- PerlCompiler -> "$self->some_bool()",
- PHPCompiler -> "intval($this->someBool())",
- PythonCompiler -> "int(self.some_bool)",
- RubyCompiler -> "(some_bool ? 1 : 0)"
- )),
-
- // Member access
- full("foo_str", CalcStrType, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "foo_str()",
- CSharpCompiler -> "FooStr",
- JavaCompiler -> "fooStr()",
- JavaScriptCompiler -> "this.fooStr",
- PerlCompiler -> "$self->foo_str()",
- PHPCompiler -> "$this->fooStr()",
- PythonCompiler -> "self.foo_str",
- RubyCompiler -> "foo_str"
- )),
-
- full("foo_block", userType("block"), userType("block"), Map[LanguageCompilerStatic, String](
- CppCompiler -> "foo_block()",
- CSharpCompiler -> "FooBlock",
- JavaCompiler -> "fooBlock()",
- JavaScriptCompiler -> "this.fooBlock",
- PerlCompiler -> "$self->foo_block()",
- PHPCompiler -> "$this->fooBlock()",
- PythonCompiler -> "self.foo_block",
- RubyCompiler -> "foo_block"
- )),
-
- full("foo.bar", FooBarProvider, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "foo()->bar()",
- CSharpCompiler -> "Foo.Bar",
- JavaCompiler -> "foo().bar()",
- JavaScriptCompiler -> "this.foo.bar",
- PerlCompiler -> "$self->foo()->bar()",
- PHPCompiler -> "$this->foo()->bar()",
- PythonCompiler -> "self.foo.bar",
- RubyCompiler -> "foo.bar"
- )),
-
- full("foo.inner.baz", FooBarProvider, CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "foo()->inner()->baz()",
- CSharpCompiler -> "Foo.Inner.Baz",
- JavaCompiler -> "foo().inner().baz()",
- JavaScriptCompiler -> "this.foo.inner.baz",
- PerlCompiler -> "$self->foo()->inner()->baz()",
- PHPCompiler -> "$this->foo()->inner()->baz()",
- PythonCompiler -> "self.foo.inner.baz",
- RubyCompiler -> "foo.inner.baz"
- )),
-
- full("_root.foo", userType("block"), userType("block"), Map[LanguageCompilerStatic, String](
- CppCompiler -> "_root()->foo()",
- CSharpCompiler -> "M_Root.Foo",
- JavaCompiler -> "_root.foo()",
- JavaScriptCompiler -> "this._root.foo",
- PerlCompiler -> "$self->_root()->foo()",
- PHPCompiler -> "$this->_root()->foo()",
- PythonCompiler -> "self._root.foo",
- RubyCompiler -> "_root.foo"
- )),
-
- full("a != 2 and a != 5", CalcIntType, CalcBooleanType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "a() != 2 && a() != 5",
- CSharpCompiler -> "A != 2 && A != 5",
- JavaCompiler -> "a() != 2 && a() != 5",
- JavaScriptCompiler -> "this.a != 2 && this.a != 5",
- PerlCompiler -> "$self->a() != 2 && $self->a() != 5",
- PHPCompiler -> "$this->a() != 2 && $this->a() != 5",
- PythonCompiler -> "self.a != 2 and self.a != 5",
- RubyCompiler -> "a != 2 && a != 5"
- )),
-
- // Arrays
- full("[0, 1, 100500]", CalcIntType, ArrayType(CalcIntType), Map[LanguageCompilerStatic, String](
- CSharpCompiler -> "new List { 0, 1, 100500 }",
- JavaCompiler -> "new ArrayList(Arrays.asList(0L, 1L, 100500L))",
- JavaScriptCompiler -> "[0, 1, 100500]",
- PerlCompiler -> "(0, 1, 100500)",
- PHPCompiler -> "[0, 1, 100500]",
- PythonCompiler -> "[0, 1, 100500]",
- RubyCompiler -> "[0, 1, 100500]"
- )),
-
- full("[34, 0, 10, 64, 65, 66, 92]", CalcIntType, CalcBytesType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"\\x22\\x00\\x0A\\x40\\x41\\x42\\x5C\", 7)",
- CSharpCompiler -> "new byte[] { 34, 0, 10, 64, 65, 66, 92 }",
- JavaCompiler -> "new byte[] { 34, 0, 10, 64, 65, 66, 92 }",
- JavaScriptCompiler -> "[34, 0, 10, 64, 65, 66, 92]",
- PerlCompiler -> "pack('C*', (34, 0, 10, 64, 65, 66, 92))",
- PHPCompiler -> "\"\\x22\\x00\\x0A\\x40\\x41\\x42\\x5C\"",
- PythonCompiler -> "struct.pack('7b', 34, 0, 10, 64, 65, 66, 92)",
- RubyCompiler -> "[34, 0, 10, 64, 65, 66, 92].pack('C*')"
- )),
-
- full("[255, 0, 255]", CalcIntType, CalcBytesType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"\\xFF\\x00\\xFF\", 3)",
- CSharpCompiler -> "new byte[] { 255, 0, 255 }",
- JavaCompiler -> "new byte[] { -1, 0, -1 }",
- JavaScriptCompiler -> "[255, 0, 255]",
- PerlCompiler -> "pack('C*', (255, 0, 255))",
- PHPCompiler -> "\"\\xFF\\x00\\xFF\"",
- PythonCompiler -> "struct.pack('3b', -1, 0, -1)",
- RubyCompiler -> "[255, 0, 255].pack('C*')"
- )),
-
- full("a[42]", ArrayType(CalcStrType), CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "a()->at(42)",
- CSharpCompiler -> "A[42]",
- JavaCompiler -> "a().get(42)",
- JavaScriptCompiler -> "this.a[42]",
- PythonCompiler -> "self.a[42]",
- RubyCompiler -> "a[42]"
- )),
-
- full("a[42 - 2]", ArrayType(CalcStrType), CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "a()->at((42 - 2))",
- CSharpCompiler -> "A[(42 - 2)]",
- JavaCompiler -> "a().get((42 - 2))",
- JavaScriptCompiler -> "this.a[(42 - 2)]",
- PythonCompiler -> "self.a[(42 - 2)]",
- RubyCompiler -> "a[(42 - 2)]"
- )),
-
- full("a.first", ArrayType(CalcIntType), CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "a()->front()",
- CSharpCompiler -> "A[0]",
- JavaCompiler -> "a().get(0)",
- JavaScriptCompiler -> "this.a[0]",
- PythonCompiler -> "self.a[0]",
- RubyCompiler -> "a.first"
- )),
-
- full("a.last", ArrayType(CalcIntType), CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "a()->back()",
- CSharpCompiler -> "A[A.Length - 1]",
- JavaCompiler -> "a().get(a().size() - 1)",
- JavaScriptCompiler -> "this.a[this.a.length - 1]",
- PythonCompiler -> "self.a[-1]",
- RubyCompiler -> "a.last"
- )),
-
- full("a.size", ArrayType(CalcIntType), CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "a()->size()",
- CSharpCompiler -> "A.Count",
- JavaCompiler -> "a().size()",
- JavaScriptCompiler -> "this.a.length",
- PHPCompiler -> "count(a)",
- PerlCompiler -> "scalar($self->a())",
- PythonCompiler -> "len(self.a)",
- RubyCompiler -> "a.length"
- )),
-
- // Strings
- full("\"str\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str\")",
- CSharpCompiler -> "\"str\"",
- JavaCompiler -> "\"str\"",
- JavaScriptCompiler -> "\"str\"",
- PerlCompiler -> "\"str\"",
- PHPCompiler -> "\"str\"",
- PythonCompiler -> "u\"str\"",
- RubyCompiler -> "\"str\""
- )),
-
- full("\"str\\nnext\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str\\nnext\")",
- CSharpCompiler -> "\"str\\nnext\"",
- JavaCompiler -> "\"str\\nnext\"",
- JavaScriptCompiler -> "\"str\\nnext\"",
- PerlCompiler -> "\"str\\nnext\"",
- PHPCompiler -> "\"str\\nnext\"",
- PythonCompiler -> "u\"str\\nnext\"",
- RubyCompiler -> "\"str\\nnext\""
- )),
-
- full("\"str\\u000anext\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str\\nnext\")",
- CSharpCompiler -> "\"str\\nnext\"",
- JavaCompiler -> "\"str\\nnext\"",
- JavaScriptCompiler -> "\"str\\nnext\"",
- PerlCompiler -> "\"str\\nnext\"",
- PHPCompiler -> "\"str\\nnext\"",
- PythonCompiler -> "u\"str\\nnext\"",
- RubyCompiler -> "\"str\\nnext\""
- )),
-
- full("\"str\\0next\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str\\000next\", 8)",
- CSharpCompiler -> "\"str\\0next\"",
- JavaCompiler -> "\"str\\000next\"",
- JavaScriptCompiler -> "\"str\\000next\"",
- PerlCompiler -> "\"str\\000next\"",
- PHPCompiler -> "\"str\\000next\"",
- PythonCompiler -> "u\"str\\000next\"",
- RubyCompiler -> "\"str\\000next\""
- )),
-
- everybodyExcept("\"str1\" + \"str2\"", "\"str1\" + \"str2\"", Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str1\") + std::string(\"str2\")",
- PerlCompiler -> "\"str1\" . \"str2\"",
- PHPCompiler -> "\"str1\" . \"str2\"",
- PythonCompiler -> "u\"str1\" + u\"str2\""
- ), CalcStrType),
-
- everybodyExcept("\"str1\" == \"str2\"", "\"str1\" == \"str2\"", Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str1\") == (std::string(\"str2\"))",
- JavaCompiler -> "\"str1\".equals(\"str2\")",
- PerlCompiler -> "\"str1\" eq \"str2\"",
- PythonCompiler -> "u\"str1\" == u\"str2\""
- ), CalcBooleanType),
-
- everybodyExcept("\"str1\" != \"str2\"", "\"str1\" != \"str2\"", Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str1\") != std::string(\"str2\")",
- JavaCompiler -> "!(\"str1\").equals(\"str2\")",
- PerlCompiler -> "\"str1\" ne \"str2\"",
- PythonCompiler -> "u\"str1\" != u\"str2\""
- ), CalcBooleanType),
-
- everybodyExcept("\"str1\" < \"str2\"", "\"str1\" < \"str2\"", Map[LanguageCompilerStatic, String](
- CppCompiler -> "(std::string(\"str1\").compare(std::string(\"str2\")) < 0)",
- CSharpCompiler -> "(\"str1\".CompareTo(\"str2\") < 0)",
- JavaCompiler -> "(\"str1\".compareTo(\"str2\") < 0)",
- PerlCompiler -> "\"str1\" lt \"str2\"",
- PythonCompiler -> "u\"str1\" < u\"str2\""
- ), CalcBooleanType),
-
- full("\"str\".length", CalcIntType, CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::string(\"str\").length()",
- CSharpCompiler -> "\"str\".Length",
- JavaCompiler -> "\"str\".length()",
- JavaScriptCompiler -> "\"str\".length",
- PerlCompiler -> "length(\"str\")",
- PHPCompiler -> "strlen(\"str\")",
- PythonCompiler -> "len(u\"str\")",
- RubyCompiler -> "\"str\".size"
- )),
-
- full("\"str\".reverse", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "kaitai::kstream::reverse(std::string(\"str\"))",
- CSharpCompiler -> "new string(Array.Reverse(\"str\".ToCharArray()))",
- JavaCompiler -> "new StringBuilder(\"str\").reverse().toString()",
- JavaScriptCompiler -> "Array.from(\"str\").reverse().join('')",
- PerlCompiler -> "scalar(reverse(\"str\"))",
- PHPCompiler -> "strrev(\"str\")",
- PythonCompiler -> "u\"str\"[::-1]",
- RubyCompiler -> "\"str\".reverse"
- )),
-
- full("\"12345\".to_i", CalcIntType, CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::stoi(std::string(\"12345\"))",
- CSharpCompiler -> "Convert.ToInt64(\"12345\", 10)",
- JavaCompiler -> "Long.parseLong(\"12345\", 10)",
- JavaScriptCompiler -> "Number.parseInt(\"12345\", 10)",
- PerlCompiler -> "\"12345\"",
- PHPCompiler -> "intval(\"12345\", 10)",
- PythonCompiler -> "int(u\"12345\")",
- RubyCompiler -> "\"12345\".to_i"
- )),
-
- full("\"1234fe\".to_i(16)", CalcIntType, CalcIntType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "std::stoi(std::string(\"1234fe\"), 0, 16)",
- CSharpCompiler -> "Convert.ToInt64(\"1234fe\", 16)",
- JavaCompiler -> "Long.parseLong(\"1234fe\", 16)",
- JavaScriptCompiler -> "Number.parseInt(\"1234fe\", 16)",
- PerlCompiler -> "hex(\"1234fe\")",
- PHPCompiler -> "intval(\"1234fe\", 16)",
- PythonCompiler -> "int(u\"1234fe\", 16)",
- RubyCompiler -> "\"1234fe\".to_i(16)"
- )),
-
- // casts
- full("other.as.bar", FooBarProvider, CalcStrType, Map[LanguageCompilerStatic, String](
- CppCompiler -> "static_cast(other())->bar()",
- CSharpCompiler -> "((Block) (Other)).Bar",
- JavaCompiler -> "((Block) (other())).bar()",
- JavaScriptCompiler -> "this.other.bar",
- PerlCompiler -> "$self->other()->bar()",
- PHPCompiler -> "$this->other()->bar()",
- PythonCompiler -> "self.other.bar",
- RubyCompiler -> "other.bar"
- )),
-
- // very simple workaround for Scala not having optional trailing commas
- everybody("999", "999")
- )
-
- for ((src, tp, expType, expOut) <- tests) {
+
+class TranslatorSpec extends FunSuite {
+
+ // Integer literals + unary minus
+ everybody("123", "123", Int1Type(true))
+ everybody("223", "223", Int1Type(false))
+ everybody("1234", "1234")
+ everybody("-456", "-456")
+ everybody("0x1234", "4660")
+ // less and more than 32 Bit signed int
+ everybody("1000000000", "1000000000")
+ everybodyExcept("100000000000", "100000000000", Map[LanguageCompilerStatic, String](
+ JavaCompiler -> "100000000000L"
+ ))
+
+ // Float literals
+ everybody("1.0", "1.0", CalcFloatType)
+ everybody("123.456", "123.456", CalcFloatType)
+ everybody("-123.456", "-123.456", CalcFloatType)
+
+ // Simple integer operations
+ everybody("1 + 2", "(1 + 2)")
+
+ everybodyExcept("3 / 2", "(3 / 2)", Map(
+ JavaScriptCompiler -> "Math.floor(3 / 2)",
+ LuaCompiler -> "3 / 2",
+ PerlCompiler -> "int(3 / 2)",
+ PHPCompiler -> "intval(3 / 2)",
+ PythonCompiler -> "3 // 2"
+ ))
+
+ everybody("1 + 2 + 5", "((1 + 2) + 5)")
+
+ everybodyExcept("(1 + 2) / (7 * 8)", "((1 + 2) / (7 * 8))", Map(
+ JavaScriptCompiler -> "Math.floor((1 + 2) / (7 * 8))",
+ LuaCompiler -> "(1 + 2) / (7 * 8)",
+ PerlCompiler -> "int((1 + 2) / (7 * 8))",
+ PHPCompiler -> "intval((1 + 2) / (7 * 8))",
+ PythonCompiler -> "(1 + 2) // (7 * 8)"
+ ))
+
+ everybody("1 < 2", "1 < 2", CalcBooleanType)
+
+ everybody("1 == 2", "1 == 2", CalcBooleanType)
+
+ full("2 < 3 ? \"foo\" : \"bar\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "(2 < 3) ? (std::string(\"foo\")) : (std::string(\"bar\"))",
+ CSharpCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
+ JavaCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
+ JavaScriptCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
+ LuaCompiler -> "2 < 3 and \"foo\" or \"bar\"",
+ PerlCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
+ PHPCompiler -> "2 < 3 ? \"foo\" : \"bar\"",
+ PythonCompiler -> "u\"foo\" if 2 < 3 else u\"bar\"",
+ RubyCompiler -> "2 < 3 ? \"foo\" : \"bar\""
+ ))
+
+ everybody("~777", "~777")
+ everybody("~(7+3)", "~((7 + 3))")
+
+ // Simple float operations
+ everybody("1.2 + 3.4", "(1.2 + 3.4)", CalcFloatType)
+ everybody("1.2 + 3", "(1.2 + 3)", CalcFloatType)
+ everybody("1 + 3.4", "(1 + 3.4)", CalcFloatType)
+
+ everybody("1.0 < 2", "1.0 < 2", CalcBooleanType)
+
+ everybody("3 / 2.0", "(3 / 2.0)", CalcFloatType)
+
+ everybody("(1 + 2) / (7 * 8.1)", "((1 + 2) / (7 * 8.1))", CalcFloatType)
+
+ // Boolean literals
+ full("true", CalcBooleanType, CalcBooleanType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "true",
+ CSharpCompiler -> "true",
+ JavaCompiler -> "true",
+ JavaScriptCompiler -> "true",
+ LuaCompiler -> "true",
+ PerlCompiler -> "1",
+ PHPCompiler -> "true",
+ PythonCompiler -> "True",
+ RubyCompiler -> "true"
+ ))
+
+ full("false", CalcBooleanType, CalcBooleanType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "false",
+ CSharpCompiler -> "false",
+ JavaCompiler -> "false",
+ JavaScriptCompiler -> "false",
+ LuaCompiler -> "false",
+ PerlCompiler -> "0",
+ PHPCompiler -> "false",
+ PythonCompiler -> "False",
+ RubyCompiler -> "false"
+ ))
+
+ full("some_bool.to_i", CalcBooleanType, CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "some_bool()",
+ CSharpCompiler -> "(SomeBool ? 1 : 0)",
+ JavaCompiler -> "(someBool() ? 1 : 0)",
+ JavaScriptCompiler -> "(this.someBool | 0)",
+ LuaCompiler -> "self.some_bool and 1 or 0",
+ PerlCompiler -> "$self->some_bool()",
+ PHPCompiler -> "intval($this->someBool())",
+ PythonCompiler -> "int(self.some_bool)",
+ RubyCompiler -> "(some_bool ? 1 : 0)"
+ ))
+
+ // Member access
+ full("foo_str", CalcStrType, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "foo_str()",
+ CSharpCompiler -> "FooStr",
+ JavaCompiler -> "fooStr()",
+ JavaScriptCompiler -> "this.fooStr",
+ LuaCompiler -> "self.foo_str",
+ PerlCompiler -> "$self->foo_str()",
+ PHPCompiler -> "$this->fooStr()",
+ PythonCompiler -> "self.foo_str",
+ RubyCompiler -> "foo_str"
+ ))
+
+ full("foo_block", userType("block"), userType("block"), Map[LanguageCompilerStatic, String](
+ CppCompiler -> "foo_block()",
+ CSharpCompiler -> "FooBlock",
+ JavaCompiler -> "fooBlock()",
+ JavaScriptCompiler -> "this.fooBlock",
+ LuaCompiler -> "self.foo_block",
+ PerlCompiler -> "$self->foo_block()",
+ PHPCompiler -> "$this->fooBlock()",
+ PythonCompiler -> "self.foo_block",
+ RubyCompiler -> "foo_block"
+ ))
+
+ full("foo.bar", FooBarProvider, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "foo()->bar()",
+ CSharpCompiler -> "Foo.Bar",
+ JavaCompiler -> "foo().bar()",
+ JavaScriptCompiler -> "this.foo.bar",
+ LuaCompiler -> "self.foo.bar",
+ PerlCompiler -> "$self->foo()->bar()",
+ PHPCompiler -> "$this->foo()->bar()",
+ PythonCompiler -> "self.foo.bar",
+ RubyCompiler -> "foo.bar"
+ ))
+
+ full("foo.inner.baz", FooBarProvider, CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "foo()->inner()->baz()",
+ CSharpCompiler -> "Foo.Inner.Baz",
+ JavaCompiler -> "foo().inner().baz()",
+ JavaScriptCompiler -> "this.foo.inner.baz",
+ LuaCompiler -> "self.foo.inner.baz",
+ PerlCompiler -> "$self->foo()->inner()->baz()",
+ PHPCompiler -> "$this->foo()->inner()->baz()",
+ PythonCompiler -> "self.foo.inner.baz",
+ RubyCompiler -> "foo.inner.baz"
+ ))
+
+ full("_root.foo", userType("block"), userType("block"), Map[LanguageCompilerStatic, String](
+ CppCompiler -> "_root()->foo()",
+ CSharpCompiler -> "M_Root.Foo",
+ JavaCompiler -> "_root.foo()",
+ JavaScriptCompiler -> "this._root.foo",
+ LuaCompiler -> "self._root.foo",
+ PerlCompiler -> "$self->_root()->foo()",
+ PHPCompiler -> "$this->_root()->foo()",
+ PythonCompiler -> "self._root.foo",
+ RubyCompiler -> "_root.foo"
+ ))
+
+ full("a != 2 and a != 5", CalcIntType, CalcBooleanType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "a() != 2 && a() != 5",
+ CSharpCompiler -> "A != 2 && A != 5",
+ JavaCompiler -> "a() != 2 && a() != 5",
+ JavaScriptCompiler -> "this.a != 2 && this.a != 5",
+ LuaCompiler -> "self.a ~= 2 and self.a ~= 5",
+ PerlCompiler -> "$self->a() != 2 && $self->a() != 5",
+ PHPCompiler -> "$this->a() != 2 && $this->a() != 5",
+ PythonCompiler -> "self.a != 2 and self.a != 5",
+ RubyCompiler -> "a != 2 && a != 5"
+ ))
+
+ // Arrays
+ full("[0, 1, 100500]", CalcIntType, ArrayType(CalcIntType), Map[LanguageCompilerStatic, String](
+ CSharpCompiler -> "new List { 0, 1, 100500 }",
+ JavaCompiler -> "new ArrayList(Arrays.asList(0L, 1L, 100500L))",
+ JavaScriptCompiler -> "[0, 1, 100500]",
+ LuaCompiler -> "{0, 1, 100500}",
+ PerlCompiler -> "(0, 1, 100500)",
+ PHPCompiler -> "[0, 1, 100500]",
+ PythonCompiler -> "[0, 1, 100500]",
+ RubyCompiler -> "[0, 1, 100500]"
+ ))
+
+ full("[34, 0, 10, 64, 65, 66, 92]", CalcIntType, CalcBytesType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"\\x22\\x00\\x0A\\x40\\x41\\x42\\x5C\", 7)",
+ CSharpCompiler -> "new byte[] { 34, 0, 10, 64, 65, 66, 92 }",
+ JavaCompiler -> "new byte[] { 34, 0, 10, 64, 65, 66, 92 }",
+ JavaScriptCompiler -> "[34, 0, 10, 64, 65, 66, 92]",
+ LuaCompiler -> "\"\\034\\000\\010\\064\\065\\066\\092\"",
+ PerlCompiler -> "pack('C*', (34, 0, 10, 64, 65, 66, 92))",
+ PHPCompiler -> "\"\\x22\\x00\\x0A\\x40\\x41\\x42\\x5C\"",
+ PythonCompiler -> "struct.pack('7b', 34, 0, 10, 64, 65, 66, 92)",
+ RubyCompiler -> "[34, 0, 10, 64, 65, 66, 92].pack('C*')"
+ ))
+
+ full("[255, 0, 255]", CalcIntType, CalcBytesType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"\\xFF\\x00\\xFF\", 3)",
+ CSharpCompiler -> "new byte[] { 255, 0, 255 }",
+ JavaCompiler -> "new byte[] { -1, 0, -1 }",
+ JavaScriptCompiler -> "[255, 0, 255]",
+ LuaCompiler -> "\"\\255\\000\\255\"",
+ PerlCompiler -> "pack('C*', (255, 0, 255))",
+ PHPCompiler -> "\"\\xFF\\x00\\xFF\"",
+ PythonCompiler -> "struct.pack('3b', -1, 0, -1)",
+ RubyCompiler -> "[255, 0, 255].pack('C*')"
+ ))
+
+ full("a[42]", ArrayType(CalcStrType), CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "a()->at(42)",
+ CSharpCompiler -> "A[42]",
+ JavaCompiler -> "a().get(42)",
+ JavaScriptCompiler -> "this.a[42]",
+ LuaCompiler -> "self.a[43]",
+ PHPCompiler -> "$this->a()[42]",
+ PythonCompiler -> "self.a[42]",
+ RubyCompiler -> "a[42]"
+ ))
+
+ full("a[42 - 2]", ArrayType(CalcStrType), CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "a()->at((42 - 2))",
+ CSharpCompiler -> "A[(42 - 2)]",
+ JavaCompiler -> "a().get((42 - 2))",
+ JavaScriptCompiler -> "this.a[(42 - 2)]",
+ LuaCompiler -> "self.a[(43 - 2)]",
+ PHPCompiler -> "$this->a()[(42 - 2)]",
+ PythonCompiler -> "self.a[(42 - 2)]",
+ RubyCompiler -> "a[(42 - 2)]"
+ ))
+
+ full("a.first", ArrayType(CalcIntType), CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "a()->front()",
+ CSharpCompiler -> "A[0]",
+ JavaCompiler -> "a().get(0)",
+ JavaScriptCompiler -> "this.a[0]",
+ LuaCompiler -> "self.a[1]",
+ PHPCompiler -> "$this->a()[0]",
+ PythonCompiler -> "self.a[0]",
+ RubyCompiler -> "a.first"
+ ))
+
+ full("a.last", ArrayType(CalcIntType), CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "a()->back()",
+ CSharpCompiler -> "A[A.Length - 1]",
+ JavaCompiler -> "a().get(a().size() - 1)",
+ JavaScriptCompiler -> "this.a[this.a.length - 1]",
+ LuaCompiler -> "self.a[#self.a]",
+ PHPCompiler -> "$this->a()[count($this->a()) - 1]",
+ PythonCompiler -> "self.a[-1]",
+ RubyCompiler -> "a.last"
+ ))
+
+ full("a.size", ArrayType(CalcIntType), CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "a()->size()",
+ CSharpCompiler -> "A.Count",
+ JavaCompiler -> "a().size()",
+ JavaScriptCompiler -> "this.a.length",
+ LuaCompiler -> "#self.a",
+ PHPCompiler -> "count($this->a())",
+ PerlCompiler -> "scalar($self->a())",
+ PythonCompiler -> "len(self.a)",
+ RubyCompiler -> "a.length"
+ ))
+
+ // Strings
+ full("\"str\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str\")",
+ CSharpCompiler -> "\"str\"",
+ JavaCompiler -> "\"str\"",
+ JavaScriptCompiler -> "\"str\"",
+ LuaCompiler -> "\"str\"",
+ PerlCompiler -> "\"str\"",
+ PHPCompiler -> "\"str\"",
+ PythonCompiler -> "u\"str\"",
+ RubyCompiler -> "\"str\""
+ ))
+
+ full("\"str\\nnext\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str\\nnext\")",
+ CSharpCompiler -> "\"str\\nnext\"",
+ JavaCompiler -> "\"str\\nnext\"",
+ JavaScriptCompiler -> "\"str\\nnext\"",
+ LuaCompiler -> "\"str\\nnext\"",
+ PerlCompiler -> "\"str\\nnext\"",
+ PHPCompiler -> "\"str\\nnext\"",
+ PythonCompiler -> "u\"str\\nnext\"",
+ RubyCompiler -> "\"str\\nnext\""
+ ))
+
+ full("\"str\\u000anext\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str\\nnext\")",
+ CSharpCompiler -> "\"str\\nnext\"",
+ JavaCompiler -> "\"str\\nnext\"",
+ JavaScriptCompiler -> "\"str\\nnext\"",
+ LuaCompiler -> "\"str\\nnext\"",
+ PerlCompiler -> "\"str\\nnext\"",
+ PHPCompiler -> "\"str\\nnext\"",
+ PythonCompiler -> "u\"str\\nnext\"",
+ RubyCompiler -> "\"str\\nnext\""
+ ))
+
+ full("\"str\\0next\"", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str\\000next\", 8)",
+ CSharpCompiler -> "\"str\\0next\"",
+ JavaCompiler -> "\"str\\000next\"",
+ JavaScriptCompiler -> "\"str\\000next\"",
+ LuaCompiler -> "\"str\\000next\"",
+ PerlCompiler -> "\"str\\000next\"",
+ PHPCompiler -> "\"str\\000next\"",
+ PythonCompiler -> "u\"str\\000next\"",
+ RubyCompiler -> "\"str\\000next\""
+ ))
+
+ everybodyExcept("\"str1\" + \"str2\"", "\"str1\" + \"str2\"", Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str1\") + std::string(\"str2\")",
+ LuaCompiler -> "\"str1\" .. \"str2\"",
+ PerlCompiler -> "\"str1\" . \"str2\"",
+ PHPCompiler -> "\"str1\" . \"str2\"",
+ PythonCompiler -> "u\"str1\" + u\"str2\""
+ ), CalcStrType)
+
+ everybodyExcept("\"str1\" == \"str2\"", "\"str1\" == \"str2\"", Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str1\") == (std::string(\"str2\"))",
+ JavaCompiler -> "\"str1\".equals(\"str2\")",
+ LuaCompiler -> "\"str1\" == \"str2\"",
+ PerlCompiler -> "\"str1\" eq \"str2\"",
+ PythonCompiler -> "u\"str1\" == u\"str2\""
+ ), CalcBooleanType)
+
+ everybodyExcept("\"str1\" != \"str2\"", "\"str1\" != \"str2\"", Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str1\") != std::string(\"str2\")",
+ JavaCompiler -> "!(\"str1\").equals(\"str2\")",
+ LuaCompiler -> "\"str1\" ~= \"str2\"",
+ PerlCompiler -> "\"str1\" ne \"str2\"",
+ PythonCompiler -> "u\"str1\" != u\"str2\""
+ ), CalcBooleanType)
+
+ everybodyExcept("\"str1\" < \"str2\"", "\"str1\" < \"str2\"", Map[LanguageCompilerStatic, String](
+ CppCompiler -> "(std::string(\"str1\").compare(std::string(\"str2\")) < 0)",
+ CSharpCompiler -> "(\"str1\".CompareTo(\"str2\") < 0)",
+ JavaCompiler -> "(\"str1\".compareTo(\"str2\") < 0)",
+ LuaCompiler -> "\"str1\" < \"str2\"",
+ PerlCompiler -> "\"str1\" lt \"str2\"",
+ PythonCompiler -> "u\"str1\" < u\"str2\""
+ ), CalcBooleanType)
+
+ full("\"str\".length", CalcIntType, CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::string(\"str\").length()",
+ CSharpCompiler -> "\"str\".Length",
+ JavaCompiler -> "\"str\".length()",
+ JavaScriptCompiler -> "#\"str\"",
+ LuaCompiler -> "string.len(\"str\")",
+ PerlCompiler -> "length(\"str\")",
+ PHPCompiler -> "strlen(\"str\")",
+ PythonCompiler -> "len(u\"str\")",
+ RubyCompiler -> "\"str\".size"
+ ))
+
+ full("\"str\".reverse", CalcIntType, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "kaitai::kstream::reverse(std::string(\"str\"))",
+ CSharpCompiler -> "new string(Array.Reverse(\"str\".ToCharArray()))",
+ JavaCompiler -> "new StringBuilder(\"str\").reverse().toString()",
+ JavaScriptCompiler -> "Array.from(\"str\").reverse().join('')",
+ LuaCompiler -> "string.reverse(\"str\")",
+ PerlCompiler -> "scalar(reverse(\"str\"))",
+ PHPCompiler -> "strrev(\"str\")",
+ PythonCompiler -> "u\"str\"[::-1]",
+ RubyCompiler -> "\"str\".reverse"
+ ))
+
+ full("\"12345\".to_i", CalcIntType, CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::stoi(std::string(\"12345\"))",
+ CSharpCompiler -> "Convert.ToInt64(\"12345\", 10)",
+ JavaCompiler -> "Long.parseLong(\"12345\", 10)",
+ JavaScriptCompiler -> "Number.parseInt(\"12345\", 10)",
+ LuaCompiler -> "tonumber(\"12345\")",
+ PerlCompiler -> "\"12345\"",
+ PHPCompiler -> "intval(\"12345\", 10)",
+ PythonCompiler -> "int(u\"12345\")",
+ RubyCompiler -> "\"12345\".to_i"
+ ))
+
+ full("\"1234fe\".to_i(16)", CalcIntType, CalcIntType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "std::stoi(std::string(\"1234fe\"), 0, 16)",
+ CSharpCompiler -> "Convert.ToInt64(\"1234fe\", 16)",
+ JavaCompiler -> "Long.parseLong(\"1234fe\", 16)",
+ JavaScriptCompiler -> "Number.parseInt(\"1234fe\", 16)",
+ LuaCompiler -> "tonumber(\"1234fe\", 16)",
+ PerlCompiler -> "hex(\"1234fe\")",
+ PHPCompiler -> "intval(\"1234fe\", 16)",
+ PythonCompiler -> "int(u\"1234fe\", 16)",
+ RubyCompiler -> "\"1234fe\".to_i(16)"
+ ))
+
+ // casts
+ full("other.as.bar", FooBarProvider, CalcStrType, Map[LanguageCompilerStatic, String](
+ CppCompiler -> "static_cast(other())->bar()",
+ CSharpCompiler -> "((Block) (Other)).Bar",
+ JavaCompiler -> "((Block) (other())).bar()",
+ JavaScriptCompiler -> "this.other.bar",
+ LuaCompiler -> "self.other.bar",
+ PerlCompiler -> "$self->other()->bar()",
+ PHPCompiler -> "$this->other()->bar()",
+ PythonCompiler -> "self.other.bar",
+ RubyCompiler -> "other.bar"
+ ))
+
+ def runTest(src: String, tp: TypeProvider, expType: DataType, expOut: ResultMap) {
var eo: Option[Ast.expr] = None
test(s"_expr:$src") {
eo = Some(Expressions.parse(src))
}
- LanguageCompilerStatic.NAME_TO_CLASS.
- filter { case (_, langObj) => langObj != GraphvizClassCompiler }.
- foreach { case (langName, langObj) =>
+ val langs = Map[LanguageCompilerStatic, BaseTranslator](
+ CppCompiler -> new CppTranslator(tp, new ImportList()),
+ CSharpCompiler -> new CSharpTranslator(tp, new ImportList()),
+ JavaCompiler -> new JavaTranslator(tp, new ImportList()),
+ JavaScriptCompiler -> new JavaScriptTranslator(tp),
+ LuaCompiler -> new LuaTranslator(tp, new ImportList()),
+ PerlCompiler -> new PerlTranslator(tp, new ImportList()),
+ PHPCompiler -> new PHPTranslator(tp, RuntimeConfig()),
+ PythonCompiler -> new PythonTranslator(tp, new ImportList()),
+ RubyCompiler -> new RubyTranslator(tp)
+ )
+
+ langs.foreach { case (langObj, tr) =>
+ val langName = LanguageCompilerStatic.CLASS_TO_NAME(langObj)
test(s"$langName:$src") {
eo match {
case Some(e) =>
- val tr: BaseTranslator = langObj.getTranslator(tp, RuntimeConfig())
+ val tr: BaseTranslator = langs(langObj)
expOut.get(langObj) match {
case Some(expResult) =>
tr.detectType(e) should be(expType)
@@ -433,6 +474,10 @@ class TranslatorSpec extends FunSuite with TableDrivenPropertyChecks {
override def resolveType(typeName: String): DataType =
throw new NotImplementedError
+
+ override def isLazy(attrName: String): Boolean = false
+
+ override def isLazy(inClass: ClassSpec, attrName: String): Boolean = false
}
case class Always(t: DataType) extends FakeTypeProvider {
@@ -472,19 +517,17 @@ class TranslatorSpec extends FunSuite with TableDrivenPropertyChecks {
lazy val ALL_LANGS = LanguageCompilerStatic.NAME_TO_CLASS.values
- def full(src: String, srcType: DataType, expType: DataType, expOut: ResultMap): TestSpec =
- (src, Always(srcType), expType, expOut)
+ def full(src: String, srcType: DataType, expType: DataType, expOut: ResultMap) =
+ runTest(src, Always(srcType), expType, expOut)
- def full(src: String, tp: TypeProvider, expType: DataType, expOut: ResultMap): TestSpec =
- (src, tp, expType, expOut)
+ def full(src: String, tp: TypeProvider, expType: DataType, expOut: ResultMap) =
+ runTest(src, tp, expType, expOut)
- def everybody(src: String, expOut: String, expType: DataType = CalcIntType): TestSpec = {
- (src, Always(CalcIntType), expType, ALL_LANGS.map((langObj) => langObj -> expOut).toMap)
- }
+ def everybody(src: String, expOut: String, expType: DataType = CalcIntType) =
+ runTest(src, Always(CalcIntType), expType, ALL_LANGS.map((langObj) => langObj -> expOut).toMap)
- def everybodyExcept(src: String, commonExpOut: String, rm: ResultMap, expType: DataType = CalcIntType): TestSpec = {
- (src, Always(CalcIntType), expType, ALL_LANGS.map((langObj) =>
+ def everybodyExcept(src: String, commonExpOut: String, rm: ResultMap, expType: DataType = CalcIntType) =
+ runTest(src, Always(CalcIntType), expType, ALL_LANGS.map((langObj) =>
langObj -> rm.getOrElse(langObj, commonExpOut)
).toMap)
- }
}
diff --git a/project/build.properties b/project/build.properties
index d638b4f34..e98ac44be 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version = 0.13.8
\ No newline at end of file
+sbt.version = 1.1.0-RC4
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 2b65a24e6..322ef8094 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,5 +1,5 @@
logLevel := Level.Warn
-addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.2.0-M8")
-addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1")
-addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.14")
+addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2")
+addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0")
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.21")
diff --git a/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala
index b15b468fc..660ba8522 100644
--- a/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala
@@ -1,19 +1,21 @@
package io.kaitai.struct
import io.kaitai.struct.CompileLog.FileSuccess
-import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype._
+import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components.{LanguageCompiler, LanguageCompilerStatic}
import scala.collection.mutable.ListBuffer
class ClassCompiler(
+ classSpecs: ClassSpecs,
val topClass: ClassSpec,
config: RuntimeConfig,
langObj: LanguageCompilerStatic
) extends AbstractCompiler {
- val provider = new ClassTypeProvider(topClass)
+ val provider = new ClassTypeProvider(classSpecs, topClass)
val topClassName = topClass.name
val lang: LanguageCompiler = langObj.getCompiler(provider, config)
@@ -36,16 +38,22 @@ class ClassCompiler(
)
}
+ /**
+ * Generates code for one full class using a given [[ClassSpec]].
+ * @param curClass current class to generate code for
+ */
def compileClass(curClass: ClassSpec): Unit = {
provider.nowClass = curClass
- if (!curClass.doc.isEmpty)
- lang.classDoc(curClass.name, curClass.doc)
+ if (!lang.innerDocstrings)
+ compileClassDoc(curClass)
lang.classHeader(curClass.name)
+ if (lang.innerDocstrings)
+ compileClassDoc(curClass)
val extraAttrs = ListBuffer[AttrSpec]()
extraAttrs += AttrSpec(List(), RootIdentifier, UserTypeInstream(topClassName, None))
- extraAttrs += AttrSpec(List(), ParentIdentifier, UserTypeInstream(curClass.parentTypeName, None))
+ extraAttrs += AttrSpec(List(), ParentIdentifier, curClass.parentType)
// Forward declarations for recursive types
curClass.types.foreach { case (typeName, _) => lang.classForwardDeclaration(List(typeName)) }
@@ -56,12 +64,69 @@ class ClassCompiler(
if (lang.debug)
lang.debugClassSequence(curClass.seq)
- lang.classConstructorHeader(curClass.name, curClass.parentTypeName, topClassName)
+ // Constructor
+ compileConstructor(curClass)
+
+ // Read method(s)
+ compileEagerRead(curClass.seq, extraAttrs, curClass.meta.endian)
+
+ // Destructor
+ compileDestructor(curClass)
+
+ // Recursive types
+ if (lang.innerClasses) {
+ compileSubclasses(curClass)
+
+ provider.nowClass = curClass
+ }
+
+ compileInstances(curClass, extraAttrs)
+
+ // Attributes declarations and readers
+ val allAttrs: List[MemberSpec] = curClass.seq ++ curClass.params ++ extraAttrs
+ compileAttrDeclarations(allAttrs)
+ compileAttrReaders(allAttrs)
+
+ lang.classFooter(curClass.name)
+
+ if (!lang.innerClasses)
+ compileSubclasses(curClass)
+
+ if (!lang.innerEnums)
+ compileEnums(curClass)
+ }
+
+ /**
+ * Compiles constructor for a given class. Generally, it should:
+ *
+ * * store passed parameters, io/root/parent/endianness if needed
+ * * initialize everything
+ * * invoke _read() method, if applicable
+ *
+ * @param curClass current class to generate code for
+ */
+ def compileConstructor(curClass: ClassSpec) = {
+ lang.classConstructorHeader(
+ curClass.name,
+ curClass.parentType,
+ topClassName,
+ curClass.meta.endian.contains(InheritedEndian),
+ curClass.params
+ )
curClass.instances.foreach { case (instName, _) => lang.instanceClear(instName) }
- compileSeq(curClass.seq, extraAttrs)
+ if (!lang.debug)
+ lang.runRead()
lang.classConstructorFooter
+ }
- lang.classDestructorHeader(curClass.name, curClass.parentTypeName, topClassName)
+ /**
+ * Compiles destructor for a given class. It should clean up everything
+ * (i.e. every applicable allocated seq / instance attribute variables, and
+ * any extra attribute variables, if they were used).
+ * @param curClass current class to generate code for
+ */
+ def compileDestructor(curClass: ClassSpec) = {
+ lang.classDestructorHeader(curClass.name, curClass.parentType, topClassName)
curClass.seq.foreach((attr) => lang.attrDestructor(attr, attr.id))
curClass.instances.foreach { case (id, instSpec) =>
instSpec match {
@@ -70,40 +135,95 @@ class ClassCompiler(
}
}
lang.classDestructorFooter
+ }
- // Recursive types
- if (lang.innerClasses) {
- compileSubclasses(curClass)
+ def compileAttrDeclarations(attrs: List[MemberSpec]): Unit = {
+ attrs.foreach { (attr) =>
+ val isNullable = if (lang.switchBytesOnlyAsRaw) {
+ attr.isNullableSwitchRaw
+ } else {
+ attr.isNullable
+ }
+ lang.attributeDeclaration(attr.id, attr.dataTypeComposite, isNullable)
+ }
+ }
- provider.nowClass = curClass
+ /**
+ * Iterates over a given list of attributes and generates attribute
+ * readers (AKA getters) for each of them.
+ * @param attrs attribute list to traverse
+ */
+ def compileAttrReaders(attrs: List[MemberSpec]): Unit =
+ attrs.foreach { (attr) =>
+ // FIXME: Python should have some form of attribute docs too
+ if (!attr.doc.isEmpty && !lang.innerDocstrings)
+ lang.attributeDoc(attr.id, attr.doc)
+ val isNullable = if (lang.switchBytesOnlyAsRaw) {
+ attr.isNullableSwitchRaw
+ } else {
+ attr.isNullable
+ }
+ lang.attributeReader(attr.id, attr.dataTypeComposite, isNullable)
}
- curClass.instances.foreach { case (instName, instSpec) => compileInstance(curClass.name, instName, instSpec, extraAttrs) }
+ def compileEagerRead(seq: List[AttrSpec], extraAttrs: ListBuffer[AttrSpec], endian: Option[Endianness]): Unit = {
+ endian match {
+ case None | Some(_: FixedEndian) =>
+ compileSeqProc(seq, extraAttrs, None)
+ case Some(ce: CalcEndian) =>
+ lang.readHeader(None, false)
+ compileCalcEndian(ce)
+ lang.runReadCalc()
+ lang.readFooter()
- // Attributes declarations and readers
- (curClass.seq ++ extraAttrs).foreach((attr) => lang.attributeDeclaration(attr.id, attr.dataTypeComposite, attr.cond))
- (curClass.seq ++ extraAttrs).foreach { (attr) =>
- if (!attr.doc.isEmpty)
- lang.attributeDoc(attr.id, attr.doc)
- lang.attributeReader(attr.id, attr.dataTypeComposite, attr.cond)
+ compileSeqProc(seq, extraAttrs, Some(LittleEndian))
+ compileSeqProc(seq, extraAttrs, Some(BigEndian))
+ case Some(InheritedEndian) =>
+ lang.readHeader(None, false)
+ lang.runReadCalc()
+ lang.readFooter()
+
+ compileSeqProc(seq, extraAttrs, Some(LittleEndian))
+ compileSeqProc(seq, extraAttrs, Some(BigEndian))
}
+ }
- lang.classFooter(curClass.name)
+ val IS_LE_ID = SpecialIdentifier("_is_le")
- if (!lang.innerClasses)
- compileSubclasses(curClass)
+ def compileCalcEndian(ce: CalcEndian): Unit = {
+ def renderProc(result: FixedEndian): Unit = {
+ val v = Ast.expr.Bool(result == LittleEndian)
+ lang.instanceCalculate(IS_LE_ID, CalcBooleanType, v)
+ }
- if (!lang.innerEnums)
- compileEnums(curClass)
+ lang.switchCases[FixedEndian](IS_LE_ID, ce.on, ce.cases, renderProc, renderProc)
+ }
+
+ /**
+ * Compiles seq reading method (complete with header and footer).
+ * @param seq sequence of attributes
+ * @param extraAttrs extra attributes to be allocated
+ * @param defEndian default endianness
+ */
+ def compileSeqProc(seq: List[AttrSpec], extraAttrs: ListBuffer[AttrSpec], defEndian: Option[FixedEndian]) = {
+ lang.readHeader(defEndian, seq.isEmpty)
+ compileSeq(seq, extraAttrs, defEndian)
+ lang.readFooter()
}
- def compileSeq(seq: List[AttrSpec], extraAttrs: ListBuffer[AttrSpec]) = {
+ /**
+ * Compiles seq reading method body (only reading statements).
+ * @param seq sequence of attributes
+ * @param extraAttrs extra attributes to be allocated
+ * @param defEndian default endianness
+ */
+ def compileSeq(seq: List[AttrSpec], extraAttrs: ListBuffer[AttrSpec], defEndian: Option[FixedEndian]) = {
var wasUnaligned = false
seq.foreach { (attr) =>
val nowUnaligned = isUnalignedBits(attr.dataType)
if (wasUnaligned && !nowUnaligned)
lang.alignToByte(lang.normalIO)
- lang.attrParse(attr, attr.id, extraAttrs)
+ lang.attrParse(attr, attr.id, extraAttrs, defEndian)
wasUnaligned = nowUnaligned
}
}
@@ -111,23 +231,30 @@ class ClassCompiler(
def compileEnums(curClass: ClassSpec): Unit =
curClass.enums.foreach { case(_, enumColl) => compileEnum(curClass, enumColl) }
+ /**
+ * Compile subclasses for a given class.
+ * @param curClass current class to generate code for
+ */
def compileSubclasses(curClass: ClassSpec): Unit =
curClass.types.foreach { case (_, intClass) => compileClass(intClass) }
- def compileInstance(className: List[String], instName: InstanceIdentifier, instSpec: InstanceSpec, extraAttrs: ListBuffer[AttrSpec]): Unit = {
+ def compileInstances(curClass: ClassSpec, extraAttrs: ListBuffer[AttrSpec]) = {
+ curClass.instances.foreach { case (instName, instSpec) =>
+ compileInstance(curClass.name, instName, instSpec, extraAttrs, curClass.meta.endian)
+ }
+ }
+
+ def compileInstance(className: List[String], instName: InstanceIdentifier, instSpec: InstanceSpec, extraAttrs: ListBuffer[AttrSpec], endian: Option[Endianness]): Unit = {
// Determine datatype
val dataType = instSpec.dataTypeComposite
- // Declare caching variable
- val condSpec = instSpec match {
- case vis: ValueInstanceSpec => ConditionalSpec(vis.ifExpr, NoRepeat)
- case pis: ParseInstanceSpec => pis.cond
- }
- lang.instanceDeclaration(instName, dataType, condSpec)
+ compileInstanceDeclaration(instName, instSpec)
- if (!instSpec.doc.isEmpty)
- lang.attributeDoc(instName, instSpec.doc)
- lang.instanceHeader(className, instName, dataType)
+ if (!lang.innerDocstrings)
+ compileInstanceDoc(instName, instSpec)
+ lang.instanceHeader(className, instName, dataType, instSpec.isNullable)
+ if (lang.innerDocstrings)
+ compileInstanceDoc(instName, instSpec)
lang.instanceCheckCacheAndReturn(instName)
instSpec match {
@@ -136,7 +263,7 @@ class ClassCompiler(
lang.instanceCalculate(instName, dataType, vi.value)
lang.attrParseIfFooter(vi.ifExpr)
case i: ParseInstanceSpec =>
- lang.attrParse(i, instName, extraAttrs)
+ lang.attrParse(i, instName, extraAttrs, endian)
}
lang.instanceSetCalculated(instName)
@@ -144,9 +271,11 @@ class ClassCompiler(
lang.instanceFooter
}
- def compileEnum(curClass: ClassSpec, enumColl: EnumSpec): Unit = {
+ def compileInstanceDeclaration(instName: InstanceIdentifier, instSpec: InstanceSpec): Unit =
+ lang.instanceDeclaration(instName, instSpec.dataTypeComposite, instSpec.isNullable)
+
+ def compileEnum(curClass: ClassSpec, enumColl: EnumSpec): Unit =
lang.enumDeclaration(curClass.name, enumColl.name.last, enumColl.sortedSeq)
- }
def isUnalignedBits(dt: DataType): Boolean =
dt match {
@@ -154,4 +283,14 @@ class ClassCompiler(
case et: EnumType => isUnalignedBits(et.basedOn)
case _ => false
}
+
+ def compileClassDoc(curClass: ClassSpec) = {
+ if (!curClass.doc.isEmpty)
+ lang.classDoc(curClass.name, curClass.doc)
+ }
+
+ def compileInstanceDoc(instName: Identifier, instSpec: InstanceSpec) {
+ if (!instSpec.doc.isEmpty)
+ lang.attributeDoc(instName, instSpec.doc)
+ }
}
diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala
index be6172e2f..11eda9e07 100644
--- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala
+++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala
@@ -6,7 +6,7 @@ import io.kaitai.struct.format._
import io.kaitai.struct.precompile.{EnumNotFoundError, FieldNotFoundError, TypeNotFoundError, TypeUndecidedError}
import io.kaitai.struct.translators.TypeProvider
-class ClassTypeProvider(topClass: ClassSpec) extends TypeProvider {
+class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends TypeProvider {
var nowClass = topClass
var _currentIteratorType: Option[DataType] = None
@@ -20,30 +20,36 @@ class ClassTypeProvider(topClass: ClassSpec) extends TypeProvider {
override def determineType(inClass: ClassSpec, attrName: String): DataType = {
attrName match {
- case "_root" =>
+ case Identifier.ROOT =>
makeUserType(topClass)
- case "_parent" =>
+ case Identifier.PARENT =>
if (inClass.parentClass == UnknownClassSpec)
- throw new RuntimeException(s"Unable to derive _parent type in ${inClass.name.mkString("::")}")
+ throw new RuntimeException(s"Unable to derive ${Identifier.PARENT} type in ${inClass.name.mkString("::")}")
makeUserType(inClass.parentClass)
- case "_io" =>
+ case Identifier.IO =>
KaitaiStreamType
- case "_" =>
+ case Identifier.ITERATOR =>
currentIteratorType
- case "_on" =>
+ case Identifier.SWITCH_ON =>
currentSwitchType
+ case Identifier.INDEX =>
+ CalcIntType
case _ =>
inClass.seq.foreach { el =>
if (el.id == NamedIdentifier(attrName))
return el.dataTypeComposite
}
+ inClass.params.foreach { el =>
+ if (el.id == NamedIdentifier(attrName))
+ return el.dataType
+ }
inClass.instances.get(InstanceIdentifier(attrName)) match {
case Some(i: ValueInstanceSpec) =>
- i.dataType match {
+ val dt = i.dataType match {
case Some(t) => t
case None => throw new TypeUndecidedError(attrName)
}
- return i.dataType.get
+ return dt
case Some(i: ParseInstanceSpec) => return i.dataTypeComposite
case None => // do nothing
}
@@ -81,18 +87,43 @@ class ClassTypeProvider(topClass: ClassSpec) extends TypeProvider {
override def resolveType(typeName: String): DataType = resolveType(nowClass, typeName)
def resolveType(inClass: ClassSpec, typeName: String): DataType = {
+ if (inClass.name.last == typeName)
+ return makeUserType(inClass)
+
inClass.types.get(typeName) match {
case Some(spec) =>
- val ut = UserTypeInstream(spec.name, None)
- ut.classSpec = Some(spec)
- ut
+ makeUserType(spec)
case None =>
// let's try upper levels of hierarchy
inClass.upClass match {
case Some(upClass) => resolveType(upClass, typeName)
case None =>
- throw new TypeNotFoundError(typeName, nowClass)
+ classSpecs.get(typeName) match {
+ case Some(spec) => makeUserType(spec)
+ case None =>
+ throw new TypeNotFoundError(typeName, nowClass)
+ }
}
}
}
+
+ override def isLazy(attrName: String): Boolean = isLazy(nowClass, attrName)
+
+ def isLazy(inClass: ClassSpec, attrName: String): Boolean = {
+ inClass.seq.foreach { el =>
+ if (el.id == NamedIdentifier(attrName))
+ return false
+ }
+ inClass.params.foreach { el =>
+ if (el.id == NamedIdentifier(attrName))
+ return false
+ }
+ inClass.instances.get(InstanceIdentifier(attrName)) match {
+ case Some(i) =>
+ return true
+ case None =>
+ // do nothing
+ }
+ throw new FieldNotFoundError(attrName, inClass)
+ }
}
diff --git a/shared/src/main/scala/io/kaitai/struct/CompileLog.scala b/shared/src/main/scala/io/kaitai/struct/CompileLog.scala
index b5d8d03f3..134494b1e 100644
--- a/shared/src/main/scala/io/kaitai/struct/CompileLog.scala
+++ b/shared/src/main/scala/io/kaitai/struct/CompileLog.scala
@@ -1,10 +1,18 @@
package io.kaitai.struct
+/**
+ * Namespace for all the objects related to compilation results.
+ */
object CompileLog {
- sealed trait InputEntry extends Jsonable
+ trait CanHasErrors {
+ def hasErrors: Boolean
+ }
+
+ sealed trait InputEntry extends Jsonable with CanHasErrors
case class InputFailure(errors: List[CompileError]) extends InputEntry {
override def toJson: String = JSON.mapToJson(Map("errors" -> errors))
+ override def hasErrors = true
}
case class InputSuccess(
@@ -15,13 +23,17 @@ object CompileLog {
"firstSpecName" -> firstSpecName,
"output" -> output
))
+
+ override def hasErrors: Boolean =
+ output.values.map(_.values.map(_.hasErrors).max).max
}
/** Compilation result of a single [[io.kaitai.struct.format.ClassSpec]] into a single target language. */
- sealed trait SpecEntry extends Jsonable
+ sealed trait SpecEntry extends Jsonable with CanHasErrors
case class SpecFailure(errors: List[CompileError]) extends SpecEntry {
override def toJson: String = JSON.mapToJson(Map("errors" -> errors))
+ override def hasErrors: Boolean = true
}
case class SpecSuccess(
@@ -32,6 +44,7 @@ object CompileLog {
"topLevelName" -> topLevelName,
"files" -> files
))
+ override def hasErrors: Boolean = false
}
case class FileSuccess(
diff --git a/shared/src/main/scala/io/kaitai/struct/GoClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GoClassCompiler.scala
new file mode 100644
index 000000000..0e57e55bb
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/GoClassCompiler.scala
@@ -0,0 +1,98 @@
+package io.kaitai.struct
+
+import io.kaitai.struct.datatype.DataType.{KaitaiStreamType, UserTypeInstream}
+import io.kaitai.struct.datatype.{Endianness, FixedEndian, InheritedEndian}
+import io.kaitai.struct.format._
+import io.kaitai.struct.languages.GoCompiler
+import io.kaitai.struct.languages.components.ExtraAttrs
+
+import scala.collection.mutable.ListBuffer
+
+class GoClassCompiler(
+ classSpecs: ClassSpecs,
+ override val topClass: ClassSpec,
+ config: RuntimeConfig
+) extends ClassCompiler(classSpecs, topClass, config, GoCompiler) {
+
+ override def compileClass(curClass: ClassSpec): Unit = {
+ provider.nowClass = curClass
+
+ val extraAttrs = ListBuffer[AttrSpec]()
+ extraAttrs += AttrSpec(List(), IoIdentifier, KaitaiStreamType)
+ extraAttrs += AttrSpec(List(), RootIdentifier, UserTypeInstream(topClassName, None))
+ extraAttrs += AttrSpec(List(), ParentIdentifier, curClass.parentType)
+
+ extraAttrs ++= getExtraAttrs(curClass)
+
+ if (!curClass.doc.isEmpty)
+ lang.classDoc(curClass.name, curClass.doc)
+
+ // Basic struct declaration
+ lang.classHeader(curClass.name)
+ compileAttrDeclarations(curClass.seq ++ extraAttrs)
+ curClass.instances.foreach { case (instName, instSpec) =>
+ compileInstanceDeclaration(instName, instSpec)
+ }
+ lang.classFooter(curClass.name)
+
+ // Constructor = Read() function
+ compileReadFunction(curClass, extraAttrs)
+
+ compileInstances(curClass, extraAttrs)
+
+ compileAttrReaders(curClass.seq ++ extraAttrs)
+
+ compileEnums(curClass)
+
+ // Recursive types
+ compileSubclasses(curClass)
+ }
+
+ def compileReadFunction(curClass: ClassSpec, extraAttrs: ListBuffer[AttrSpec]) = {
+ lang.classConstructorHeader(
+ curClass.name,
+ curClass.parentType,
+ topClassName,
+ curClass.meta.endian.contains(InheritedEndian),
+ curClass.params
+ )
+ // FIXME
+ val defEndian = curClass.meta.endian match {
+ case Some(fe: FixedEndian) => Some(fe)
+ case _ => None
+ }
+ compileSeq(curClass.seq, extraAttrs, defEndian)
+ lang.classConstructorFooter
+ }
+
+ override def compileInstance(className: List[String], instName: InstanceIdentifier, instSpec: InstanceSpec, extraAttrs: ListBuffer[AttrSpec], endian: Option[Endianness]): Unit = {
+ // FIXME: support calculated endianness
+
+ // Determine datatype
+ val dataType = instSpec.dataTypeComposite
+
+ if (!instSpec.doc.isEmpty)
+ lang.attributeDoc(instName, instSpec.doc)
+ lang.instanceHeader(className, instName, dataType, instSpec.isNullable)
+ lang.instanceCheckCacheAndReturn(instName)
+
+ instSpec match {
+ case vi: ValueInstanceSpec =>
+ lang.attrParseIfHeader(instName, vi.ifExpr)
+ lang.instanceCalculate(instName, dataType, vi.value)
+ lang.attrParseIfFooter(vi.ifExpr)
+ case i: ParseInstanceSpec =>
+ lang.attrParse(i, instName, extraAttrs, None) // FIXME
+ }
+
+ lang.instanceSetCalculated(instName)
+ lang.instanceReturn(instName)
+ lang.instanceFooter
+ }
+
+ def getExtraAttrs(curClass: ClassSpec): List[AttrSpec] = {
+ curClass.seq.foldLeft(List[AttrSpec]())(
+ (attrs, attr) => attrs ++ ExtraAttrs.forAttr(attr)
+ )
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala
index 686f0e902..03d8ef1ce 100644
--- a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala
@@ -1,22 +1,23 @@
package io.kaitai.struct
-import io.kaitai.struct.exprlang.Ast
-import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components.{LanguageCompiler, LanguageCompilerStatic}
-import io.kaitai.struct.translators.{BaseTranslator, RubyTranslator, TypeProvider}
+import io.kaitai.struct.precompile.CalculateSeqSizes
+import io.kaitai.struct.translators.RubyTranslator
import scala.collection.mutable.ListBuffer
-class GraphvizClassCompiler(topClass: ClassSpec) extends AbstractCompiler {
+class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends AbstractCompiler {
import GraphvizClassCompiler._
val out = new StringLanguageOutputWriter(indent)
- val provider = new ClassTypeProvider(topClass)
- val translator = getTranslator(provider, RuntimeConfig())
+ val provider = new ClassTypeProvider(classSpecs, topClass)
+ val translator = new RubyTranslator(provider)
val links = ListBuffer[(String, String, String)]()
val extraClusterLines = new StringLanguageOutputWriter(indent)
@@ -86,19 +87,16 @@ class GraphvizClassCompiler(topClass: ClassSpec) extends AbstractCompiler {
def compileSeq(className: List[String], curClass: ClassSpec): Unit = {
tableStart(className, "seq")
- var seqPos: Option[Int] = Some(0)
- curClass.seq.foreach { (attr) =>
+
+ CalculateSeqSizes.forEachSeqAttr(curClass, (attr, seqPos, _, _) => {
attr.id match {
case NamedIdentifier(name) =>
tableRow(className, seqPosToStr(seqPos), attr, name)
-
- val size = dataTypeBitsSize(attr.dataType)
- seqPos = (seqPos, size) match {
- case (Some(pos), Some(siz)) => Some(pos + siz)
- case _ => None
- }
+ case NumberedIdentifier(n) =>
+ tableRow(className, seqPosToStr(seqPos), attr, s"_${NumberedIdentifier.TEMPLATE}$n")
}
- }
+ })
+
tableEnd
}
@@ -254,20 +252,23 @@ class GraphvizClassCompiler(topClass: ClassSpec) extends AbstractCompiler {
*/
def dataTypeSizeAsString(dataType: DataType, attrName: String): String = {
dataType match {
- case _: Int1Type => "1"
- case IntMultiType(_, width, _) => width.width.toString
- case FloatMultiType(width, _) => width.width.toString
- case FixedBytesType(contents, _) => contents.length.toString
case _: BytesEosType => END_OF_STREAM
case blt: BytesLimitType => expressionSize(blt.size, attrName)
- case _: BytesTerminatedType => UNKNOWN
case StrFromBytesType(basedOn, _) => dataTypeSizeAsString(basedOn, attrName)
case utb: UserTypeFromBytes => dataTypeSizeAsString(utb.bytes, attrName)
- case UserTypeInstream(_, _) => UNKNOWN
case EnumType(_, basedOn) => dataTypeSizeAsString(basedOn, attrName)
- case _: SwitchType => UNKNOWN
- case BitsType1 => "1b"
- case BitsType(width) => s"${width}b"
+ case _ =>
+ CalculateSeqSizes.dataTypeBitsSize(dataType) match {
+ case FixedSized(n) =>
+ if (n % 8 == 0) {
+ s"${n / 8}"
+ } else {
+ s"${n}b"
+ }
+ case DynamicSized => UNKNOWN
+ case NotCalculatedSized | StartedCalculationSized =>
+ throw new RuntimeException("Should never happen: problems with CalculateSeqSizes")
+ }
}
}
@@ -367,14 +368,29 @@ class GraphvizClassCompiler(topClass: ClassSpec) extends AbstractCompiler {
s"${getGraphvizNode(className, cs, s)}:${s}_type"
def getGraphvizNode(className: List[String], cs: ClassSpec, s: String): String = {
- cs.seq.foreach((attr) =>
- attr.id match {
+ cs.seq.foreach { (attr) =>
+ val name = attr.id match {
case NamedIdentifier(attrName) =>
- if (attrName == s) {
- return s"${type2class(className)}__seq"
- }
+ attrName
+ case NumberedIdentifier(n) =>
+ s"_${NumberedIdentifier.TEMPLATE}$n"
}
- )
+ if (name == s) {
+ return s"${type2class(className)}__seq"
+ }
+ }
+
+ cs.params.foreach { (attr) =>
+ val name = attr.id match {
+ case NamedIdentifier(attrName) =>
+ attrName
+ case NumberedIdentifier(n) =>
+ s"_${NumberedIdentifier.TEMPLATE}$n"
+ }
+ if (name == s) {
+ return s"${type2class(className)}__params"
+ }
+ }
cs.instances.get(InstanceIdentifier(s)).foreach((inst) =>
return s"${type2class(className)}__inst__$s"
@@ -388,8 +404,6 @@ class GraphvizClassCompiler(topClass: ClassSpec) extends AbstractCompiler {
}
object GraphvizClassCompiler extends LanguageCompilerStatic {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig): BaseTranslator = new RubyTranslator(tp)
-
// FIXME: Unused, should be probably separated from LanguageCompilerStatic
override def getCompiler(
tp: ClassTypeProvider,
@@ -399,46 +413,9 @@ object GraphvizClassCompiler extends LanguageCompilerStatic {
def type2class(name: List[String]) = name.last
def type2display(name: List[String]) = name.map(Utils.upperCamelCase).mkString("::")
- /**
- * Determines how many bits occupies given data type.
- *
- * @param dataType data type to analyze
- * @return number of bits or None, if it's impossible to determine a priori
- */
- def dataTypeBitsSize(dataType: DataType): Option[Int] = {
- dataType match {
- case BitsType1 => Some(1)
- case BitsType(width) => Some(width)
- case EnumType(_, basedOn) => dataTypeBitsSize(basedOn)
- case _ => dataTypeByteSize(dataType).map((byteSize) => byteSize * 8)
- }
- }
-
- /**
- * Determines how many bytes occupies a given data type.
- *
- * @param dataType data type to analyze
- * @return number of bytes or None, if it's impossible to determine a priori
- */
- def dataTypeByteSize(dataType: DataType): Option[Int] = {
- dataType match {
- case _: Int1Type => Some(1)
- case IntMultiType(_, width, _) => Some(width.width)
- case FixedBytesType(contents, _) => Some(contents.length)
- case FloatMultiType(width, _) => Some(width.width)
- case _: BytesEosType => None
- case blt: BytesLimitType => evaluateIntLiteral(blt.size)
- case _: BytesTerminatedType => None
- case StrFromBytesType(basedOn, _) => dataTypeByteSize(basedOn)
- case utb: UserTypeFromBytes => dataTypeByteSize(utb.bytes)
- case UserTypeInstream(_, _) => None
- case _: SwitchType => None
- }
- }
-
def dataTypeName(dataType: DataType): String = {
dataType match {
- case rt: ReadableType => rt.apiCall
+ case rt: ReadableType => rt.apiCall(None) // FIXME
case ut: UserType => type2display(ut.name)
case FixedBytesType(contents, _) => contents.map(_.formatted("%02X")).mkString(" ")
case BytesTerminatedType(terminator, include, consume, eosError, _) =>
@@ -464,20 +441,6 @@ object GraphvizClassCompiler extends LanguageCompilerStatic {
}
}
- /**
- * Evaluates the expression, if possible to get the result without introduction
- * of any variables or anything.
- *
- * @param expr expression to evaluate
- * @return integer result or None
- */
- def evaluateIntLiteral(expr: Ast.expr): Option[Int] = {
- expr match {
- case Ast.expr.IntNum(x) => Some(x.toInt)
- case _ => None
- }
- }
-
def htmlEscape(s: String): String = {
s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)
}
diff --git a/shared/src/main/scala/io/kaitai/struct/ImportList.scala b/shared/src/main/scala/io/kaitai/struct/ImportList.scala
new file mode 100644
index 000000000..b619100d1
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/ImportList.scala
@@ -0,0 +1,13 @@
+package io.kaitai.struct
+
+import scala.collection.mutable.ListBuffer
+
+/**
+ * Manages imports/includes/requires/etc lists used for particular compilation
+ * unit, makes sure they are unique.
+ */
+class ImportList {
+ private val list = ListBuffer[String]()
+ def add(s: String) = Utils.addUniqueAttr(list, s)
+ def toList: List[String] = list.toList
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/JSON.scala b/shared/src/main/scala/io/kaitai/struct/JSON.scala
index 4e2be6070..adb3fc04c 100644
--- a/shared/src/main/scala/io/kaitai/struct/JSON.scala
+++ b/shared/src/main/scala/io/kaitai/struct/JSON.scala
@@ -1,13 +1,17 @@
package io.kaitai.struct
+import io.kaitai.struct.translators.CommonLiterals
+
+/** Common trait for all objects that can be serialized as JSON. */
trait Jsonable {
+ /** Serialize current state of the object into JSON string. */
def toJson: String
}
/**
* Ultra-minimalistic JSON strings generator from arbitrary Scala objects.
*/
-object JSON {
+object JSON extends CommonLiterals {
/**
* Converts an arbitrary Scala object to JSON string representation.
* @param obj object to convert
@@ -23,9 +27,8 @@ object JSON {
}
}
- // FIXME: do proper string handling
def stringToJson(str: String): String =
- "\"%s\"".format(str)
+ doStringLiteral(str)
def listToJson(obj: List[_]): String =
"[" + obj.map((x) => stringify(x)).mkString(",") + "]"
diff --git a/shared/src/main/scala/io/kaitai/struct/Log.scala b/shared/src/main/scala/io/kaitai/struct/Log.scala
index 13ad6689b..1aeb161f6 100644
--- a/shared/src/main/scala/io/kaitai/struct/Log.scala
+++ b/shared/src/main/scala/io/kaitai/struct/Log.scala
@@ -25,6 +25,8 @@ object Log {
"value",
"parent",
"type_resolve",
+ "type_valid",
+ "seq_sizes",
"import"
)
@@ -32,6 +34,8 @@ object Log {
var typeProcValue: Logger = NullLogger
var typeProcParent: Logger = NullLogger
var typeResolve: Logger = NullLogger
+ var typeValid: Logger = NullLogger
+ var seqSizes: Logger = NullLogger
var importOps: Logger = NullLogger
def initFromVerboseFlag(subsystems: Seq[String]): Unit = {
@@ -43,6 +47,8 @@ object Log {
case "value" => typeProcValue = ConsoleLogger
case "parent" => typeProcParent = ConsoleLogger
case "type_resolve" => typeResolve = ConsoleLogger
+ case "type_valid" => typeValid = ConsoleLogger
+ case "seq_sizes" => seqSizes = ConsoleLogger
case "import" => importOps = ConsoleLogger
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/Main.scala b/shared/src/main/scala/io/kaitai/struct/Main.scala
index c43a0e14c..420775f38 100644
--- a/shared/src/main/scala/io/kaitai/struct/Main.scala
+++ b/shared/src/main/scala/io/kaitai/struct/Main.scala
@@ -1,6 +1,7 @@
package io.kaitai.struct
import io.kaitai.struct.format.{ClassSpec, ClassSpecs, GenericStructClassSpec}
+import io.kaitai.struct.languages.GoCompiler
import io.kaitai.struct.languages.components.LanguageCompilerStatic
import io.kaitai.struct.precompile._
@@ -20,7 +21,7 @@ object Main {
* into it and modifying classes itself by precompilation step
*/
def importAndPrecompile(specs: ClassSpecs, config: RuntimeConfig): Future[Unit] = {
- new LoadImports(specs).processClass(specs.firstSpec).map { (allSpecs) =>
+ new LoadImports(specs).processClass(specs.firstSpec, LoadImports.BasePath).map { (allSpecs) =>
Log.importOps.info(() => s"imports done, got: ${specs.keys} (async=$allSpecs)")
specs.foreach { case (_, classSpec) =>
@@ -31,11 +32,12 @@ object Main {
def precompile(classSpecs: ClassSpecs, topClass: ClassSpec, config: RuntimeConfig): Unit = {
classSpecs.foreach { case (_, curClass) => MarkupClassNames.markupClassNames(curClass) }
- val opaqueTypes = topClass.meta.get.opaqueTypes.getOrElse(config.opaqueTypes)
+ val opaqueTypes = topClass.meta.opaqueTypes.getOrElse(config.opaqueTypes)
new ResolveTypes(classSpecs, opaqueTypes).run()
- classSpecs.foreach { case (_, curClass) => ParentTypes.markup(curClass) }
+ new ParentTypes(classSpecs).run()
new SpecsValueTypeDerive(classSpecs).run()
- new TypeValidator(topClass).run()
+ new TypeValidator(classSpecs, topClass).run()
+ new CalculateSeqSizes(classSpecs).run()
topClass.parentClass = GenericStructClassSpec
}
@@ -43,19 +45,22 @@ object Main {
/**
* Compiles a single [[ClassSpec]] into a single target language using
* provided configuration.
+ * @param specs bundle of class specifications (used to search to references there)
* @param spec class specification to compile
* @param lang specifies which language compiler will be used
* @param conf runtime compiler configuration
* @return a container that contains all compiled files and results
*/
- def compile(spec: ClassSpec, lang: LanguageCompilerStatic, conf: RuntimeConfig): CompileLog.SpecSuccess = {
+ def compile(specs: ClassSpecs, spec: ClassSpec, lang: LanguageCompilerStatic, conf: RuntimeConfig): CompileLog.SpecSuccess = {
val config = updateConfig(conf, spec)
val cc = lang match {
case GraphvizClassCompiler =>
- new GraphvizClassCompiler(spec)
+ new GraphvizClassCompiler(specs, spec)
+ case GoCompiler =>
+ new GoClassCompiler(specs, spec, config)
case _ =>
- new ClassCompiler(spec, config, lang)
+ new ClassCompiler(specs, spec, config, lang)
}
cc.compile
}
@@ -68,7 +73,7 @@ object Main {
* @return updated runtime configuration with applied enforcements
*/
private def updateConfig(config: RuntimeConfig, topClass: ClassSpec): RuntimeConfig = {
- if (topClass.meta.get.forceDebug) {
+ if (topClass.meta.forceDebug) {
config.copy(debug = true)
} else {
config
diff --git a/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala b/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala
index 5a782c02d..aa7a56abf 100644
--- a/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala
+++ b/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala
@@ -3,7 +3,10 @@ package io.kaitai.struct
case class RuntimeConfig(
debug: Boolean = false,
opaqueTypes: Boolean = false,
+ goPackage: String = "",
javaPackage: String = "",
+ javaFromFileClass: String = "io.kaitai.struct.ByteBufferKaitaiStream",
dotNetNamespace: String = "Kaitai",
- phpNamespace: String = ""
+ phpNamespace: String = "",
+ pythonPackage: String = ""
)
diff --git a/shared/src/main/scala/io/kaitai/struct/Utils.scala b/shared/src/main/scala/io/kaitai/struct/Utils.scala
index eab0ca8f5..a14816f37 100644
--- a/shared/src/main/scala/io/kaitai/struct/Utils.scala
+++ b/shared/src/main/scala/io/kaitai/struct/Utils.scala
@@ -64,6 +64,18 @@ object Utils {
}
}
+ /**
+ * Joins collection together to make a single string. Makes extra exception for empty
+ * collections (not like [[TraversableOnce]] `mkString`).
+ * @param start the starting string.
+ * @param sep the separator string.
+ * @param end the ending string.
+ * @return If the collection is empty, returns empty string, otherwise returns `start`,
+ * then elements of the collection, every pair separated with `sep`, then `end`.
+ */
+ def join[T](coll: TraversableOnce[T], start: String, sep: String, end: String): String =
+ if (coll.isEmpty) "" else coll.mkString(start, sep, end)
+
/**
* Converts byte array (Seq[Byte]) into hex-escaped C-style literal characters
* (i.e. like \xFF).
diff --git a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala
index 85b2dd85d..78e111043 100644
--- a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala
+++ b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala
@@ -1,7 +1,8 @@
package io.kaitai.struct.datatype
-import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.exprlang.{Ast, Expressions}
import io.kaitai.struct.format._
+import io.kaitai.struct.translators.TypeDetector
sealed trait DataType
@@ -21,7 +22,7 @@ object DataType {
* parameterless KaitaiStream API call.
*/
trait ReadableType extends DataType {
- def apiCall: String
+ def apiCall(defEndian: Option[FixedEndian]): String
}
abstract class NumericType extends DataType
@@ -30,12 +31,13 @@ object DataType {
abstract class IntType extends NumericType
case object CalcIntType extends IntType
case class Int1Type(signed: Boolean) extends IntType with ReadableType {
- override def apiCall: String = if (signed) "s1" else "u1"
+ override def apiCall(defEndian: Option[FixedEndian]): String = if (signed) "s1" else "u1"
}
- case class IntMultiType(signed: Boolean, width: IntWidth, endian: Endianness) extends IntType with ReadableType {
- override def apiCall: String = {
+ case class IntMultiType(signed: Boolean, width: IntWidth, endian: Option[FixedEndian]) extends IntType with ReadableType {
+ override def apiCall(defEndian: Option[FixedEndian]): String = {
val ch1 = if (signed) 's' else 'u'
- s"$ch1${width.width}${endian.toString}"
+ val finalEnd = endian.orElse(defEndian)
+ s"$ch1${width.width}${finalEnd.map(_.toSuffix).getOrElse("")}"
}
}
case object BitsType1 extends BooleanType
@@ -43,9 +45,10 @@ object DataType {
abstract class FloatType extends NumericType
case object CalcFloatType extends FloatType
- case class FloatMultiType(width: IntWidth, endian: Endianness) extends FloatType with ReadableType {
- override def apiCall: String = {
- s"f${width.width}${endian.toString}"
+ case class FloatMultiType(width: IntWidth, endian: Option[FixedEndian]) extends FloatType with ReadableType {
+ override def apiCall(defEndian: Option[FixedEndian]): String = {
+ val finalEnd = endian.orElse(defEndian)
+ s"f${width.width}${finalEnd.map(_.toSuffix).getOrElse("")}"
}
}
@@ -86,23 +89,29 @@ object DataType {
case object CalcBooleanType extends BooleanType
case class ArrayType(elType: DataType) extends DataType
- abstract class UserType(val name: List[String], val forcedParent: Option[Ast.expr]) extends DataType {
+ abstract class UserType(
+ val name: List[String],
+ val forcedParent: Option[Ast.expr],
+ var args: Seq[Ast.expr]
+ ) extends DataType {
var classSpec: Option[ClassSpec] = None
def isOpaque = {
val cs = classSpec.get
- cs.isTopLevel || (cs.meta match {
- case None => false
- case Some(meta) => meta.isOpaque
- })
+ cs.isTopLevel || cs.meta.isOpaque
}
}
- case class UserTypeInstream(_name: List[String], _forcedParent: Option[Ast.expr]) extends UserType(_name, _forcedParent)
+ case class UserTypeInstream(
+ _name: List[String],
+ _forcedParent: Option[Ast.expr],
+ _args: Seq[Ast.expr] = Seq()
+ ) extends UserType(_name, _forcedParent, _args)
case class UserTypeFromBytes(
_name: List[String],
_forcedParent: Option[Ast.expr],
+ _args: Seq[Ast.expr] = Seq(),
bytes: BytesType,
override val process: Option[ProcessExpr]
- ) extends UserType(_name, _forcedParent) with Processing
+ ) extends UserType(_name, _forcedParent, _args) with Processing
val USER_TYPE_NO_PARENT = Ast.expr.Bool(false)
@@ -114,23 +123,114 @@ object DataType {
var enumSpec: Option[EnumSpec] = None
}
- case class SwitchType(on: Ast.expr, cases: Map[Ast.expr, DataType]) extends DataType
+ case class SwitchType(on: Ast.expr, cases: Map[Ast.expr, DataType]) extends DataType {
+ def combinedType: DataType = TypeDetector.combineTypes(cases.values)
+
+ /**
+ * @return True if this switch type includes an "else" case
+ */
+ def hasElseCase: Boolean = cases.contains(SwitchType.ELSE_CONST)
+
+ /**
+ * If a switch type has no else statement, it will turn out to be null
+ * every case would fail, so it's nullable.
+ * @return True if this switch type is nullable for regular languages.
+ */
+ def isNullable: Boolean = !hasElseCase
+
+ /**
+ * @return True if this switch type is nullable in a raw switch bytes languages (C++).
+ */
+ def isNullableSwitchRaw: Boolean = {
+ val elseCase = cases.get(SwitchType.ELSE_CONST)
+ elseCase match {
+ case Some(_: BytesType) =>
+ // else case with bytes type, nullable for C++-like languages
+ true
+ case Some(x) =>
+ // else case with any user type, non-nullable
+ false
+ case None =>
+ // no else case, even raw bytes, definitely nullable
+ true
+ }
+ }
+
+ def hasSize: Boolean =
+ cases.values.exists((t) =>
+ t.isInstanceOf[UserTypeFromBytes] || t.isInstanceOf[BytesType]
+ )
+ }
object SwitchType {
/**
* Constant that would be used for "else" case in SwitchType case class "cases" map.
*/
val ELSE_CONST = Ast.expr.Name(Ast.identifier("_"))
+
+ val LEGAL_KEYS_SWITCH = Set(
+ "switch-on",
+ "cases"
+ )
+
+ def fromYaml1(switchSpec: Map[String, Any], path: List[String]): (String, Map[String, String]) = {
+ val _on = ParseUtils.getValueStr(switchSpec, "switch-on", path)
+ val _cases: Map[String, String] = switchSpec.get("cases") match {
+ case None => Map()
+ case Some(x) => ParseUtils.asMapStrStr(x, path ++ List("cases"))
+ }
+
+ ParseUtils.ensureLegalKeys(switchSpec, LEGAL_KEYS_SWITCH, path)
+ (_on, _cases)
+ }
+
+ def fromYaml(
+ switchSpec: Map[String, Any],
+ path: List[String],
+ metaDef: MetaSpec,
+ arg: YamlAttrArgs
+ ): SwitchType = {
+ val (_on, _cases) = fromYaml1(switchSpec, path)
+
+ val on = Expressions.parse(_on)
+ val cases: Map[Ast.expr, DataType] = _cases.map { case (condition, typeName) =>
+ Expressions.parse(condition) -> DataType.fromYaml(
+ Some(typeName), path ++ List("cases"), metaDef,
+ arg
+ )
+ }
+
+ // If we have size defined, and we don't have any "else" case already, add
+ // an implicit "else" case that will at least catch everything else as
+ // "untyped" byte array of given size
+ val addCases: Map[Ast.expr, DataType] = if (cases.contains(ELSE_CONST)) {
+ Map()
+ } else {
+ (arg.size, arg.sizeEos) match {
+ case (Some(sizeValue), false) =>
+ Map(SwitchType.ELSE_CONST -> BytesLimitType(sizeValue, None, false, None, arg.process))
+ case (None, true) =>
+ Map(SwitchType.ELSE_CONST -> BytesEosType(None, false, None, arg.process))
+ case (None, false) =>
+ Map()
+ case (Some(_), true) =>
+ throw new YAMLParseException("can't have both `size` and `size-eos` defined", path)
+ }
+ }
+
+ SwitchType(on, cases ++ addCases)
+ }
}
private val ReIntType = """([us])(2|4|8)(le|be)?""".r
private val ReFloatType = """f(4|8)(le|be)?""".r
- private val ReBitType= """b(\d+)""".r
+ private val ReBitType = """b(\d+)""".r
+ private val ReUserTypeWithArgs = """(.+)\((.*)\)""".r
def fromYaml(
dto: Option[String],
path: List[String],
- metaDef: MetaDefaults,
+ metaDef: MetaSpec,
arg: YamlAttrArgs
): DataType = {
val r = dto match {
@@ -185,14 +285,18 @@ object DataType {
val bat = arg2.getByteArrayType(path)
StrFromBytesType(bat, enc)
case _ =>
- val dtl = classNameToList(dt)
+ val (arglessType, args) = dt match {
+ case ReUserTypeWithArgs(typeStr, argsStr) => (typeStr, Expressions.parseList(argsStr))
+ case _ => (dt, List())
+ }
+ val dtl = classNameToList(arglessType)
if (arg.size.isEmpty && !arg.sizeEos && arg.terminator.isEmpty) {
if (arg.process.isDefined)
throw new YAMLParseException(s"user type '$dt': need 'size' / 'size-eos' / 'terminator' if 'process' is used", path)
- UserTypeInstream(dtl, arg.parent)
+ UserTypeInstream(dtl, arg.parent, args)
} else {
val bat = arg.getByteArrayType(path)
- UserTypeFromBytes(dtl, arg.parent, bat, arg.process)
+ UserTypeFromBytes(dtl, arg.parent, args, bat, arg.process)
}
}
}
@@ -209,7 +313,46 @@ object DataType {
}
}
- def getEncoding(curEncoding: Option[String], metaDef: MetaDefaults, path: List[String]): String = {
+ private val RePureIntType = """([us])(2|4|8)""".r
+ private val RePureFloatType = """f(4|8)""".r
+
+ def pureFromString(dto: Option[String]): DataType = {
+ dto match {
+ case None => CalcBytesType
+ case Some(dt) => dt match {
+ case "u1" => Int1Type(false)
+ case "s1" => Int1Type(true)
+ case RePureIntType(signStr, widthStr) =>
+ IntMultiType(
+ signStr match {
+ case "s" => true
+ case "u" => false
+ },
+ widthStr match {
+ case "2" => Width2
+ case "4" => Width4
+ case "8" => Width8
+ },
+ None
+ )
+ case RePureFloatType(widthStr) =>
+ FloatMultiType(
+ widthStr match {
+ case "4" => Width4
+ case "8" => Width8
+ },
+ None
+ )
+ case "str" => CalcStrType
+ case "bool" => CalcBooleanType
+ case "struct" => KaitaiStructType
+ case "io" => KaitaiStreamType
+ case "any" => AnyType
+ }
+ }
+ }
+
+ def getEncoding(curEncoding: Option[String], metaDef: MetaSpec, path: List[String]): String = {
curEncoding.orElse(metaDef.encoding) match {
case Some(enc) => enc
case None =>
@@ -224,4 +367,4 @@ object DataType {
* @return class name notation as list of components
*/
def classNameToList(s: String): List[String] = s.split("::", -1).toList
-}
\ No newline at end of file
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/datatype/Endianness.scala b/shared/src/main/scala/io/kaitai/struct/datatype/Endianness.scala
index b75150e1f..9db938c3b 100644
--- a/shared/src/main/scala/io/kaitai/struct/datatype/Endianness.scala
+++ b/shared/src/main/scala/io/kaitai/struct/datatype/Endianness.scala
@@ -1,32 +1,67 @@
package io.kaitai.struct.datatype
-import io.kaitai.struct.format.YAMLParseException
+import io.kaitai.struct.datatype.DataType.SwitchType
+import io.kaitai.struct.exprlang.{Ast, Expressions}
+import io.kaitai.struct.format.{ParseUtils, YAMLParseException}
sealed trait Endianness
-case object LittleEndian extends Endianness {
- override def toString = "le"
+
+abstract class FixedEndian extends Endianness {
+ def toSuffix: String
+}
+case object LittleEndian extends FixedEndian {
+ override def toSuffix = "le"
}
-case object BigEndian extends Endianness {
- override def toString = "be"
+case object BigEndian extends FixedEndian {
+ override def toSuffix = "be"
}
+case class CalcEndian(on: Ast.expr, cases: Map[Ast.expr, FixedEndian]) extends Endianness
+
+case object InheritedEndian extends Endianness
+
object Endianness {
- def defaultFromString(s: Option[String], path: List[String]) = s match {
- case None => None
- case Some("be") => Some(BigEndian)
- case Some("le") => Some(LittleEndian)
- case Some(unknown) => throw YAMLParseException.badDictValue(
- Set("be", "le"), unknown, path ++ List("endian")
- )
+ def fromYaml(src: Option[Any], path: List[String]): Option[Endianness] = {
+ src match {
+ case None => None
+ case Some("be") => Some(BigEndian)
+ case Some("le") => Some(LittleEndian)
+ case Some(srcMap: Map[Any, Any]) =>
+ val endianMap = ParseUtils.asMapStr(srcMap, path)
+ Some(fromMap(endianMap, path))
+ case _ =>
+ throw new YAMLParseException(
+ s"unable to parse endianness: `le`, `be` or calculated endianness map is expected",
+ path ++ List("endian")
+ )
+ }
}
- def fromString(s: Option[String], defaultEndian: Option[Endianness], dt: String, path: List[String]) = s match {
- case Some("le") => LittleEndian
- case Some("be") => BigEndian
+ def fromMap(srcMap: Map[String, Any], path: List[String]): CalcEndian = {
+ val (_on, _cases) = SwitchType.fromYaml1(srcMap, path)
+
+ val on = Expressions.parse(_on)
+ val cases: Map[Ast.expr, FixedEndian] = _cases.map { case (condition, endStr) =>
+ Expressions.parse(condition) -> (endStr match {
+ case "be" => BigEndian
+ case "le" => LittleEndian
+ case _ =>
+ throw YAMLParseException.badDictValue(Set("be", "le"), endStr, path ++ List("cases", condition))
+ })
+ }
+
+ CalcEndian(on, cases)
+ }
+
+ def fromString(s: Option[String], defaultEndian: Option[Endianness], dt: String, path: List[String]): Option[FixedEndian] = s match {
+ case Some("le") => Some(LittleEndian)
+ case Some("be") => Some(BigEndian)
case None =>
defaultEndian match {
- case Some(e) => e
- case None => throw new YAMLParseException(s"unable to use type '$dt' without default endianness", path ++ List("type"))
+ case Some(e: FixedEndian) => Some(e)
+ case Some(_: CalcEndian) | Some(InheritedEndian) => None // to be overridden during compile
+ case None =>
+ throw new YAMLParseException(s"unable to use type '$dt' without default endianness", path ++ List("type"))
}
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala
index f0533ad29..1e0d6f1ea 100644
--- a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala
+++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala
@@ -162,13 +162,18 @@ object Expressions {
val topExpr: P[Ast.expr] = P( test ~ End )
+ val topExprList: P[Seq[Ast.expr]] = P(testlist1 ~ End)
+
class ParseException(val src: String, val failure: Parsed.Failure)
extends RuntimeException(failure.msg)
- def parse(src: String): Ast.expr = {
- val r = Expressions.topExpr.parse(src)
+ def parse(src: String): Ast.expr = realParse(src, topExpr)
+ def parseList(src: String): Seq[Ast.expr] = realParse(src, topExprList)
+
+ private def realParse[T](src: String, parser: P[T]): T = {
+ val r = parser.parse(src)
r match {
- case Parsed.Success(value, index) => value
+ case Parsed.Success(value, _) => value
case f: Parsed.Failure =>
throw new ParseException(src, f)
}
diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Lexical.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Lexical.scala
index b85d17a18..b04b07e25 100644
--- a/shared/src/main/scala/io/kaitai/struct/exprlang/Lexical.scala
+++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Lexical.scala
@@ -80,9 +80,9 @@ object Lexical {
val bindigit: P0 = P( "0" | "1" | "_" )
val hexdigit: P0 = P( digit | CharIn('a' to 'f', 'A' to 'F') | "_" )
- val floatnumber: P[BigDecimal] = P( pointfloat | exponentfloat )
+ val floatnumber: P[BigDecimal] = P( exponentfloat | pointfloat )
val pointfloat: P[BigDecimal] = P( intpart.? ~ fraction | intpart ~ "." ).!.map(BigDecimal(_))
- val exponentfloat: P[BigDecimal] = P( (intpart | pointfloat) ~ exponent ).!.map(BigDecimal(_))
+ val exponentfloat: P[BigDecimal] = P( (pointfloat | intpart) ~ exponent ).!.map(BigDecimal(_))
val intpart: P[BigDecimal] = P( digit.rep(1) ).!.map(BigDecimal(_))
val fraction: P0 = P( "." ~ digit.rep(1) )
val exponent: P0 = P( ("e" | "E") ~ ("+" | "-").? ~ digit.rep(1) )
diff --git a/shared/src/main/scala/io/kaitai/struct/format/AttrSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/AttrSpec.scala
index 2f1558327..444d81578 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/AttrSpec.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/AttrSpec.scala
@@ -18,20 +18,53 @@ case object NoRepeat extends RepeatSpec
case class ConditionalSpec(ifExpr: Option[Ast.expr], repeat: RepeatSpec)
-trait AttrLikeSpec extends YAMLPath {
+trait AttrLikeSpec extends MemberSpec {
def dataType: DataType
def cond: ConditionalSpec
def doc: DocSpec
def isArray: Boolean = cond.repeat != NoRepeat
- def dataTypeComposite: DataType = {
+ override def dataTypeComposite: DataType = {
if (isArray) {
ArrayType(dataType)
} else {
dataType
}
}
+
+ override def isNullable: Boolean = {
+ if (cond.ifExpr.isDefined) {
+ true
+ } else {
+ dataType match {
+ case st: SwitchType =>
+ st.isNullable
+ case _ =>
+ false
+ }
+ }
+ }
+
+ def isNullableSwitchRaw: Boolean = {
+ if (cond.ifExpr.isDefined) {
+ true
+ } else {
+ dataType match {
+ case st: SwitchType =>
+ st.isNullableSwitchRaw
+ case _ =>
+ false
+ }
+ }
+ }
+
+ /**
+ * Determines if this attribute is to be parsed lazily (i.e. on first use),
+ * or eagerly (during object construction, usually in a `_read` method)
+ * @return True if this attribute is lazy, false if it's eager
+ */
+ def isLazy: Boolean
}
case class AttrSpec(
@@ -40,7 +73,9 @@ case class AttrSpec(
dataType: DataType,
cond: ConditionalSpec = ConditionalSpec(None, NoRepeat),
doc: DocSpec = DocSpec.EMPTY
-) extends AttrLikeSpec
+) extends AttrLikeSpec with MemberSpec {
+ override def isLazy = false
+}
case class YamlAttrArgs(
size: Option[Ast.expr],
@@ -93,6 +128,7 @@ object AttrSpec {
"contents",
"size",
"size-eos",
+ "pad-right",
"parent",
"process"
)
@@ -108,7 +144,7 @@ object AttrSpec {
"enum"
)
- def fromYaml(src: Any, path: List[String], metaDef: MetaDefaults, idx: Int): AttrSpec = {
+ def fromYaml(src: Any, path: List[String], metaDef: MetaSpec, idx: Int): AttrSpec = {
val srcMap = ParseUtils.asMapStr(src, path)
val id = ParseUtils.getOptValueStr(srcMap, "id", path) match {
case Some(idStr) =>
@@ -123,7 +159,7 @@ object AttrSpec {
fromYaml(srcMap, path, metaDef, id)
}
- def fromYaml(srcMap: Map[String, Any], path: List[String], metaDef: MetaDefaults, id: Identifier): AttrSpec = {
+ def fromYaml(srcMap: Map[String, Any], path: List[String], metaDef: MetaSpec, id: Identifier): AttrSpec = {
try {
fromYaml2(srcMap, path, metaDef, id)
} catch {
@@ -132,9 +168,9 @@ object AttrSpec {
}
}
- def fromYaml2(srcMap: Map[String, Any], path: List[String], metaDef: MetaDefaults, id: Identifier): AttrSpec = {
+ def fromYaml2(srcMap: Map[String, Any], path: List[String], metaDef: MetaSpec, id: Identifier): AttrSpec = {
val doc = DocSpec.fromYaml(srcMap, path)
- val process = ProcessExpr.fromStr(ParseUtils.getOptValueStr(srcMap, "process", path))
+ val process = ProcessExpr.fromStr(ParseUtils.getOptValueStr(srcMap, "process", path), path)
// TODO: add proper path propagation
val contents = srcMap.get("contents").map(parseContentSpec(_, path ++ List("contents")))
val size = ParseUtils.getOptValueStr(srcMap, "size", path).map(Expressions.parse)
@@ -226,23 +262,33 @@ object AttrSpec {
private def parseSwitch(
switchSpec: Map[String, Any],
path: List[String],
- metaDef: MetaDefaults,
+ metaDef: MetaSpec,
arg: YamlAttrArgs
): DataType = {
val _on = ParseUtils.getValueStr(switchSpec, "switch-on", path)
- val _cases: Map[String, String] = switchSpec.get("cases") match {
- case None => Map()
- case Some(x) => ParseUtils.asMapStrStr(x, path ++ List("cases"))
- }
+ val _cases = ParseUtils.getValueMapStrStr(switchSpec, "cases", path)
ParseUtils.ensureLegalKeys(switchSpec, LEGAL_KEYS_SWITCH, path)
- val on = Expressions.parse(_on)
+ val on = try {
+ Expressions.parse(_on)
+ } catch {
+ case epe: Expressions.ParseException =>
+ throw YAMLParseException.expression(epe, path ++ List("switch-on"))
+ }
+
val cases = _cases.map { case (condition, typeName) =>
- Expressions.parse(condition) -> DataType.fromYaml(
- Some(typeName), path ++ List("cases"), metaDef,
+ val casePath = path ++ List("cases", condition)
+ val condType = DataType.fromYaml(
+ Some(typeName), casePath, metaDef,
arg
)
+ try {
+ Expressions.parse(condition) -> condType
+ } catch {
+ case epe: Expressions.ParseException =>
+ throw YAMLParseException.expression(epe, casePath)
+ }
}
// If we have size defined, and we don't have any "else" case already, add
diff --git a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala
index 68ab182aa..2a6a34dab 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala
@@ -1,5 +1,9 @@
package io.kaitai.struct.format
+import io.kaitai.struct.datatype.DataType
+import io.kaitai.struct.datatype.DataType.{KaitaiStructType, UserTypeInstream}
+import scala.collection.mutable
+
/**
* Type that we use when we want to refer to a class specification or something
* close, but not (yet) that well defined.
@@ -8,11 +12,18 @@ sealed trait ClassSpecLike
case object UnknownClassSpec extends ClassSpecLike
case object GenericStructClassSpec extends ClassSpecLike
+sealed trait Sized
+case object DynamicSized extends Sized
+case object NotCalculatedSized extends Sized
+case object StartedCalculationSized extends Sized
+case class FixedSized(n: Int) extends Sized
+
case class ClassSpec(
path: List[String],
isTopLevel: Boolean,
- meta: Option[MetaSpec],
+ meta: MetaSpec,
doc: DocSpec,
+ params: List[ParamDefSpec],
seq: List[AttrSpec],
types: Map[String, ClassSpec],
instances: Map[InstanceIdentifier, InstanceSpec],
@@ -39,9 +50,22 @@ case class ClassSpec(
*/
var upClass: Option[ClassSpec] = None
- def parentTypeName: List[String] = parentClass match {
- case UnknownClassSpec | GenericStructClassSpec => List("kaitai_struct")
- case t: ClassSpec => t.name
+ var seqSize: Sized = NotCalculatedSized
+
+ def parentType: DataType = parentClass match {
+ case UnknownClassSpec | GenericStructClassSpec => KaitaiStructType
+ case t: ClassSpec => UserTypeInstream(t.name, None)
+ }
+
+ /**
+ * Recursively traverses tree of types starting from this type, calling
+ * certain function for every type, starting from this one.
+ */
+ def forEachRec(proc: (ClassSpec) => Unit): Unit = {
+ proc.apply(this)
+ types.foreach { case (_, typeSpec) =>
+ typeSpec.forEachRec(proc)
+ }
}
}
@@ -50,31 +74,37 @@ object ClassSpec {
"meta",
"doc",
"doc-ref",
+ "params",
"seq",
"types",
"instances",
"enums"
)
- def fromYaml(src: Any, path: List[String], metaDef: MetaDefaults): ClassSpec = {
+ def fromYaml(src: Any, path: List[String], metaDef: MetaSpec): ClassSpec = {
val srcMap = ParseUtils.asMapStr(src, path)
ParseUtils.ensureLegalKeys(srcMap, LEGAL_KEYS, path)
- val meta = srcMap.get("meta").map(MetaSpec.fromYaml(_, path ++ List("meta")))
- val curMetaDef = metaDef.updateWith(meta)
+ val metaPath = path ++ List("meta")
+ val explicitMeta = srcMap.get("meta").map(MetaSpec.fromYaml(_, metaPath)).getOrElse(MetaSpec.emptyWithPath(metaPath))
+ val meta = explicitMeta.fillInDefaults(metaDef)
val doc = DocSpec.fromYaml(srcMap, path)
+ val params: List[ParamDefSpec] = srcMap.get("params") match {
+ case Some(value) => paramDefFromYaml(value, path ++ List("params"))
+ case None => List()
+ }
val seq: List[AttrSpec] = srcMap.get("seq") match {
- case Some(value) => seqFromYaml(value, path ++ List("seq"), curMetaDef)
+ case Some(value) => seqFromYaml(value, path ++ List("seq"), meta)
case None => List()
}
val types: Map[String, ClassSpec] = srcMap.get("types") match {
- case Some(value) => typesFromYaml(value, path ++ List("types"), curMetaDef)
+ case Some(value) => typesFromYaml(value, path ++ List("types"), meta)
case None => Map()
}
val instances: Map[InstanceIdentifier, InstanceSpec] = srcMap.get("instances") match {
- case Some(value) => instancesFromYaml(value, path ++ List("instances"), curMetaDef)
+ case Some(value) => instancesFromYaml(value, path ++ List("instances"), meta)
case None => Map()
}
val enums: Map[String, EnumSpec] = srcMap.get("enums") match {
@@ -82,13 +112,17 @@ object ClassSpec {
case None => Map()
}
- val cs = ClassSpec(path, path.isEmpty, meta, doc, seq, types, instances, enums)
+ checkDupSeqInstIds(seq, instances)
+
+ val cs = ClassSpec(
+ path, path.isEmpty,
+ meta, doc,
+ params, seq, types, instances, enums
+ )
// If that's a top-level class, set its name from meta/id
if (path.isEmpty) {
- if (meta.isEmpty)
- throw new YAMLParseException("no `meta` encountered in top-level class spec", path)
- meta.get.id match {
+ explicitMeta.id match {
case None =>
throw new YAMLParseException("no `meta/id` encountered in top-level class spec", path ++ List("meta", "id"))
case Some(id) =>
@@ -99,18 +133,68 @@ object ClassSpec {
cs
}
- def seqFromYaml(src: Any, path: List[String], metaDef: MetaDefaults): List[AttrSpec] = {
+ def paramDefFromYaml(src: Any, path: List[String]): List[ParamDefSpec] = {
src match {
case srcList: List[Any] =>
- srcList.zipWithIndex.map { case (attrSrc, idx) =>
+ val params = srcList.zipWithIndex.map { case (attrSrc, idx) =>
+ ParamDefSpec.fromYaml(attrSrc, path ++ List(idx.toString), idx)
+ }
+ // FIXME: checkDupSeqIds(params)
+ params
+ case unknown =>
+ throw new YAMLParseException(s"expected array, found $unknown", path)
+ }
+ }
+
+ def seqFromYaml(src: Any, path: List[String], metaDef: MetaSpec): List[AttrSpec] = {
+ src match {
+ case srcList: List[Any] =>
+ val seq = srcList.zipWithIndex.map { case (attrSrc, idx) =>
AttrSpec.fromYaml(attrSrc, path ++ List(idx.toString), metaDef, idx)
}
+ checkDupSeqIds(seq)
+ seq
case unknown =>
throw new YAMLParseException(s"expected array, found $unknown", path)
}
}
- def typesFromYaml(src: Any, path: List[String], metaDef: MetaDefaults): Map[String, ClassSpec] = {
+ def checkDupSeqIds(seq: List[AttrSpec]): Unit = {
+ val attrIds = mutable.Map[String, AttrSpec]()
+ seq.foreach { (attr) =>
+ attr.id match {
+ case NamedIdentifier(id) =>
+ checkDupId(attrIds.get(id), id, attr)
+ attrIds.put(id, attr)
+ case _ => // do nothing with non-named IDs
+ }
+ }
+ }
+
+ def checkDupSeqInstIds(seq: List[AttrSpec], instances: Map[InstanceIdentifier, InstanceSpec]): Unit = {
+ val attrIds: Map[String, AttrSpec] = seq.flatMap((attr) => attr.id match {
+ case NamedIdentifier(id) => Some(id -> attr)
+ case _ => None
+ }).toMap
+
+ instances.foreach { case (id, instSpec) =>
+ checkDupId(attrIds.get(id.name), id.name, instSpec)
+ }
+ }
+
+ private def checkDupId(prevAttrOpt: Option[AttrSpec], id: String, nowAttr: YAMLPath) {
+ prevAttrOpt match {
+ case Some(prevAttr) =>
+ throw new YAMLParseException(
+ s"duplicate attribute ID '$id', previously defined at /${prevAttr.pathStr}",
+ nowAttr.path
+ )
+ case None =>
+ // no dups, ok
+ }
+ }
+
+ def typesFromYaml(src: Any, path: List[String], metaDef: MetaSpec): Map[String, ClassSpec] = {
val srcMap = ParseUtils.asMapStr(src, path)
srcMap.map { case (typeName, body) =>
Identifier.checkIdentifierSource(typeName, "type", path ++ List(typeName))
@@ -118,7 +202,7 @@ object ClassSpec {
}
}
- def instancesFromYaml(src: Any, path: List[String], metaDef: MetaDefaults): Map[InstanceIdentifier, InstanceSpec] = {
+ def instancesFromYaml(src: Any, path: List[String], metaDef: MetaSpec): Map[InstanceIdentifier, InstanceSpec] = {
val srcMap = ParseUtils.asMap(src, path)
srcMap.map { case (key, body) =>
val instName = ParseUtils.asStr(key, path)
@@ -137,14 +221,15 @@ object ClassSpec {
}
}
- def fromYaml(src: Any): ClassSpec = fromYaml(src, List(), MetaDefaults(None, None))
+ def fromYaml(src: Any): ClassSpec = fromYaml(src, List(), MetaSpec.OPAQUE)
def opaquePlaceholder(typeName: List[String]): ClassSpec = {
val placeholder = ClassSpec(
List(),
true,
- meta = Some(MetaSpec.OPAQUE),
+ meta = MetaSpec.OPAQUE,
doc = DocSpec.EMPTY,
+ params = List(),
seq = List(),
types = Map(),
instances = Map(),
diff --git a/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala b/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala
index 24837174d..664ce5297 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala
@@ -1,5 +1,7 @@
package io.kaitai.struct.format
+import io.kaitai.struct.precompile.ErrorInInput
+
import scala.collection.mutable
import scala.concurrent.Future
@@ -12,6 +14,29 @@ import scala.concurrent.Future
abstract class ClassSpecs(val firstSpec: ClassSpec) extends mutable.HashMap[String, ClassSpec] {
this(firstSpec.name.head) = firstSpec
+ /**
+ * Calls certain function on all [[ClassSpec]] elements stored in this ClassSpecs,
+ * and all subtypes stored in these elements, recursively.
+ */
+ def forEachRec(proc: (ClassSpec) => Unit): Unit =
+ forEachTopLevel((_, typeSpec) => typeSpec.forEachRec(proc))
+
+ /**
+ * Calls certain function on all top-level [[ClassSpec]] elements stored in this
+ * ClassSpecs.
+ */
+ def forEachTopLevel(proc: (String, ClassSpec) => Unit): Unit = {
+ foreach { case (specName, typeSpec) =>
+ try {
+ proc(specName, typeSpec)
+ } catch {
+ case ErrorInInput(err, path, None) =>
+ // Try to emit more specific error, with a reference to current file
+ throw ErrorInInput(err, path, Some(specName))
+ }
+ }
+ }
+
def importRelative(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]]
def importAbsolute(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]]
}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/EnumSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/EnumSpec.scala
index f7db4f515..8f10e19f2 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/EnumSpec.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/EnumSpec.scala
@@ -1,25 +1,23 @@
package io.kaitai.struct.format
-case class EnumSpec(map: Map[Long, String]) {
+case class EnumSpec(map: Map[Long, EnumValueSpec]) {
var name = List[String]()
/**
* Stabilize order of generated enums by sorting it by integer ID - it
* both looks nicer and doesn't screw diffs in generated code.
*/
- lazy val sortedSeq: Seq[(Long, String)] = map.toSeq.sortBy(_._1)
+ lazy val sortedSeq: Seq[(Long, EnumValueSpec)] = map.toSeq.sortBy(_._1)
}
object EnumSpec {
def fromYaml(src: Any, path: List[String]): EnumSpec = {
val srcMap = ParseUtils.asMap(src, path)
- EnumSpec(srcMap.map { case (id, name) =>
+ EnumSpec(srcMap.map { case (id, desc) =>
val idLong = ParseUtils.asLong(id, path)
- val symbName = ParseUtils.asStr(name, path ++ List(idLong.toString))
+ val value = EnumValueSpec.fromYaml(desc, path ++ List(idLong.toString))
- Identifier.checkIdentifierSource(symbName, "enum member", path ++ List(idLong.toString))
-
- idLong -> symbName
+ idLong -> value
})
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/EnumValueSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/EnumValueSpec.scala
new file mode 100644
index 000000000..905e968a6
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/format/EnumValueSpec.scala
@@ -0,0 +1,40 @@
+package io.kaitai.struct.format
+
+case class EnumValueSpec(name: String, doc: DocSpec)
+
+object EnumValueSpec {
+ def fromYaml(src: Any, path: List[String]): EnumValueSpec = {
+ src match {
+ case name: String =>
+ fromSimpleName(name, path)
+ case x: Boolean =>
+ fromSimpleName(x.toString, path)
+ case srcMap: Map[Any, Any] =>
+ fromMap(ParseUtils.anyMapToStrMap(srcMap, path), path)
+ case _ =>
+ throw YAMLParseException.badType("string or map", src, path)
+ }
+ }
+
+ def fromSimpleName(name: String, path: List[String]): EnumValueSpec = {
+ Identifier.checkIdentifierSource(name, "enum member", path)
+ EnumValueSpec(name, DocSpec.EMPTY)
+ }
+
+ val LEGAL_KEYS = Set(
+ "id",
+ "doc",
+ "doc-ref"
+ )
+
+ def fromMap(srcMap: Map[String, Any], path: List[String]): EnumValueSpec = {
+ ParseUtils.ensureLegalKeys(srcMap, LEGAL_KEYS, path, Some("enum value spec"))
+
+ val name = ParseUtils.getValueStr(srcMap, "id", path)
+ Identifier.checkIdentifierSource(name, "enum value spec id", path)
+
+ val doc = DocSpec.fromYaml(srcMap, path)
+
+ EnumValueSpec(name, doc)
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/Identifier.scala b/shared/src/main/scala/io/kaitai/struct/format/Identifier.scala
index 01d6fbaa6..e3ae3c429 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/Identifier.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/Identifier.scala
@@ -3,13 +3,22 @@ package io.kaitai.struct.format
/**
* Common abstract container for all identifiers that Kaitai Struct deals with.
*/
-abstract class Identifier
+abstract class Identifier {
+ /**
+ * @return Human-readable name of identifier, to be used exclusively for ksc
+ * error messaging purposes.
+ */
+ def humanReadable: String
+}
/**
* Identifier generated automatically for seq attributes which lack true string "id" field.
* @param idx unique number to identify attribute with
*/
-case class NumberedIdentifier(idx: Int) extends Identifier
+case class NumberedIdentifier(idx: Int) extends Identifier {
+ import NumberedIdentifier._
+ override def humanReadable: String = s"${TEMPLATE}_$idx"
+}
object NumberedIdentifier {
val TEMPLATE = "unnamed"
@@ -21,9 +30,13 @@ object NumberedIdentifier {
*/
case class NamedIdentifier(name: String) extends Identifier {
Identifier.checkIdentifier(name)
+
+ override def humanReadable: String = name
}
-case class InvalidIdentifier(id: String) extends RuntimeException
+case class InvalidIdentifier(id: String) extends RuntimeException(
+ s"invalid ID: '$id', expected /${Identifier.ReIdentifier.toString}/"
+)
object Identifier {
val ReIdentifier = "^[a-z][a-z0-9_]*$".r
@@ -60,18 +73,30 @@ object Identifier {
val IO = "_io"
val ITERATOR = "_"
val ITERATOR2 = "_buf"
+ val INDEX = "_index"
+ val SWITCH_ON = "_on"
+ val IS_LE = "_is_le"
}
-case class RawIdentifier(innerId: Identifier) extends Identifier
+case class RawIdentifier(innerId: Identifier) extends Identifier {
+ override def humanReadable: String = s"raw(${innerId.humanReadable})"
+}
-case class IoStorageIdentifier(innerId: Identifier) extends Identifier
+case class IoStorageIdentifier(innerId: Identifier) extends Identifier {
+ override def humanReadable: String = s"io(${innerId.humanReadable})"
+}
case class InstanceIdentifier(name: String) extends Identifier {
Identifier.checkIdentifier(name)
+
+ override def humanReadable: String = name
}
-case class SpecialIdentifier(name: String) extends Identifier
+case class SpecialIdentifier(name: String) extends Identifier {
+ override def humanReadable: String = name
+}
object RootIdentifier extends SpecialIdentifier(Identifier.ROOT)
object ParentIdentifier extends SpecialIdentifier(Identifier.PARENT)
object IoIdentifier extends SpecialIdentifier(Identifier.IO)
+object EndianIdentifier extends SpecialIdentifier(Identifier.IS_LE)
diff --git a/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala
index 024313427..6d6ca2821 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala
@@ -3,8 +3,9 @@ package io.kaitai.struct.format
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.exprlang.{Ast, Expressions}
-sealed abstract class InstanceSpec(val doc: DocSpec) {
+sealed abstract class InstanceSpec(val doc: DocSpec) extends YAMLPath {
def dataTypeComposite: DataType
+ def isNullable: Boolean
}
case class ValueInstanceSpec(
path: List[String],
@@ -12,17 +13,21 @@ case class ValueInstanceSpec(
value: Ast.expr,
ifExpr: Option[Ast.expr],
var dataType: Option[DataType]
-) extends InstanceSpec(_doc) with YAMLPath {
+) extends InstanceSpec(_doc) {
override def dataTypeComposite = dataType.get
+ override def isNullable: Boolean = ifExpr.isDefined
}
case class ParseInstanceSpec(
+ id: Identifier,
path: List[String],
private val _doc: DocSpec,
dataType: DataType,
cond: ConditionalSpec,
pos: Option[Ast.expr],
io: Option[Ast.expr]
-) extends InstanceSpec(_doc) with AttrLikeSpec with YAMLPath
+) extends InstanceSpec(_doc) with AttrLikeSpec {
+ override def isLazy = true
+}
object InstanceSpec {
val LEGAL_KEYS_VALUE_INST = Set(
@@ -33,7 +38,7 @@ object InstanceSpec {
"if"
)
- def fromYaml(src: Any, path: List[String], metaDef: MetaDefaults, id: InstanceIdentifier): InstanceSpec = {
+ def fromYaml(src: Any, path: List[String], metaDef: MetaSpec, id: InstanceIdentifier): InstanceSpec = {
val srcMap = ParseUtils.asMapStr(src, path)
ParseUtils.getOptValueStr(srcMap, "value", path).map(Expressions.parse) match {
@@ -65,7 +70,7 @@ object InstanceSpec {
val fakeAttrMap = srcMap.filterKeys((key) => key != "pos" && key != "io")
val a = AttrSpec.fromYaml(fakeAttrMap, path, metaDef, id)
- ParseInstanceSpec(path, a.doc, a.dataType, a.cond, pos, io)
+ ParseInstanceSpec(id, path, a.doc, a.dataType, a.cond, pos, io)
}
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/KSVersion.scala b/shared/src/main/scala/io/kaitai/struct/format/KSVersion.scala
index b41836555..160ad48eb 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/KSVersion.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/KSVersion.scala
@@ -65,7 +65,7 @@ object KSVersion {
def current: KSVersion = _current.get
def fromStr(str: String): KSVersion =
- KSVersion(str.replace("-SNAPSHOT", "").split('.').map(_.toInt).toList)
+ KSVersion(str.replaceAll("-SNAPSHOT.*$", "").split('.').map(_.toInt).toList)
/**
* Hardcoded minimal version of runtime API that this particular
diff --git a/shared/src/main/scala/io/kaitai/struct/format/MemberSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/MemberSpec.scala
new file mode 100644
index 000000000..c8ed8ce3e
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/format/MemberSpec.scala
@@ -0,0 +1,31 @@
+package io.kaitai.struct.format
+
+import io.kaitai.struct.datatype.DataType
+
+/**
+ * Base trait for everything that would be compiled to be members of the class,
+ * i.e. sequence attributes, parse instances, value instances, parameters.
+ */
+trait MemberSpec extends YAMLPath {
+ def id: Identifier
+ def dataType: DataType
+ def doc: DocSpec
+
+ def dataTypeComposite = dataType
+
+ /**
+ * Determines if this attribute can be "null" in some circumstances or not.
+ * In some target languages, it would affect data types used, init and
+ * cleanup procedures.
+ * @return True if this attribute can be "null", false if it's never "null"
+ */
+ def isNullable: Boolean
+
+ /**
+ * Determines if this attribute can be "null" in some circumstances or not.
+ * This version is for languages like C++, which make a special exception
+ * for raw byte arrays placement for switch statements.
+ * @return True if this attribute can be "null", false if it's never "null"
+ */
+ def isNullableSwitchRaw: Boolean
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/MetaDefaults.scala b/shared/src/main/scala/io/kaitai/struct/format/MetaDefaults.scala
deleted file mode 100644
index 928f578cd..000000000
--- a/shared/src/main/scala/io/kaitai/struct/format/MetaDefaults.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.kaitai.struct.format
-
-import io.kaitai.struct.datatype.Endianness
-
-case class MetaDefaults(
- endian: Option[Endianness],
- encoding: Option[String]
-) {
- def updateWith(metaOpt: Option[MetaSpec]): MetaDefaults = {
- metaOpt match {
- case None => this
- case Some(meta) =>
- MetaDefaults(
- meta.endian.orElse(this.endian),
- meta.encoding.orElse(this.encoding)
- )
- }
- }
-}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala
index abb8a6690..000b55245 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/MetaSpec.scala
@@ -1,6 +1,6 @@
package io.kaitai.struct.format
-import io.kaitai.struct.datatype.Endianness
+import io.kaitai.struct.datatype.{CalcEndian, Endianness, InheritedEndian}
case class MetaSpec(
path: List[String],
@@ -11,9 +11,37 @@ case class MetaSpec(
forceDebug: Boolean,
opaqueTypes: Option[Boolean],
imports: List[String]
-) extends YAMLPath
+) extends YAMLPath {
+ def fillInDefaults(defSpec: MetaSpec): MetaSpec = {
+ fillInEncoding(defSpec.encoding).
+ fillInEndian(defSpec.endian)
+ }
+
+ private
+ def fillInEncoding(defEncoding: Option[String]): MetaSpec = {
+ (defEncoding, encoding) match {
+ case (None, _) => this
+ case (_, Some(_)) => this
+ case (Some(_), None) =>
+ this.copy(encoding = defEncoding)
+ }
+ }
+
+ def fillInEndian(defEndian: Option[Endianness]): MetaSpec = {
+ (defEndian, endian) match {
+ case (None, _) => this
+ case (_, Some(_)) => this
+ case (Some(_: CalcEndian), None) =>
+ this.copy(endian = Some(InheritedEndian))
+ case (Some(_), None) =>
+ this.copy(endian = defEndian)
+ }
+ }
+}
object MetaSpec {
+ def emptyWithPath(path: List[String]) = OPAQUE.copy(isOpaque = false, path = path)
+
val OPAQUE = MetaSpec(
path = List(),
isOpaque = true,
@@ -36,6 +64,7 @@ object MetaSpec {
"ks-opaque-types",
"license",
"file-extension",
+ "xref",
"application"
)
@@ -48,6 +77,8 @@ object MetaSpec {
throw YAMLParseException.incompatibleVersion(ver, KSVersion.current, path)
}
+ val endian: Option[Endianness] = Endianness.fromYaml(srcMap.get("endian"), path)
+
ParseUtils.ensureLegalKeys(srcMap, LEGAL_KEYS, path)
val id = ParseUtils.getOptValueStr(srcMap, "id", path)
@@ -55,10 +86,6 @@ object MetaSpec {
Identifier.checkIdentifierSource(idStr, "meta", path ++ List("id"))
)
- val endian: Option[Endianness] = Endianness.defaultFromString(
- ParseUtils.getOptValueStr(srcMap, "endian", path),
- path
- )
val encoding = ParseUtils.getOptValueStr(srcMap, "encoding", path)
val forceDebug = ParseUtils.getOptValueBool(srcMap, "ks-debug", path).getOrElse(false)
diff --git a/shared/src/main/scala/io/kaitai/struct/format/ParamDefSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/ParamDefSpec.scala
new file mode 100644
index 000000000..b5cef37be
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/format/ParamDefSpec.scala
@@ -0,0 +1,36 @@
+package io.kaitai.struct.format
+
+import io.kaitai.struct.datatype.DataType
+
+case class ParamDefSpec(
+ path: List[String],
+ id: Identifier,
+ dataType: DataType,
+ doc: DocSpec = DocSpec.EMPTY
+) extends MemberSpec {
+ override def isNullable: Boolean = false
+ override def isNullableSwitchRaw: Boolean = false
+}
+
+object ParamDefSpec {
+ def fromYaml(src: Any, path: List[String], idx: Int): ParamDefSpec = {
+ val srcMap = ParseUtils.asMapStr(src, path)
+ val id = ParseUtils.getValueIdentifier(srcMap, idx, "parameter", path)
+ fromYaml(srcMap, path, id)
+ }
+
+ val LEGAL_KEYS = Set(
+ "id",
+ "type",
+ "doc",
+ "doc-ref"
+ )
+
+ def fromYaml(srcMap: Map[String, Any], path: List[String], id: Identifier): ParamDefSpec = {
+ val doc = DocSpec.fromYaml(srcMap, path)
+ val typeStr = ParseUtils.getOptValueStr(srcMap, "type", path)
+ val dataType = DataType.pureFromString(typeStr)
+
+ ParamDefSpec(path, id, dataType, doc)
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/ParseUtils.scala b/shared/src/main/scala/io/kaitai/struct/format/ParseUtils.scala
index 09e589827..614f6954d 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/ParseUtils.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/ParseUtils.scala
@@ -21,10 +21,19 @@ object ParseUtils {
def getValueStr(src: Map[String, Any], field: String, path: List[String]): String = {
src.get(field) match {
- case Some(value: String) =>
- value
- case unknown =>
- throw YAMLParseException.badType("string", unknown, path ++ List(field))
+ case Some(value) =>
+ asStr(value, path ++ List(field))
+ case None =>
+ throw YAMLParseException.noKey(path ++ List(field))
+ }
+ }
+
+ def getValueMapStrStr(src: Map[String, Any], field: String, path: List[String]): Map[String, String] = {
+ src.get(field) match {
+ case Some(value) =>
+ asMapStrStr(value, path ++ List(field))
+ case None =>
+ throw YAMLParseException.noKey(path ++ List(field))
}
}
@@ -61,6 +70,19 @@ object ParseUtils {
}
}
+ def getValueIdentifier(src: Map[String, Any], idx: Int, entityName: String, path: List[String]): Identifier = {
+ getOptValueStr(src, "id", path) match {
+ case Some(idStr) =>
+ try {
+ NamedIdentifier(idStr)
+ } catch {
+ case _: InvalidIdentifier =>
+ throw YAMLParseException.invalidId(idStr, entityName, path ++ List("id"))
+ }
+ case None => NumberedIdentifier(idx)
+ }
+ }
+
/**
* Gets a list of T-typed values from a given YAML map's key "field",
* reporting errors accurately and ensuring type safety.
@@ -113,6 +135,8 @@ object ParseUtils {
str
case n: Int =>
n.toString
+ case n: Long =>
+ n.toString
case n: Double =>
n.toString
case n: Boolean =>
diff --git a/shared/src/main/scala/io/kaitai/struct/format/ProcessExpr.scala b/shared/src/main/scala/io/kaitai/struct/format/ProcessExpr.scala
index 3f56f2b50..7fe8a6cf9 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/ProcessExpr.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/ProcessExpr.scala
@@ -1,29 +1,21 @@
package io.kaitai.struct.format
-import io.kaitai.struct.exprlang.{Expressions, Ast}
+import io.kaitai.struct.exprlang.{Ast, Expressions}
-trait ProcessExpr {
- def outputType: String
-}
+sealed trait ProcessExpr
-case object ProcessZlib extends ProcessExpr {
- override def outputType: String = null
-}
-case object ProcessHexStrToInt extends ProcessExpr {
- override def outputType: String = "u4"
-}
-case class ProcessXor(key: Ast.expr) extends ProcessExpr {
- override def outputType: String = null
-}
-case class ProcessRotate(left: Boolean, key: Ast.expr) extends ProcessExpr {
- override def outputType: String = null
-}
+case object ProcessZlib extends ProcessExpr
+case class ProcessXor(key: Ast.expr) extends ProcessExpr
+case class ProcessRotate(left: Boolean, key: Ast.expr) extends ProcessExpr
+case class ProcessCustom(name: List[String], args: Seq[Ast.expr]) extends ProcessExpr
object ProcessExpr {
private val ReXor = "^xor\\(\\s*(.*?)\\s*\\)$".r
private val ReRotate = "^ro(l|r)\\(\\s*(.*?)\\s*\\)$".r
+ private val ReCustom = "^([a-z][a-z0-9_.]*)\\(\\s*(.*?)\\s*\\)$".r
+ private val ReCustomNoArg = "^([a-z][a-z0-9_.]*)$".r
- def fromStr(s: Option[String]): Option[ProcessExpr] = {
+ def fromStr(s: Option[String], path: List[String]): Option[ProcessExpr] = {
s match {
case None =>
None
@@ -31,14 +23,16 @@ object ProcessExpr {
Some(op match {
case "zlib" =>
ProcessZlib
- case "hexstr_to_int" =>
- ProcessHexStrToInt
case ReXor(arg) =>
ProcessXor(Expressions.parse(arg))
case ReRotate(dir, arg) =>
ProcessRotate(dir == "l", Expressions.parse(arg))
+ case ReCustom(name, args) =>
+ ProcessCustom(name.split('.').toList, Expressions.parseList(args))
+ case ReCustomNoArg(name) =>
+ ProcessCustom(name.split('.').toList, Seq())
case _ =>
- throw new RuntimeException(s"Invalid process: '$s'")
+ throw YAMLParseException.badProcess(op, path)
})
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/YAMLParseException.scala b/shared/src/main/scala/io/kaitai/struct/format/YAMLParseException.scala
index 747f044fa..bedf3bb70 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/YAMLParseException.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/YAMLParseException.scala
@@ -8,6 +8,9 @@ class YAMLParseException(val msg: String, val path: List[String])
extends RuntimeException(s"/${path.mkString("/")}: $msg", null)
object YAMLParseException {
+ def noKey(path: List[String]): YAMLParseException =
+ new YAMLParseException(s"missing mandatory argument `${path.last}`", path)
+
def badType(expected: String, got: Any, path: List[String]): YAMLParseException = {
val gotStr = got match {
case null => "null"
@@ -35,11 +38,23 @@ object YAMLParseException {
val f = epe.failure
val pos = StringReprOps.prettyIndex(f.extra.input, f.index)
new YAMLParseException(
- s"parsing expression '${epe.src}' failed on $pos, expected ${f.extra.traced.expected}",
+ s"parsing expression '${epe.src}' failed on $pos, expected ${f.extra.traced.expected.replaceAll("\n", "\\n")}",
path
)
}
def exprType(expected: String, got: DataType, path: List[String]): YAMLParseException =
new YAMLParseException(s"invalid type: expected $expected, got $got", path)
+
+ def badProcess(got: String, path: List[String]): YAMLParseException =
+ new YAMLParseException(s"incorrect process expression `$got`", path)
+
+ def invalidParamCount(paramSize: Int, argSize: Int, path: List[String]): YAMLParseException =
+ new YAMLParseException(s"parameter count mismatch: $paramSize declared, but $argSize used", path)
+
+ def paramMismatch(idx: Int, argType: DataType, paramName: String, paramType: DataType, path: List[String]): YAMLParseException =
+ new YAMLParseException(
+ s"can't pass argument #$idx of type $argType into parameter `$paramName` of type $paramType",
+ path
+ )
}
diff --git a/shared/src/main/scala/io/kaitai/struct/format/YAMLPath.scala b/shared/src/main/scala/io/kaitai/struct/format/YAMLPath.scala
index 7c1dd465c..9282a6e66 100644
--- a/shared/src/main/scala/io/kaitai/struct/format/YAMLPath.scala
+++ b/shared/src/main/scala/io/kaitai/struct/format/YAMLPath.scala
@@ -1,5 +1,11 @@
package io.kaitai.struct.format
+/**
+ * Common trait for all format parts that stores YAML path that corresponds
+ * to particular format part. Used to throw path-localized exceptions, i.e.
+ * [[YAMLParseException]] and [[io.kaitai.struct.precompile.ErrorInInput]],
+ * and implement better error messaging.
+ */
trait YAMLPath {
def path: List[String]
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala
index a6048dae8..55fcd971a 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala
@@ -1,13 +1,13 @@
package io.kaitai.struct.languages
import io.kaitai.struct._
-import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype.{CalcEndian, DataType, FixedEndian, InheritedEndian}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.format.{RepeatUntil, _}
import io.kaitai.struct.languages.components._
-import io.kaitai.struct.translators.{CSharpTranslator, TypeDetector, TypeProvider}
+import io.kaitai.struct.translators.{CSharpTranslator, TypeDetector}
class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
extends LanguageCompiler(typeProvider, config)
@@ -21,24 +21,26 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
with NoNeedForFullClassPath {
import CSharpCompiler._
- override def getStatic = CSharpCompiler
+ val translator = new CSharpTranslator(typeProvider, importList)
override def indent: String = " "
override def outFileName(topClassName: String): String = s"${type2class(topClassName)}.cs"
+ override def outImports(topClass: ClassSpec) =
+ importList.toList.map((x) => s"using $x;").mkString("", "\n", "\n")
+
override def fileHeader(topClassName: String): Unit = {
- out.puts(s"// $headerComment")
+ outHeader.puts(s"// $headerComment")
+ outHeader.puts
var ns = "Kaitai"
if (!config.dotNetNamespace.isEmpty)
ns = config.dotNetNamespace
- out.puts
- out.puts("using System;")
- out.puts("using System.Collections.Generic;")
- if (ns != "Kaitai") out.puts("using Kaitai;")
- out.puts
+ if (ns != "Kaitai")
+ importList.add("Kaitai")
+ out.puts
out.puts(s"namespace $ns")
out.puts(s"{")
out.inc
@@ -55,46 +57,106 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"{")
out.inc
- out.puts(s"public static ${type2class(name)} FromFile(string fileName)")
- out.puts(s"{")
- out.inc
- out.puts(s"return new ${type2class(name)}(new $kstreamName(fileName));")
- out.dec
- out.puts("}")
+ // `FromFile` is generated only for parameterless types
+ if (typeProvider.nowClass.params.isEmpty) {
+ out.puts(s"public static ${type2class(name)} FromFile(string fileName)")
+ out.puts(s"{")
+ out.inc
+ out.puts(s"return new ${type2class(name)}(new $kstreamName(fileName));")
+ out.dec
+ out.puts("}")
+ out.puts
+ }
}
override def classFooter(name: String): Unit = fileFooter(name)
- override def classConstructorHeader(name: String, parentClassName: String, rootClassName: String): Unit = {
- out.puts
- out.puts(s"public ${type2class(name)}($kstreamName io, ${type2class(parentClassName)} parent = null, ${type2class(rootClassName)} root = null) : base(io)")
+ override def classConstructorHeader(name: String, parentType: DataType, rootClassName: String, isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ typeProvider.nowClass.meta.endian match {
+ case Some(_: CalcEndian) | Some(InheritedEndian) =>
+ out.puts(s"private bool? ${privateMemberName(EndianIdentifier)};")
+ case _ =>
+ // no _is_le variable
+ }
+
+ val addEndian = if (isHybrid) ", bool? isLe = null" else ""
+
+ val pIo = paramName(IoIdentifier)
+ val pParent = paramName(ParentIdentifier)
+ val pRoot = paramName(RootIdentifier)
+
+ val paramsArg = Utils.join(params.map((p) =>
+ s"${kaitaiType2NativeType(p.dataType)} ${paramName(p.id)}"
+ ), "", ", ", ", ")
+
+ out.puts(
+ s"public ${type2class(name)}($paramsArg" +
+ s"$kstreamName $pIo, " +
+ s"${kaitaiType2NativeType(parentType)} $pParent = null, " +
+ s"${type2class(rootClassName)} $pRoot = null$addEndian) : base($pIo)"
+ )
out.puts(s"{")
out.inc
- out.puts(s"${privateMemberName(ParentIdentifier)} = parent;")
+ handleAssignmentSimple(ParentIdentifier, pParent)
+
+ handleAssignmentSimple(
+ RootIdentifier,
+ if (name == rootClassName) s"$pRoot ?? this" else pRoot
+ )
- if (name == rootClassName)
- out.puts(s"${privateMemberName(RootIdentifier)} = root ?? this;")
- else
- out.puts(s"${privateMemberName(RootIdentifier)} = root;")
+ if (isHybrid)
+ handleAssignmentSimple(EndianIdentifier, "isLe")
- out.puts("_parse();")
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
+ }
+
+ override def classConstructorFooter: Unit = fileFooter(null)
+
+ override def runRead(): Unit =
+ out.puts("_read();")
+
+ override def runReadCalc(): Unit = {
+ out.puts
+ out.puts(s"if (${privateMemberName(EndianIdentifier)} == null) {")
+ out.inc
+ out.puts("throw new Exception(\"Unable to decide on endianness\");")
+ importList.add("System")
+ out.dec
+ out.puts(s"} else if (${privateMemberName(EndianIdentifier)} == true) {")
+ out.inc
+ out.puts("_readLE();")
+ out.dec
+ out.puts("} else {")
+ out.inc
+ out.puts("_readBE();")
out.dec
out.puts("}")
- out.puts
+ }
- out.puts("private void _parse()")
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean) = {
+ val readAccessAndType = if (debug) {
+ "public"
+ } else {
+ "private"
+ }
+ val suffix = endian match {
+ case Some(e) => s"${e.toSuffix.toUpperCase}"
+ case None => ""
+ }
+ out.puts(s"$readAccessAndType void _read$suffix()")
out.puts("{")
out.inc
}
- override def classConstructorFooter: Unit = fileFooter(null)
+ override def readFooter(): Unit = fileFooter("")
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
- out.puts(s"private ${kaitaiType2NativeType(attrType)} ${privateMemberName(attrName)};")
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"private ${kaitaiType2NativeTypeNullable(attrType, isNullable)} ${privateMemberName(attrName)};")
}
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
- out.puts(s"public ${kaitaiType2NativeType(attrType)} ${publicMemberName(attrName)} { get { return ${privateMemberName(attrName)}; } }")
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"public ${kaitaiType2NativeTypeNullable(attrType, isNullable)} ${publicMemberName(attrName)} { get { return ${privateMemberName(attrName)}; } }")
}
override def universalDoc(doc: DocSpec): Unit = {
@@ -118,6 +180,18 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
}
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts(s"if (${privateMemberName(EndianIdentifier)} == true) {")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("} else {")
+ out.inc
+ beProc()
+ out.dec
+ out.puts("}")
+ }
+
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit =
out.puts(s"${privateMemberName(attrName)} = $normalIO.EnsureFixedContents($contents);")
@@ -137,6 +211,11 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
s"8 - (${expression(rotValue)})"
}
out.puts(s"$destName = $normalIO.ProcessRotateLeft($srcName, $expr, 1);")
+ case ProcessCustom(name, args) =>
+ val procClass = types2class(name)
+ val procName = s"_process_${idToStr(varSrc)}"
+ out.puts(s"$procClass $procName = new $procClass(${args.map(expression).mkString(", ")});")
+ out.puts(s"$destName = $procName.Decode($srcName);")
}
}
@@ -188,9 +267,14 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def condIfFooter(expr: expr): Unit = fileFooter(null)
override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean): Unit = {
+ importList.add("System.Collections.Generic")
+
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = new List();")
out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayType(dataType))}();")
+ out.puts("{")
+ out.inc
+ out.puts("var i = 0;")
out.puts(s"while (!$io.IsEof) {")
out.inc
}
@@ -199,13 +283,22 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"${privateMemberName(id)}.Add($expr);")
}
- override def condRepeatEosFooter: Unit = fileFooter(null)
+ override def condRepeatEosFooter: Unit = {
+ out.puts("i++;")
+ out.dec
+ out.puts("}")
+ out.dec
+ out.puts("}")
+ }
override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: expr): Unit = {
+ importList.add("System.Collections.Generic")
+
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = new List((int) (${expression(repeatExpr)}));")
out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayType(dataType))}((int) (${expression(repeatExpr)}));")
- out.puts(s"for (var i = 0; i < ${expression(repeatExpr)}; i++) {")
+ out.puts(s"for (var i = 0; i < ${expression(repeatExpr)}; i++)")
+ out.puts("{")
out.inc
}
@@ -216,11 +309,14 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def condRepeatExprFooter: Unit = fileFooter(null)
override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
+ importList.add("System.Collections.Generic")
+
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = new List();")
out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayType(dataType))}();")
out.puts("{")
out.inc
+ out.puts("var i = 0;")
out.puts(s"${kaitaiType2NativeType(dataType)} ${translator.doName("_")};")
out.puts("do {")
out.inc
@@ -238,6 +334,7 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
typeProvider._currentIteratorType = Some(dataType)
+ out.puts("i++;")
out.dec
out.puts(s"} while (!(${expression(untilExpr)}));")
out.dec
@@ -248,10 +345,10 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"${privateMemberName(id)} = $expr;")
}
- override def parseExpr(dataType: DataType, io: String): String = {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
dataType match {
case t: ReadableType =>
- s"$io.Read${Utils.capitalize(t.apiCall)}()"
+ s"$io.Read${Utils.capitalize(t.apiCall(defEndian))}()"
case blt: BytesLimitType =>
s"$io.ReadBytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -263,6 +360,7 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case BitsType(width: Int) =>
s"$io.ReadBitsInt($width)"
case t: UserType =>
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), "", ", ", ", ")
val addArgs = if (t.isOpaque) {
""
} else {
@@ -271,9 +369,13 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case Some(fp) => translator.translate(fp)
case None => "this"
}
- s", $parent, ${privateMemberName(RootIdentifier)}"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => s", ${privateMemberName(EndianIdentifier)}"
+ case _ => ""
+ }
+ s", $parent, ${privateMemberName(RootIdentifier)}$addEndian"
}
- s"new ${types2class(t.name)}($io$addArgs)"
+ s"new ${types2class(t.name)}($addParams$io$addArgs)"
}
}
@@ -289,35 +391,98 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
expr2
}
- override def switchStart(id: Identifier, on: Ast.expr): Unit =
- out.puts(s"switch (${expression(on)}) {")
+ /**
+ * Designates switch mode. If false, we're doing real switch-case for this
+ * attribute. If true, we're doing if-based emulation.
+ */
+ var switchIfs = false
+
+ val NAME_SWITCH_ON = Ast.expr.Name(Ast.identifier(Identifier.SWITCH_ON))
+
+ override def switchStart(id: Identifier, on: Ast.expr): Unit = {
+ val onType = translator.detectType(on)
+ typeProvider._currentSwitchType = Some(onType)
+
+ // Determine switching mode for this construct based on type
+ switchIfs = onType match {
+ case _: IntType | _: EnumType | _: StrType => false
+ case _ => true
+ }
+
+ if (switchIfs) {
+ out.puts("{")
+ out.inc
+ out.puts(s"${kaitaiType2NativeType(onType)} ${expression(NAME_SWITCH_ON)} = ${expression(on)};")
+ } else {
+ out.puts(s"switch (${expression(on)}) {")
+ }
+ }
+
+ def switchCmpExpr(condition: Ast.expr): String =
+ expression(
+ Ast.expr.Compare(
+ NAME_SWITCH_ON,
+ Ast.cmpop.Eq,
+ condition
+ )
+ )
+
+ override def switchCaseFirstStart(condition: Ast.expr): Unit = {
+ if (switchIfs) {
+ out.puts(s"if (${switchCmpExpr(condition)})")
+ out.puts("{")
+ out.inc
+ } else {
+ switchCaseStart(condition)
+ }
+ }
override def switchCaseStart(condition: Ast.expr): Unit = {
- out.puts(s"case ${expression(condition)}: {")
- out.inc
+ if (switchIfs) {
+ out.puts(s"else if (${switchCmpExpr(condition)})")
+ out.puts("{")
+ out.inc
+ } else {
+ out.puts(s"case ${expression(condition)}: {")
+ out.inc
+ }
}
override def switchCaseEnd(): Unit = {
- out.puts("break;")
- out.dec
- out.puts("}")
+ if (switchIfs) {
+ out.dec
+ out.puts("}")
+ } else {
+ out.puts("break;")
+ out.dec
+ out.puts("}")
+ }
}
override def switchElseStart(): Unit = {
- out.puts("default: {")
- out.inc
+ if (switchIfs) {
+ out.puts("else")
+ out.puts("{")
+ out.inc
+ } else {
+ out.puts("default: {")
+ out.inc
+ }
}
- override def switchEnd(): Unit =
+ override def switchEnd(): Unit = {
+ if (switchIfs)
+ out.dec
out.puts("}")
+ }
- override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = {
out.puts(s"private bool ${flagForInstName(attrName)};")
- out.puts(s"private ${kaitaiType2NativeType(attrType)} ${privateMemberName(attrName)};")
+ out.puts(s"private ${kaitaiType2NativeTypeNullable(attrType, isNullable)} ${privateMemberName(attrName)};")
}
- override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType): Unit = {
- out.puts(s"public ${kaitaiType2NativeType(dataType)} ${publicMemberName(instName)}")
+ override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"public ${kaitaiType2NativeTypeNullable(dataType, isNullable)} ${publicMemberName(instName)}")
out.puts("{")
out.inc
out.puts("get")
@@ -343,7 +508,7 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"return ${privateMemberName(instName)};")
}
- override def instanceCalculate(instName: InstanceIdentifier, dataType: DataType, value: expr): Unit =
+ override def instanceCalculate(instName: Identifier, dataType: DataType, value: expr): Unit =
// Perform explicit cast as unsigned integers can't be directly assigned to the default int type
handleAssignmentSimple(instName, s"(${kaitaiType2NativeType(dataType)}) (${expression(value)})")
@@ -391,13 +556,15 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case _ => s"_${idToStr(id)}"
}
}
+
+ override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}"
+
+ override def paramName(id: Identifier): String = s"p_${idToStr(id)}"
}
object CSharpCompiler extends LanguageCompilerStatic
with StreamStructNames
with UpperCamelCaseClasses {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig) = new CSharpTranslator(tp)
-
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
@@ -446,6 +613,18 @@ object CSharpCompiler extends LanguageCompilerStatic
}
}
+ def kaitaiType2NativeTypeNullable(t: DataType, isNullable: Boolean): String = {
+ val r = kaitaiType2NativeType(t)
+ if (isNullable) {
+ t match {
+ case _: NumericType | _: BooleanType => s"$r?"
+ case _ => r
+ }
+ } else {
+ r
+ }
+ }
+
def types2class(names: List[String]) = names.map(x => type2class(x)).mkString(".")
override def kstructName = "KaitaiStruct"
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala
index 1d0add50f..3ebf25e96 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala
@@ -1,13 +1,15 @@
package io.kaitai.struct.languages
import io.kaitai.struct._
-import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype.{CalcEndian, DataType, FixedEndian, InheritedEndian}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components._
-import io.kaitai.struct.translators.{CppTranslator, TypeDetector, TypeProvider}
+import io.kaitai.struct.translators.{CppTranslator, TypeDetector}
+
+import scala.collection.mutable.ListBuffer
class CppCompiler(
typeProvider: ClassTypeProvider,
@@ -20,18 +22,25 @@ class CppCompiler(
with EveryReadIsExpression {
import CppCompiler._
+ val importListSrc = new ImportList
+ val importListHdr = new ImportList
+
+ override val translator = new CppTranslator(typeProvider, importListSrc)
+ val outSrcHeader = new StringLanguageOutputWriter(indent)
+ val outHdrHeader = new StringLanguageOutputWriter(indent)
val outSrc = new StringLanguageOutputWriter(indent)
val outHdr = new StringLanguageOutputWriter(indent)
override def results(topClass: ClassSpec): Map[String, String] = {
val fn = topClass.nameAsStr
Map(
- s"$fn.cpp" -> outSrc.result,
- s"$fn.h" -> outHdr.result
+ s"$fn.cpp" -> (outSrcHeader.result + importListToStr(importListSrc) + outSrc.result),
+ s"$fn.h" -> (outHdrHeader.result + importListToStr(importListHdr) + outHdr.result)
)
}
- override def getStatic = CppCompiler
+ private def importListToStr(importList: ImportList): String =
+ importList.toList.map((x) => s"#include <$x>").mkString("", "\n", "\n")
sealed trait AccessMode
case object PrivateAccess extends AccessMode
@@ -43,24 +52,20 @@ class CppCompiler(
override def outFileName(topClassName: String): String = topClassName
override def fileHeader(topClassName: String): Unit = {
- outSrc.puts(s"// $headerComment")
- outSrc.puts
- outSrc.puts("#include \"" + outFileName(topClassName) + ".h\"")
- outSrc.puts
- outSrc.puts("#include ")
- outSrc.puts("#include ")
+ outSrcHeader.puts(s"// $headerComment")
+ outSrcHeader.puts
+ outSrcHeader.puts("#include \"" + outFileName(topClassName) + ".h\"")
+ outSrcHeader.puts
- outHdr.puts(s"#ifndef ${defineName(topClassName)}")
- outHdr.puts(s"#define ${defineName(topClassName)}")
- outHdr.puts
- outHdr.puts(s"// $headerComment")
- outHdr.puts
- outHdr.puts("#include ")
- outHdr.puts("#include ")
- outHdr.puts
- outHdr.puts("#include ")
- outHdr.puts("#include ") // TODO: add only if required
- outHdr.puts("#include ") // TODO: add only if required
+ outHdrHeader.puts(s"#ifndef ${defineName(topClassName)}")
+ outHdrHeader.puts(s"#define ${defineName(topClassName)}")
+ outHdrHeader.puts
+ outHdrHeader.puts(s"// $headerComment")
+ outHdrHeader.puts
+ outHdrHeader.puts("#include \"kaitai/kaitaistruct.h\"")
+ outHdrHeader.puts
+
+ importListHdr.add("stdint.h")
// API compatibility check
val minVer = KSVersion.minimalRuntime.toInt
@@ -113,27 +118,60 @@ class CppCompiler(
outHdr.puts(s"class ${types2class(name)};")
}
- override def classConstructorHeader(name: List[String], parentClassName: List[String], rootClassName: List[String]): Unit = {
+ override def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ val (endianSuffixHdr, endianSuffixSrc) = if (isHybrid) {
+ (", int p_is_le = -1", ", int p_is_le")
+ } else {
+ ("", "")
+ }
+
+ val paramsArg = Utils.join(params.map((p) =>
+ s"${kaitaiType2NativeType(p.dataType)} ${paramName(p.id)}"
+ ), "", ", ", ", ")
+
+ // Parameter names
+ val pIo = paramName(IoIdentifier)
+ val pParent = paramName(ParentIdentifier)
+ val pRoot = paramName(RootIdentifier)
+
+ // Types
+ val tIo = s"$kstreamName*"
+ val tParent = kaitaiType2NativeType(parentType)
+ val tRoot = s"${types2class(rootClassName)}*"
+
outHdr.puts
- outHdr.puts(s"${types2class(List(name.last))}(" +
- s"$kstreamName* p_io, " +
- s"${types2class(parentClassName)}* p_parent = 0, " +
- s"${types2class(rootClassName)}* p_root = 0);"
+ outHdr.puts(s"${types2class(List(name.last))}($paramsArg" +
+ s"$tIo $pIo, " +
+ s"$tParent $pParent = 0, " +
+ s"$tRoot $pRoot = 0$endianSuffixHdr);"
)
outSrc.puts
- outSrc.puts(s"${types2class(name)}::${types2class(List(name.last))}(" +
- s"$kstreamName *p_io, " +
- s"${types2class(parentClassName)} *p_parent, " +
- s"${types2class(rootClassName)} *p_root) : $kstructName(p_io) {"
+ outSrc.puts(s"${types2class(name)}::${types2class(List(name.last))}($paramsArg" +
+ s"$tIo $pIo, " +
+ s"$tParent $pParent, " +
+ s"$tRoot $pRoot$endianSuffixSrc) : $kstructName($pIo) {"
)
outSrc.inc
- handleAssignmentSimple(ParentIdentifier, "p_parent")
+ handleAssignmentSimple(ParentIdentifier, pParent)
handleAssignmentSimple(RootIdentifier, if (name == rootClassName) {
"this"
} else {
- "p_root"
+ pRoot
})
+
+ typeProvider.nowClass.meta.endian match {
+ case Some(_: CalcEndian) | Some(InheritedEndian) =>
+ ensureMode(PrivateAccess)
+ outHdr.puts("int m__is_le;")
+ handleAssignmentSimple(EndianIdentifier, if (isHybrid) "p_is_le" else "-1")
+ ensureMode(PublicAccess)
+ case _ =>
+ // no _is_le variable
+ }
+
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
}
override def classConstructorFooter: Unit = {
@@ -141,7 +179,7 @@ class CppCompiler(
outSrc.puts("}")
}
- override def classDestructorHeader(name: List[String], parentTypeName: List[String], topClassName: List[String]): Unit = {
+ override def classDestructorHeader(name: List[String], parentType: DataType, topClassName: List[String]): Unit = {
outHdr.puts(s"~${types2class(List(name.last))}();")
outSrc.puts
@@ -151,10 +189,51 @@ class CppCompiler(
override def classDestructorFooter = classConstructorFooter
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def runRead(): Unit = {
+ outSrc.puts("_read();")
+ }
+
+ override def runReadCalc(): Unit = {
+ outSrc.puts
+ outSrc.puts("if (m__is_le == -1) {")
+ outSrc.inc
+ importListSrc.add("stdexcept")
+ outSrc.puts("throw std::runtime_error(\"unable to decide on endianness\");")
+ outSrc.dec
+ outSrc.puts("} else if (m__is_le == 1) {")
+ outSrc.inc
+ outSrc.puts("_read_le();")
+ outSrc.dec
+ outSrc.puts("} else {")
+ outSrc.inc
+ outSrc.puts("_read_be();")
+ outSrc.dec
+ outSrc.puts("}")
+ }
+
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit = {
+ val suffix = endian match {
+ case Some(e) => s"_${e.toSuffix}"
+ case None => ""
+ }
+ ensureMode(PrivateAccess)
+ outHdr.puts(s"void _read$suffix();")
+ outSrc.puts
+ outSrc.puts(s"void ${types2class(typeProvider.nowClass.name)}::_read$suffix() {")
+ outSrc.inc
+ }
+
+ override def readFooter(): Unit = {
+ outSrc.dec
+ outSrc.puts("}")
+
+ ensureMode(PublicAccess)
+ }
+
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
ensureMode(PrivateAccess)
outHdr.puts(s"${kaitaiType2NativeType(attrType)} ${privateMemberName(attrName)};")
- declareNullFlag(attrName, condSpec)
+ declareNullFlag(attrName, isNullable)
}
def ensureMode(newMode: AccessMode): Unit = {
@@ -170,7 +249,7 @@ class CppCompiler(
}
}
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
ensureMode(PublicAccess)
outHdr.puts(s"${kaitaiType2NativeType(attrType)} ${publicMemberName(attrName)}() const { return ${privateMemberName(attrName)}; }")
}
@@ -197,79 +276,112 @@ class CppCompiler(
}
override def attrDestructor(attr: AttrLikeSpec, id: Identifier): Unit = {
- val t = attr.dataTypeComposite
-
- val checkFlags = attr match {
- case is: InstanceSpec =>
- val dataType = is.dataTypeComposite
- dataType match {
- case ut: UserType =>
- val checkLazy = calculatedFlagForName(id.asInstanceOf[InstanceIdentifier])
- val checkNull = if (is.cond.ifExpr.isDefined) {
- s" && !${nullFlagForName(id)}"
- } else {
- ""
- }
- outSrc.puts(s"if ($checkLazy$checkNull) {")
- outSrc.inc
- true
- case _ =>
- false
- }
- case as: AttrSpec =>
- as.dataType match {
- case ut: UserType =>
- if (as.cond.ifExpr.isDefined) {
- outSrc.puts(s"if (!${nullFlagForName(id)}) {")
- outSrc.inc
- true
- } else {
- false
- }
- case _ =>
- false
- }
+ val checkLazy = if (attr.isLazy) {
+ Some(calculatedFlagForName(id))
+ } else {
+ None
}
- t match {
- case ArrayType(_: UserTypeFromBytes) =>
- outSrc.puts(s"delete ${privateMemberName(RawIdentifier(id))};")
- case _ =>
- // no cleanup needed
+ val checkNull = if (attr.isNullableSwitchRaw) {
+ Some(s"!${nullFlagForName(id)}")
+ } else {
+ None
}
- t match {
- case ArrayType(el: UserType) =>
- val arrVar = privateMemberName(id)
- outSrc.puts(s"for (std::vector<${kaitaiType2NativeType(el)}>::iterator it = $arrVar->begin(); it != $arrVar->end(); ++it) {")
- outSrc.inc
- outSrc.puts("delete *it;")
- outSrc.dec
- outSrc.puts("}")
- case _ =>
- // no cleanup needed
- }
+ val checks: List[String] = List(checkLazy, checkNull).flatten
- t match {
- case _: UserTypeFromBytes =>
- outSrc.puts(s"delete ${privateMemberName(IoStorageIdentifier(RawIdentifier(id)))};")
- case _ =>
- // no cleanup needed
+ if (checks.nonEmpty) {
+ outSrc.puts(s"if (${checks.mkString(" && ")}) {")
+ outSrc.inc
}
- t match {
- case _: UserType | _: ArrayType =>
- outSrc.puts(s"delete ${privateMemberName(id)};")
- case _ =>
- // no cleanup needed
+ val (innerType, hasRaw) = attr.dataType match {
+ case ut: UserTypeFromBytes => (ut, true)
+ case st: SwitchType => (st.combinedType, st.hasSize)
+ case t => (t, false)
}
- if (checkFlags) {
+ destructMember(id, innerType, attr.isArray, hasRaw, hasRaw)
+
+ if (checks.nonEmpty) {
outSrc.dec
outSrc.puts("}")
}
}
+ def destructMember(id: Identifier, innerType: DataType, isArray: Boolean, hasRaw: Boolean, hasIO: Boolean): Unit = {
+ if (isArray) {
+ // raw is std::vector*, no need to delete its contents, but we
+ // need to clean up the vector pointer itself
+ if (hasRaw)
+ outSrc.puts(s"delete ${privateMemberName(RawIdentifier(id))};")
+
+ // IO is std::vector*, needs destruction of both members
+ // and the vector pointer itself
+ if (hasIO) {
+ val ioVar = privateMemberName(IoStorageIdentifier(RawIdentifier(id)))
+ destructVector(s"$kstreamName*", ioVar)
+ outSrc.puts(s"delete $ioVar;")
+ }
+
+ // main member contents
+ if (needsDestruction(innerType)) {
+ val arrVar = privateMemberName(id)
+
+ // C++ specific substitution: AnyType results from generic struct + raw bytes
+ // so we would assume that only generic struct needs to be cleaned up
+ val realType = innerType match {
+ case AnyType => KaitaiStructType
+ case _ => innerType
+ }
+
+ destructVector(kaitaiType2NativeType(realType), arrVar)
+ }
+
+ // main member is a std::vector of something, always needs destruction
+ outSrc.puts(s"delete ${privateMemberName(id)};")
+ } else {
+ // raw is just a string, no need to cleanup => we ignore `hasRaw`
+
+ // but hasIO is important
+ if (hasIO)
+ outSrc.puts(s"delete ${privateMemberName(IoStorageIdentifier(RawIdentifier(id)))};")
+
+ if (needsDestruction(innerType))
+ outSrc.puts(s"delete ${privateMemberName(id)};")
+ }
+ }
+
+ def needsDestruction(t: DataType): Boolean = t match {
+ case _: UserType | _: ArrayType | KaitaiStructType | AnyType => true
+ case _ => false
+ }
+
+ /**
+ * Generates std::vector contents destruction loop.
+ * @param elType element type, i.e. XXX in `std::vector<XXX>`
+ * @param arrVar variable name that holds pointer to std::vector
+ */
+ def destructVector(elType: String, arrVar: String): Unit = {
+ outSrc.puts(s"for (std::vector<$elType>::iterator it = $arrVar->begin(); it != $arrVar->end(); ++it) {")
+ outSrc.inc
+ outSrc.puts("delete *it;")
+ outSrc.dec
+ outSrc.puts("}")
+ }
+
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ outSrc.puts("if (m__is_le == 1) {")
+ outSrc.inc
+ leProc()
+ outSrc.dec
+ outSrc.puts("} else {")
+ outSrc.inc
+ beProc()
+ outSrc.dec
+ outSrc.puts("}")
+ }
+
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit =
outSrc.puts(s"${privateMemberName(attrName)} = $normalIO->ensure_fixed_contents($contents);")
@@ -293,13 +405,20 @@ class CppCompiler(
s"8 - (${expression(rotValue)})"
}
outSrc.puts(s"$destName = $kstreamName::process_rotate_left($srcName, $expr);")
+ case ProcessCustom(name, args) =>
+ val procClass = name.map((x) => type2class(x)).mkString("::")
+ val procName = s"_process_${idToStr(varSrc)}"
+
+ importListSrc.add(name.last + ".h")
+
+ outSrc.puts(s"$procClass $procName(${args.map(expression).mkString(", ")});")
+ outSrc.puts(s"$destName = $procName.decode($srcName);")
}
}
- override def allocateIO(varName: Identifier, rep: RepeatSpec): IoStorageIdentifier = {
- val memberName = privateMemberName(varName)
-
- val ioName = IoStorageIdentifier(varName)
+ override def allocateIO(id: Identifier, rep: RepeatSpec, extraAttrs: ListBuffer[AttrSpec]): String = {
+ val memberName = privateMemberName(id)
+ val ioId = IoStorageIdentifier(id)
val args = rep match {
case RepeatEos | RepeatExpr(_) => s"$memberName->at($memberName->size() - 1)"
@@ -307,7 +426,20 @@ class CppCompiler(
case NoRepeat => memberName
}
- outSrc.puts(s"${privateMemberName(ioName)} = new $kstreamName($args);")
+ val newStream = s"new $kstreamName($args)"
+
+ val (ioType, ioName) = rep match {
+ case NoRepeat =>
+ outSrc.puts(s"${privateMemberName(ioId)} = $newStream;")
+ (KaitaiStreamType, privateMemberName(ioId))
+ case _ =>
+ val localIO = s"io_${idToStr(id)}"
+ outSrc.puts(s"$kstreamName* $localIO = $newStream;")
+ outSrc.puts(s"${privateMemberName(ioId)}->push_back($localIO);")
+ (ArrayType(KaitaiStreamType), localIO)
+ }
+
+ Utils.addUniqueAttr(extraAttrs, AttrSpec(List(), ioId, ioType))
ioName
}
@@ -351,9 +483,16 @@ class CppCompiler(
}
override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean): Unit = {
- if (needRaw)
+ importListHdr.add("vector")
+
+ if (needRaw) {
outSrc.puts(s"${privateMemberName(RawIdentifier(id))} = new std::vector();")
+ outSrc.puts(s"${privateMemberName(IoStorageIdentifier(RawIdentifier(id)))} = new std::vector<$kstreamName*>();")
+ }
outSrc.puts(s"${privateMemberName(id)} = new std::vector<${kaitaiType2NativeType(dataType)}>();")
+ outSrc.puts("{")
+ outSrc.inc
+ outSrc.puts("int i = 0;")
outSrc.puts(s"while (!$io->is_eof()) {")
outSrc.inc
}
@@ -363,16 +502,25 @@ class CppCompiler(
}
override def condRepeatEosFooter: Unit = {
+ outSrc.puts("i++;")
+ outSrc.dec
+ outSrc.puts("}")
outSrc.dec
outSrc.puts("}")
}
override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: Ast.expr): Unit = {
+ importListHdr.add("vector")
+
val lenVar = s"l_${idToStr(id)}"
outSrc.puts(s"int $lenVar = ${expression(repeatExpr)};")
if (needRaw) {
- outSrc.puts(s"${privateMemberName(RawIdentifier(id))} = new std::vector();")
- outSrc.puts(s"${privateMemberName(RawIdentifier(id))}->reserve($lenVar);")
+ val rawId = privateMemberName(RawIdentifier(id))
+ outSrc.puts(s"$rawId = new std::vector();")
+ outSrc.puts(s"$rawId->reserve($lenVar);")
+ val ioId = privateMemberName(IoStorageIdentifier(RawIdentifier(id)))
+ outSrc.puts(s"$ioId = new std::vector<$kstreamName*>();")
+ outSrc.puts(s"$ioId->reserve($lenVar);")
}
outSrc.puts(s"${privateMemberName(id)} = new std::vector<${kaitaiType2NativeType(dataType)}>();")
outSrc.puts(s"${privateMemberName(id)}->reserve($lenVar);")
@@ -390,11 +538,16 @@ class CppCompiler(
}
override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
- if (needRaw)
+ importListHdr.add("vector")
+
+ if (needRaw) {
outSrc.puts(s"${privateMemberName(RawIdentifier(id))} = new std::vector();")
+ outSrc.puts(s"${privateMemberName(IoStorageIdentifier(RawIdentifier(id)))} = new std::vector<$kstreamName*>();")
+ }
outSrc.puts(s"${privateMemberName(id)} = new std::vector<${kaitaiType2NativeType(dataType)}>();")
outSrc.puts("{")
outSrc.inc
+ outSrc.puts("int i = 0;")
outSrc.puts(s"${kaitaiType2NativeType(dataType)} ${translator.doName("_")};")
outSrc.puts("do {")
outSrc.inc
@@ -412,6 +565,7 @@ class CppCompiler(
override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
typeProvider._currentIteratorType = Some(dataType)
+ outSrc.puts("i++;")
outSrc.dec
outSrc.puts(s"} while (!(${expression(untilExpr)}));")
outSrc.dec
@@ -422,10 +576,10 @@ class CppCompiler(
outSrc.puts(s"${privateMemberName(id)} = $expr;")
}
- override def parseExpr(dataType: DataType, io: String): String = {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
dataType match {
case t: ReadableType =>
- s"$io->read_${t.apiCall}()"
+ s"$io->read_${t.apiCall(defEndian)}()"
case blt: BytesLimitType =>
s"$io->read_bytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -437,6 +591,7 @@ class CppCompiler(
case BitsType(width: Int) =>
s"$io->read_bits_int($width)"
case t: UserType =>
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), "", ", ", ", ")
val addArgs = if (t.isOpaque) {
""
} else {
@@ -445,9 +600,13 @@ class CppCompiler(
case Some(fp) => translator.translate(fp)
case None => "this"
}
- s", $parent, ${privateMemberName(RootIdentifier)}"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => ", m__is_le"
+ case _ => ""
+ }
+ s", $parent, ${privateMemberName(RootIdentifier)}$addEndian"
}
- s"new ${types2class(t.name)}($io$addArgs)"
+ s"new ${types2class(t.name)}($addParams$io$addArgs)"
}
}
@@ -492,7 +651,7 @@ class CppCompiler(
outSrc.puts(s"if (on == ${expression(condition)}) {")
outSrc.inc
} else {
- outSrc.puts(s"case ${expression(condition)}:")
+ outSrc.puts(s"case ${expression(condition)}: {")
outSrc.inc
}
}
@@ -502,7 +661,7 @@ class CppCompiler(
outSrc.puts(s"else if (on == ${expression(condition)}) {")
outSrc.inc
} else {
- outSrc.puts(s"case ${expression(condition)}:")
+ outSrc.puts(s"case ${expression(condition)}: {")
outSrc.inc
}
}
@@ -514,6 +673,7 @@ class CppCompiler(
} else {
outSrc.puts("break;")
outSrc.dec
+ outSrc.puts("}")
}
}
@@ -522,7 +682,7 @@ class CppCompiler(
outSrc.puts("else {")
outSrc.inc
} else {
- outSrc.puts("default:")
+ outSrc.puts("default: {")
outSrc.inc
}
}
@@ -537,14 +697,14 @@ class CppCompiler(
override def switchBytesOnlyAsRaw = true
- override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = {
ensureMode(PrivateAccess)
outHdr.puts(s"bool ${calculatedFlagForName(attrName)};")
outHdr.puts(s"${kaitaiType2NativeType(attrType)} ${privateMemberName(attrName)};")
- declareNullFlag(attrName, condSpec)
+ declareNullFlag(attrName, isNullable)
}
- override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType): Unit = {
+ override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
ensureMode(PublicAccess)
outHdr.puts(s"${kaitaiType2NativeType(dataType)} ${publicMemberName(instName)}();")
@@ -569,7 +729,7 @@ class CppCompiler(
outSrc.puts(s"return ${privateMemberName(instName)};")
}
- override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, String)]): Unit = {
+ override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
val enumClass = types2class(List(enumName))
outHdr.puts
@@ -578,12 +738,12 @@ class CppCompiler(
if (enumColl.size > 1) {
enumColl.dropRight(1).foreach { case (id, label) =>
- outHdr.puts(s"${value2Const(enumName, label)} = $id,")
+ outHdr.puts(s"${value2Const(enumName, label.name)} = $id,")
}
}
enumColl.last match {
case (id, label) =>
- outHdr.puts(s"${value2Const(enumName, label)} = $id")
+ outHdr.puts(s"${value2Const(enumName, label.name)} = $id")
}
outHdr.dec
@@ -649,15 +809,25 @@ class CppCompiler(
def defineName(className: String) = className.toUpperCase + "_H_"
- def calculatedFlagForName(ksName: InstanceIdentifier) =
- s"f_${ksName.name}"
+ /**
+ * Returns name of a member that stores "calculated flag" for a given lazy
+ * attribute. That is, if it's true, then calculation have already taken
+ * place and we need to return already calculated member in a getter, or,
+ * if it's false, we need to calculate / parse it first.
+ * @param ksName attribute ID
+ * @return calculated flag member name associated with it
+ */
+ def calculatedFlagForName(ksName: Identifier) =
+ s"f_${idToStr(ksName)}"
- def nullFlagForName(ksName: Identifier) = {
- ksName match {
- case NamedIdentifier(name) => s"n_$name"
- case InstanceIdentifier(name) => s"n_$name"
- }
- }
+ /**
+ * Returns name of a member that stores "null flag" for a given attribute,
+ * that is, if it's true, then associated attribute is null.
+ * @param ksName attribute ID
+ * @return null flag member name associated with it
+ */
+ def nullFlagForName(ksName: Identifier) =
+ s"n_${idToStr(ksName)}"
override def idToStr(id: Identifier): String = {
id match {
@@ -674,8 +844,12 @@ class CppCompiler(
override def publicMemberName(id: Identifier): String = idToStr(id)
- def declareNullFlag(attrName: Identifier, condSpec: ConditionalSpec) = {
- if (condSpec.ifExpr.nonEmpty) {
+ override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}"
+
+ override def paramName(id: Identifier): String = s"p_${idToStr(id)}"
+
+ def declareNullFlag(attrName: Identifier, isNullable: Boolean) = {
+ if (isNullable) {
outHdr.puts(s"bool ${nullFlagForName(attrName)};")
ensureMode(PublicAccess)
outHdr.puts(s"bool _is_null_${idToStr(attrName)}() { ${publicMemberName(attrName)}(); return ${nullFlagForName(attrName)}; };")
@@ -687,7 +861,6 @@ class CppCompiler(
}
object CppCompiler extends LanguageCompilerStatic with StreamStructNames {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig) = new CppTranslator(tp)
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala
new file mode 100644
index 000000000..430ff940b
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala
@@ -0,0 +1,494 @@
+package io.kaitai.struct.languages
+
+import io.kaitai.struct.datatype.{DataType, FixedEndian}
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format._
+import io.kaitai.struct.languages.components._
+import io.kaitai.struct.translators.{GoTranslator, TranslatorResult, TypeDetector}
+import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, Utils}
+
+class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
+ extends LanguageCompiler(typeProvider, config)
+ with SingleOutputFile
+ with UpperCamelCaseClasses
+ with ObjectOrientedLanguage
+ with UniversalFooter
+ with UniversalDoc
+ with AllocateIOLocalVar
+ with GoReads
+ with FixedContentsUsingArrayByteLiteral {
+ import GoCompiler._
+
+ override val translator = new GoTranslator(out, typeProvider, importList)
+
+ override def innerClasses = false
+
+ override def universalFooter: Unit = {
+ out.dec
+ out.puts("}")
+ }
+
+ override def indent: String = "\t"
+ override def outFileName(topClassName: String): String =
+ s"src/${config.goPackage}/$topClassName.go"
+
+ override def outImports(topClass: ClassSpec) = {
+ val imp = importList.toList
+ imp.size match {
+ case 0 => ""
+ case 1 => "import \"" + imp.head + "\"\n"
+ case _ =>
+ "import (\n" +
+ imp.map((x) => indent + "\"" + x + "\"").mkString("", "\n", "\n") +
+ ")\n"
+ }
+ }
+
+ override def fileHeader(topClassName: String): Unit = {
+ outHeader.puts(s"// $headerComment")
+ if (!config.goPackage.isEmpty) {
+ outHeader.puts
+ outHeader.puts(s"package ${config.goPackage}")
+ }
+ outHeader.puts
+
+ importList.add("github.com/kaitai-io/kaitai_struct_go_runtime/kaitai")
+
+ out.puts
+ }
+
+ override def classHeader(name: List[String]): Unit = {
+ out.puts(s"type ${types2class(name)} struct {")
+ out.inc
+ }
+
+ override def classFooter(name: List[String]): Unit = universalFooter
+
+ override def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ out.puts
+ out.puts(
+ s"func (this *${types2class(name)}) Read(" +
+ s"io *$kstreamName, " +
+ s"parent ${kaitaiType2NativeType(parentType)}, " +
+ s"root *${types2class(rootClassName)}) (err error) {"
+ )
+ out.inc
+ out.puts(s"${privateMemberName(IoIdentifier)} = io")
+ out.puts(s"${privateMemberName(ParentIdentifier)} = parent")
+ out.puts(s"${privateMemberName(RootIdentifier)} = root")
+ out.puts
+ }
+
+ override def classConstructorFooter: Unit = {
+ out.puts("return err")
+ universalFooter
+ }
+
+ override def runRead(): Unit = {}
+ override def runReadCalc(): Unit = ???
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit = {}
+ override def readFooter(): Unit = {}
+
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"${idToStr(attrName)} ${kaitaiType2NativeType(attrType)}")
+ translator.returnRes = None
+ }
+
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}
+
+ override def universalDoc(doc: DocSpec): Unit = {
+ out.puts
+ out.puts( "/**")
+
+ doc.summary.foreach((summary) => out.putsLines(" * ", summary))
+
+ doc.ref match {
+ case TextRef(text) =>
+ out.putsLines(" * ", "@see \"" + text + "\"")
+ case ref: UrlRef =>
+ out.putsLines(" * ", s"@see ${ref.toAhref}")
+ case NoRef =>
+ // no reference => output nothing
+ }
+
+ out.puts( " */")
+ }
+
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = ???
+
+ override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit = {
+ out.puts(s"${privateMemberName(attrName)} = $normalIO.ensureFixedContents($contents);")
+ }
+
+ override def attrProcess(proc: ProcessExpr, varSrc: Identifier, varDest: Identifier): Unit = {
+ val srcName = privateMemberName(varSrc)
+ val destName = privateMemberName(varDest)
+
+ proc match {
+ case ProcessXor(xorValue) =>
+ out.puts(s"$destName = $kstreamName.processXor($srcName, ${expression(xorValue)});")
+ case ProcessZlib =>
+ out.puts(s"$destName = $kstreamName.processZlib($srcName);")
+ case ProcessRotate(isLeft, rotValue) =>
+ val expr = if (isLeft) {
+ expression(rotValue)
+ } else {
+ s"8 - (${expression(rotValue)})"
+ }
+ out.puts(s"$destName = $kstreamName.processRotateLeft($srcName, $expr, 1);")
+ }
+ }
+
+ override def allocateIO(varName: Identifier, rep: RepeatSpec): String = {
+ val javaName = privateMemberName(varName)
+
+ val ioName = idToStr(IoStorageIdentifier(varName))
+
+ val args = rep match {
+ case RepeatEos | RepeatExpr(_) => s"$javaName.get($javaName.size() - 1)"
+ case RepeatUntil(_) => translator.specialName(Identifier.ITERATOR2)
+ case NoRepeat => javaName
+ }
+
+ importList.add("bytes")
+
+ out.puts(s"$ioName := kaitai.NewStream(bytes.NewReader($args))")
+ ioName
+ }
+
+ override def useIO(ioEx: Ast.expr): String = {
+ out.puts(s"$kstreamName io = ${expression(ioEx)};")
+ "io"
+ }
+
+ override def pushPos(io: String): Unit = {
+ out.puts(s"_pos, err := $io.Pos()")
+ translator.outAddErrCheck()
+ }
+
+ override def seek(io: String, pos: Ast.expr): Unit = {
+ importList.add("io")
+
+ out.puts(s"_, err = $io.Seek(int64(${expression(pos)}), io.SeekStart)")
+ translator.outAddErrCheck()
+ }
+
+ override def popPos(io: String): Unit = {
+ importList.add("io")
+
+ out.puts(s"_, err = $io.Seek(_pos, io.SeekStart)")
+ translator.outAddErrCheck()
+ }
+
+ override def alignToByte(io: String): Unit =
+ out.puts(s"$io.AlignToByte()")
+
+ override def condIfHeader(expr: Ast.expr): Unit = {
+ out.puts(s"if (${expression(expr)}) {")
+ out.inc
+ }
+
+ override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean): Unit = {
+ if (needRaw)
+ out.puts(s"${privateMemberName(RawIdentifier(id))} = new ArrayList();")
+ //out.puts(s"${privateMemberName(id)} = make(${kaitaiType2NativeType(ArrayType(dataType))})")
+ out.puts(s"for !$io.EOF() {")
+ out.inc
+ }
+
+ override def handleAssignmentRepeatEos(id: Identifier, r: TranslatorResult): Unit = {
+ val name = privateMemberName(id)
+ val expr = translator.resToStr(r)
+ out.puts(s"$name = append($name, $expr)")
+ }
+
+ override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: Ast.expr): Unit = {
+ if (needRaw)
+ out.puts(s"${privateMemberName(RawIdentifier(id))} = new ArrayList((int) (${expression(repeatExpr)}));")
+ out.puts(s"${privateMemberName(id)} = make(${kaitaiType2NativeType(ArrayType(dataType))}, ${expression(repeatExpr)})")
+ out.puts(s"for i := range ${privateMemberName(id)} {")
+ out.inc
+ }
+
+ override def handleAssignmentRepeatExpr(id: Identifier, r: TranslatorResult): Unit = {
+ val name = privateMemberName(id)
+ val expr = translator.resToStr(r)
+ out.puts(s"$name[i] = $expr")
+ }
+
+ override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: Ast.expr): Unit = {
+ if (needRaw)
+ out.puts(s"${privateMemberName(RawIdentifier(id))} = new ArrayList();")
+ out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayType(dataType))}();")
+ out.puts("{")
+ out.inc
+ out.puts(s"${kaitaiType2NativeType(dataType)} ${translator.specialName(Identifier.ITERATOR)};")
+ out.puts("do {")
+ out.inc
+ }
+
+ override def handleAssignmentRepeatUntil(id: Identifier, r: TranslatorResult, isRaw: Boolean): Unit = {
+ val expr = translator.resToStr(r)
+ val (typeDecl, tempVar) = if (isRaw) {
+ ("byte[] ", translator.specialName(Identifier.ITERATOR2))
+ } else {
+ ("", translator.specialName(Identifier.ITERATOR))
+ }
+ out.puts(s"$typeDecl$tempVar = $expr;")
+ out.puts(s"${privateMemberName(id)}.add($tempVar);")
+ }
+
+ override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: Ast.expr): Unit = {
+ typeProvider._currentIteratorType = Some(dataType)
+ out.dec
+ out.puts(s"} while (!(${expression(untilExpr)}));")
+ out.dec
+ out.puts("}")
+ }
+
+ override def handleAssignmentSimple(id: Identifier, r: TranslatorResult): Unit = {
+ val expr = translator.resToStr(r)
+ out.puts(s"${privateMemberName(id)} = $expr")
+ }
+
+ override def parseExpr(dataType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
+ dataType match {
+ case t: ReadableType =>
+ s"$io.Read${Utils.capitalize(t.apiCall(defEndian))}()"
+ case blt: BytesLimitType =>
+ s"$io.ReadBytes(int(${expression(blt.size)}))"
+ case _: BytesEosType =>
+ s"$io.ReadBytesFull()"
+ case BytesTerminatedType(terminator, include, consume, eosError, _) =>
+ s"$io.ReadBytesTerm($terminator, $include, $consume, $eosError)"
+ case BitsType1 =>
+ s"$io.ReadBitsInt(1) != 0"
+ case BitsType(width: Int) =>
+ s"$io.ReadBitsInt($width)"
+ case t: UserType =>
+ val addArgs = if (t.isOpaque) {
+ ""
+ } else {
+ val parent = t.forcedParent match {
+ case Some(USER_TYPE_NO_PARENT) => "null"
+ case Some(fp) => translator.translate(fp)
+ case None => "this"
+ }
+ s", $parent, _root"
+ }
+ s"${types2class(t.name)}($io$addArgs)"
+ }
+ }
+
+// override def bytesPadTermExpr(expr0: String, padRight: Option[Int], terminator: Option[Int], include: Boolean) = {
+// val expr1 = padRight match {
+// case Some(padByte) => s"$kstreamName.bytesStripRight($expr0, (byte) $padByte)"
+// case None => expr0
+// }
+// val expr2 = terminator match {
+// case Some(term) => s"$kstreamName.bytesTerminate($expr1, (byte) $term, $include)"
+// case None => expr1
+// }
+// expr2
+// }
+
+ override def switchStart(id: Identifier, on: Ast.expr): Unit =
+ out.puts(s"switch (${expression(on)}) {")
+
+ override def switchCaseStart(condition: Ast.expr): Unit = {
+ // Java is very specific about what can be used as "condition" in "case
+ // condition:".
+ val condStr = condition match {
+ case Ast.expr.EnumByLabel(enumName, enumVal) =>
+ // If switch is over a enum, only literal enum values are supported,
+ // and they must be written as "MEMBER", not "SomeEnum.MEMBER".
+ value2Const(enumVal.name)
+ case _ =>
+ expression(condition)
+ }
+
+ out.puts(s"case $condStr: {")
+ out.inc
+ }
+
+ override def switchCaseEnd(): Unit = {
+ out.puts("break;")
+ out.dec
+ out.puts("}")
+ }
+
+ override def switchElseStart(): Unit = {
+ out.puts("default: {")
+ out.inc
+ }
+
+ override def switchEnd(): Unit =
+ out.puts("}")
+
+ override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"${calculatedFlagForName(attrName)} bool")
+ out.puts(s"${idToStr(attrName)} ${kaitaiType2NativeType(attrType)}")
+ }
+
+ override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"func (this *${types2class(className)}) ${publicMemberName(instName)}() (v ${kaitaiType2NativeType(dataType)}, err error) {")
+ out.inc
+ translator.returnRes = Some(dataType match {
+ case _: IntType => "0"
+ case _: BooleanType => "false"
+ case _: StrType => "\"\""
+ case _ => "nil"
+ })
+ }
+
+ override def instanceCalculate(instName: Identifier, dataType: DataType, value: Ast.expr): Unit = {
+ val r = translator.translate(value)
+ val converted = dataType match {
+ case _: UserType => r
+ case _ => s"${kaitaiType2NativeType(dataType)}($r)"
+ }
+ out.puts(s"${privateMemberName(instName)} = $converted")
+ }
+
+ override def instanceCheckCacheAndReturn(instName: InstanceIdentifier): Unit = {
+ out.puts(s"if (this.${calculatedFlagForName(instName)}) {")
+ out.inc
+ instanceReturn(instName)
+ universalFooter
+ }
+
+ override def instanceReturn(instName: InstanceIdentifier): Unit = {
+ out.puts(s"return ${privateMemberName(instName)}, nil")
+ }
+
+ override def instanceSetCalculated(instName: InstanceIdentifier): Unit =
+ out.puts(s"this.${calculatedFlagForName(instName)} = true")
+
+ override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
+ val enumClass = type2class(enumName)
+
+ out.puts
+ out.puts(s"public enum $enumClass {")
+ out.inc
+
+ if (enumColl.size > 1) {
+ enumColl.dropRight(1).foreach { case (id, label) =>
+ out.puts(s"${value2Const(label.name)}($id),")
+ }
+ }
+ enumColl.last match {
+ case (id, label) =>
+ out.puts(s"${value2Const(label.name)}($id);")
+ }
+
+ out.puts
+ out.puts("private final long id;")
+ out.puts(s"$enumClass(long id) { this.id = id; }")
+ out.puts("public long id() { return id; }")
+ out.puts(s"private static final Map byId = new HashMap(${enumColl.size});")
+ out.puts("static {")
+ out.inc
+ out.puts(s"for ($enumClass e : $enumClass.values())")
+ out.inc
+ out.puts(s"byId.put(e.id(), e);")
+ out.dec
+ out.dec
+ out.puts("}")
+ out.puts(s"public static $enumClass byId(long id) { return byId.get(id); }")
+ out.dec
+ out.puts("}")
+ }
+
+ def value2Const(s: String) = s.toUpperCase
+
+ def idToStr(id: Identifier): String = {
+ id match {
+ case SpecialIdentifier(name) => name
+ case NamedIdentifier(name) => Utils.upperCamelCase(name)
+ case NumberedIdentifier(idx) => s"_${NumberedIdentifier.TEMPLATE}$idx"
+ case InstanceIdentifier(name) => Utils.lowerCamelCase(name)
+ case RawIdentifier(innerId) => "_raw_" + idToStr(innerId)
+ case IoStorageIdentifier(innerId) => "_io_" + idToStr(innerId)
+ }
+ }
+
+ override def privateMemberName(id: Identifier): String = s"this.${idToStr(id)}"
+
+ override def publicMemberName(id: Identifier): String = {
+ id match {
+ case IoIdentifier => "_IO"
+ case RootIdentifier => "_Root"
+ case ParentIdentifier => "_Parent"
+ case NamedIdentifier(name) => Utils.upperCamelCase(name)
+ case NumberedIdentifier(idx) => s"_${NumberedIdentifier.TEMPLATE}$idx"
+ case InstanceIdentifier(name) => Utils.upperCamelCase(name)
+ case RawIdentifier(innerId) => "_raw_" + idToStr(innerId)
+ }
+ }
+
+ override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}"
+
+ def calculatedFlagForName(id: Identifier) = s"_f_${idToStr(id)}"
+}
+
+object GoCompiler extends LanguageCompilerStatic
+ with UpperCamelCaseClasses
+ with StreamStructNames {
+
+ override def getCompiler(
+ tp: ClassTypeProvider,
+ config: RuntimeConfig
+ ): LanguageCompiler = new GoCompiler(tp, config)
+
+ /**
+ * Determine Go data type corresponding to a KS data type.
+ *
+ * @param attrType KS data type
+ * @return Go data type
+ */
+ def kaitaiType2NativeType(attrType: DataType): String = {
+ attrType match {
+ case Int1Type(false) => "uint8"
+ case IntMultiType(false, Width2, _) => "uint16"
+ case IntMultiType(false, Width4, _) => "uint32"
+ case IntMultiType(false, Width8, _) => "uint64"
+
+ case Int1Type(true) => "int8"
+ case IntMultiType(true, Width2, _) => "int16"
+ case IntMultiType(true, Width4, _) => "int32"
+ case IntMultiType(true, Width8, _) => "int64"
+
+ case FloatMultiType(Width4, _) => "float32"
+ case FloatMultiType(Width8, _) => "float64"
+
+ case BitsType(_) => "uint64"
+
+ case _: BooleanType => "bool"
+ case CalcIntType => "int"
+ case CalcFloatType => "float64"
+
+ case _: StrType => "string"
+ case _: BytesType => "[]byte"
+
+ case AnyType => "interface{}"
+ case KaitaiStreamType => "*" + kstreamName
+ case KaitaiStructType => kstructName
+
+ case t: UserType => "*" + types2class(t.classSpec match {
+ case Some(cs) => cs.name
+ case None => t.name
+ })
+ case EnumType(name, _) => types2class(name)
+
+ case ArrayType(inType) => s"[]${kaitaiType2NativeType(inType)}"
+
+ case SwitchType(_, cases) => kaitaiType2NativeType(TypeDetector.combineTypes(cases.values))
+ }
+ }
+
+ def types2class(names: List[String]) = names.map(x => type2class(x)).mkString("_")
+
+ override def kstreamName: String = "kaitai.Stream"
+ override def kstructName: String = "interface{}"
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala
index 22570285f..b973b15f8 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala
@@ -1,13 +1,13 @@
package io.kaitai.struct.languages
-import io.kaitai.struct.datatype.DataType
+import io.kaitai.struct._
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype.{CalcEndian, DataType, FixedEndian, InheritedEndian}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components._
-import io.kaitai.struct.translators.{JavaTranslator, TypeDetector, TypeProvider}
-import io.kaitai.struct._
+import io.kaitai.struct.translators.{JavaTranslator, TypeDetector}
class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
extends LanguageCompiler(typeProvider, config)
@@ -22,7 +22,18 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
with NoNeedForFullClassPath {
import JavaCompiler._
- override def getStatic = JavaCompiler
+ val translator = new JavaTranslator(typeProvider, importList)
+
+ // Preprocess fromFileClass and make import
+ val fromFileClass = {
+ val pos = config.javaFromFileClass.lastIndexOf('.')
+ if (pos < 0) {
+ config.javaFromFileClass
+ } else {
+ importList.add(config.javaFromFileClass)
+ config.javaFromFileClass.substring(pos + 1)
+ }
+ }
override def universalFooter: Unit = {
out.dec
@@ -33,22 +44,20 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def outFileName(topClassName: String): String =
s"src/${config.javaPackage.replace('.', '/')}/${type2class(topClassName)}.java"
+ override def outImports(topClass: ClassSpec) =
+ "\n" + importList.toList.map((x) => s"import $x;").mkString("\n") + "\n"
+
override def fileHeader(topClassName: String): Unit = {
- out.puts(s"// $headerComment")
+ outHeader.puts(s"// $headerComment")
if (!config.javaPackage.isEmpty) {
- out.puts
- out.puts(s"package ${config.javaPackage};")
+ outHeader.puts
+ outHeader.puts(s"package ${config.javaPackage};")
}
- out.puts
- out.puts(s"import io.kaitai.struct.$kstructName;")
- out.puts(s"import io.kaitai.struct.$kstreamName;")
- out.puts
- out.puts("import java.io.IOException;")
- out.puts("import java.util.Arrays;")
- out.puts("import java.util.ArrayList;")
- out.puts("import java.util.HashMap;")
- out.puts("import java.util.Map;")
- out.puts("import java.nio.charset.Charset;")
+
+ // Used in every class
+ importList.add(s"io.kaitai.struct.$kstructName")
+ importList.add(s"io.kaitai.struct.$kstreamName")
+ importList.add("java.io.IOException")
out.puts
}
@@ -69,69 +78,128 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts("public Map> _arrStart = new HashMap>();")
out.puts("public Map> _arrEnd = new HashMap>();")
out.puts
+
+ importList.add("java.util.ArrayList")
+ importList.add("java.util.HashMap")
+ importList.add("java.util.Map")
}
- out.puts(s"public static ${type2class(name)} fromFile(String fileName) throws IOException {")
- out.inc
- out.puts(s"return new ${type2class(name)}(new $kstreamName(fileName));")
- out.dec
- out.puts("}")
+ val isInheritedEndian = typeProvider.nowClass.meta.endian match {
+ case Some(InheritedEndian) => true
+ case _ => false
+ }
+
+ // fromFile helper makes no sense for inherited endianness structures:
+ // they require endianness to be parsed anyway
+ if (!isInheritedEndian && !config.javaFromFileClass.isEmpty && typeProvider.nowClass.params.isEmpty) {
+ out.puts(s"public static ${type2class(name)} fromFile(String fileName) throws IOException {")
+ out.inc
+ out.puts(s"return new ${type2class(name)}(new $fromFileClass(fileName));")
+ out.dec
+ out.puts("}")
+ }
}
- override def classConstructorHeader(name: String, parentClassName: String, rootClassName: String): Unit = {
+ override def classConstructorHeader(name: String, parentType: DataType, rootClassName: String, isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ typeProvider.nowClass.meta.endian match {
+ case Some(_: CalcEndian) | Some(InheritedEndian) =>
+ out.puts("private Boolean _is_le;")
+ case _ =>
+ // no _is_le variable
+ }
+
+ val paramsArg = Utils.join(params.map((p) =>
+ s"${kaitaiType2JavaType(p.dataType)} ${paramName(p.id)}"
+ ), ", ", ", ", "")
+
+ if (isHybrid) {
+ // Inherited endian classes can be only internal, so they have mandatory 4th argument
+ // and 1..3-argument constructors don't make sense
+
+ out.puts
+ out.puts(s"public ${type2class(name)}($kstreamName _io, ${kaitaiType2JavaType(parentType)} _parent, ${type2class(rootClassName)} _root, boolean _is_le$paramsArg) {")
+ out.inc
+ out.puts("super(_io);")
+ out.puts("this._parent = _parent;")
+ out.puts("this._root = _root;")
+ out.puts("this._is_le = _is_le;")
+ } else {
+ // Normal 3 constructors, chained into the last
+
+ val paramsRelay = Utils.join(params.map((p) => paramName(p.id)), ", ", ", ", "")
+
+ out.puts
+ out.puts(s"public ${type2class(name)}($kstreamName _io$paramsArg) {")
+ out.inc
+ out.puts(s"this(_io, null, null$paramsRelay);")
+ out.dec
+ out.puts("}")
+
+ out.puts
+ out.puts(s"public ${type2class(name)}($kstreamName _io, ${kaitaiType2JavaType(parentType)} _parent$paramsArg) {")
+ out.inc
+ out.puts(s"this(_io, _parent, null$paramsRelay);")
+ out.dec
+ out.puts("}")
+
+ out.puts
+ out.puts(s"public ${type2class(name)}($kstreamName _io, ${kaitaiType2JavaType(parentType)} _parent, ${type2class(rootClassName)} _root$paramsArg) {")
+ out.inc
+ out.puts("super(_io);")
+ out.puts("this._parent = _parent;")
+ if (name == rootClassName) {
+ out.puts("this._root = _root == null ? this : _root;")
+ } else {
+ out.puts("this._root = _root;")
+ }
+ }
+
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
+ }
+
+ override def runRead(): Unit =
+ out.puts("_read();")
+
+ override def runReadCalc(): Unit = {
out.puts
- out.puts(s"public ${type2class(name)}($kstreamName _io) {")
+ out.puts("if (_is_le == null) {")
out.inc
- out.puts("super(_io);")
- if (name == rootClassName)
- out.puts("this._root = this;")
- if (!debug)
- out.puts("_read();")
+ out.puts(s"throw new $kstreamName.UndecidedEndiannessError();")
out.dec
- out.puts("}")
-
- out.puts
- out.puts(s"public ${type2class(name)}($kstreamName _io, ${type2class(parentClassName)} _parent) {")
+ out.puts("} else if (_is_le) {")
out.inc
- out.puts("super(_io);")
- out.puts("this._parent = _parent;")
- if (name == rootClassName)
- out.puts("this._root = this;")
- if (!debug)
- out.puts("_read();")
+ out.puts("_readLE();")
out.dec
- out.puts("}")
-
- out.puts
- out.puts(s"public ${type2class(name)}($kstreamName _io, ${type2class(parentClassName)} _parent, ${type2class(rootClassName)} _root) {")
+ out.puts("} else {")
out.inc
- out.puts("super(_io);")
- out.puts("this._parent = _parent;")
- out.puts("this._root = _root;")
- if (!debug)
- out.puts("_read();")
+ out.puts("_readBE();")
out.dec
out.puts("}")
+ }
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean) = {
val readAccessAndType = if (debug) {
"public"
} else {
"private"
}
- out.puts(s"$readAccessAndType void _read() {")
+ val suffix = endian match {
+ case Some(e) => s"${e.toSuffix.toUpperCase}"
+ case None => ""
+ }
+ out.puts(s"$readAccessAndType void _read$suffix() {")
out.inc
}
- override def classConstructorFooter: Unit = {
- universalFooter
- }
+ override def readFooter(): Unit = universalFooter
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
- out.puts(s"private ${kaitaiType2JavaType(attrType, condSpec)} ${idToStr(attrName)};")
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"private ${kaitaiType2JavaType(attrType, isNullable)} ${idToStr(attrName)};")
}
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
- out.puts(s"public ${kaitaiType2JavaType(attrType, condSpec)} ${idToStr(attrName)}() { return ${idToStr(attrName)}; }")
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"public ${kaitaiType2JavaType(attrType, isNullable)} ${idToStr(attrName)}() { return ${idToStr(attrName)}; }")
}
override def universalDoc(doc: DocSpec): Unit = {
@@ -152,6 +220,18 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts( " */")
}
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts("if (_is_le) {")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("} else {")
+ out.inc
+ beProc()
+ out.dec
+ out.puts("}")
+ }
+
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit = {
out.puts(s"${privateMemberName(attrName)} = $normalIO.ensureFixedContents($contents);")
}
@@ -172,6 +252,14 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
s"8 - (${expression(rotValue)})"
}
out.puts(s"$destName = $kstreamName.processRotateLeft($srcName, $expr, 1);")
+ case ProcessCustom(name, args) =>
+ val namespace = name.init.mkString(".")
+ val procClass = namespace +
+ (if (namespace.nonEmpty) "." else "") +
+ type2class(name.last)
+ val procName = s"_process_${idToStr(varSrc)}"
+ out.puts(s"$procClass $procName = new $procClass(${args.map(expression).mkString(", ")});")
+ out.puts(s"$destName = $procName.decode($srcName);")
}
}
@@ -186,7 +274,8 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case NoRepeat => javaName
}
- out.puts(s"$kstreamName $ioName = new $kstreamName($args);")
+ importList.add("io.kaitai.struct.ByteBufferKaitaiStream")
+ out.puts(s"$kstreamName $ioName = new ByteBufferKaitaiStream($args);")
ioName
}
@@ -259,20 +348,35 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = new ArrayList();")
out.puts(s"${privateMemberName(id)} = new ${kaitaiType2JavaType(ArrayType(dataType))}();")
+ out.puts("{")
+ out.inc
+ out.puts("int i = 0;")
out.puts(s"while (!$io.isEof()) {")
out.inc
+
+ importList.add("java.util.ArrayList")
}
override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit = {
out.puts(s"${privateMemberName(id)}.add($expr);")
}
+ override def condRepeatEosFooter: Unit = {
+ out.puts("i++;")
+ out.dec
+ out.puts("}")
+ out.dec
+ out.puts("}")
+ }
+
override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: expr): Unit = {
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = new ArrayList(Long.valueOf(${expression(repeatExpr)}).intValue());")
out.puts(s"${idToStr(id)} = new ${kaitaiType2JavaType(ArrayType(dataType))}(Long.valueOf(${expression(repeatExpr)}).intValue());")
out.puts(s"for (int i = 0; i < ${expression(repeatExpr)}; i++) {")
out.inc
+
+ importList.add("java.util.ArrayList")
}
override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit = {
@@ -286,8 +390,11 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts("{")
out.inc
out.puts(s"${kaitaiType2JavaType(dataType)} ${translator.doName("_")};")
+ out.puts("int i = 0;")
out.puts("do {")
out.inc
+
+ importList.add("java.util.ArrayList")
}
override def handleAssignmentRepeatUntil(id: Identifier, expr: String, isRaw: Boolean): Unit = {
@@ -302,6 +409,7 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
typeProvider._currentIteratorType = Some(dataType)
+ out.puts("i++;")
out.dec
out.puts(s"} while (!(${expression(untilExpr)}));")
out.dec
@@ -314,10 +422,10 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def handleAssignmentTempVar(dataType: DataType, id: String, expr: String): Unit =
out.puts(s"${kaitaiType2JavaType(dataType)} $id = $expr;")
- override def parseExpr(dataType: DataType, io: String): String = {
- dataType match {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
+ val expr = dataType match {
case t: ReadableType =>
- s"$io.read${Utils.capitalize(t.apiCall)}()"
+ s"$io.read${Utils.capitalize(t.apiCall(defEndian))}()"
case blt: BytesLimitType =>
s"$io.readBytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -337,9 +445,20 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case Some(fp) => translator.translate(fp)
case None => "this"
}
- s", $parent, _root"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => ", _is_le"
+ case _ => ""
+ }
+ s", $parent, _root$addEndian"
}
- s"new ${types2class(t.name)}($io$addArgs)"
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), ", ", ", ", "")
+ s"new ${types2class(t.name)}($io$addArgs$addParams)"
+ }
+
+ if (assignType != dataType) {
+ s"(${kaitaiType2JavaType(assignType)}) ($expr)"
+ } else {
+ expr
}
}
@@ -358,44 +477,104 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def userTypeDebugRead(id: String): Unit =
out.puts(s"$id._read();")
- override def switchStart(id: Identifier, on: Ast.expr): Unit =
- out.puts(s"switch (${expression(on)}) {")
+ /**
+ * Designates switch mode. If false, we're doing real switch-case for this
+ * attribute. If true, we're doing if-based emulation.
+ */
+ var switchIfs = false
+
+ val NAME_SWITCH_ON = Ast.expr.Name(Ast.identifier(Identifier.SWITCH_ON))
- override def switchCaseStart(condition: Ast.expr): Unit = {
- // Java is very specific about what can be used as "condition" in "case
- // condition:".
- val condStr = condition match {
- case Ast.expr.EnumByLabel(enumName, enumVal) =>
- // If switch is over a enum, only literal enum values are supported,
- // and they must be written as "MEMBER", not "SomeEnum.MEMBER".
- value2Const(enumVal.name)
- case _ =>
- expression(condition)
+ override def switchStart(id: Identifier, on: Ast.expr): Unit = {
+ val onType = translator.detectType(on)
+ typeProvider._currentSwitchType = Some(onType)
+
+ // Determine switching mode for this construct based on type
+ switchIfs = onType match {
+ case _: IntType | _: EnumType | _: StrType => false
+ case _ => true
}
- out.puts(s"case $condStr: {")
- out.inc
+ if (switchIfs) {
+ out.puts("{")
+ out.inc
+ out.puts(s"${kaitaiType2JavaType(onType)} ${expression(NAME_SWITCH_ON)} = ${expression(on)};")
+ } else {
+ out.puts(s"switch (${expression(on)}) {")
+ }
+ }
+
+ def switchCmpExpr(condition: Ast.expr): String =
+ expression(
+ Ast.expr.Compare(
+ NAME_SWITCH_ON,
+ Ast.cmpop.Eq,
+ condition
+ )
+ )
+
+ override def switchCaseFirstStart(condition: Ast.expr): Unit = {
+ if (switchIfs) {
+ out.puts(s"if (${switchCmpExpr(condition)}) {")
+ out.inc
+ } else {
+ switchCaseStart(condition)
+ }
+ }
+
+ override def switchCaseStart(condition: Ast.expr): Unit = {
+ if (switchIfs) {
+ out.puts(s"else if (${switchCmpExpr(condition)}) {")
+ out.inc
+ } else {
+ // Java is very specific about what can be used as "condition" in "case
+ // condition:".
+ val condStr = condition match {
+ case Ast.expr.EnumByLabel(_, enumVal) =>
+ // If switch is over a enum, only literal enum values are supported,
+ // and they must be written as "MEMBER", not "SomeEnum.MEMBER".
+ value2Const(enumVal.name)
+ case _ =>
+ expression(condition)
+ }
+
+ out.puts(s"case $condStr: {")
+ out.inc
+ }
}
override def switchCaseEnd(): Unit = {
- out.puts("break;")
- out.dec
- out.puts("}")
+ if (switchIfs) {
+ out.dec
+ out.puts("}")
+ } else {
+ out.puts("break;")
+ out.dec
+ out.puts("}")
+ }
}
override def switchElseStart(): Unit = {
- out.puts("default: {")
- out.inc
+ if (switchIfs) {
+ out.puts("else {")
+ out.inc
+ } else {
+ out.puts("default: {")
+ out.inc
+ }
}
- override def switchEnd(): Unit =
+ override def switchEnd(): Unit = {
+ if (switchIfs)
+ out.dec
out.puts("}")
+ }
- override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = {
out.puts(s"private ${kaitaiType2JavaTypeBoxed(attrType)} ${idToStr(attrName)};")
}
- override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType): Unit = {
+ override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
out.puts(s"public ${kaitaiType2JavaTypeBoxed(dataType)} ${idToStr(instName)}() {")
out.inc
}
@@ -411,7 +590,7 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"return ${privateMemberName(instName)};")
}
- override def instanceCalculate(instName: InstanceIdentifier, dataType: DataType, value: expr): Unit = {
+ override def instanceCalculate(instName: Identifier, dataType: DataType, value: expr): Unit = {
val primType = kaitaiType2JavaTypePrim(dataType)
val boxedType = kaitaiType2JavaTypeBoxed(dataType)
@@ -461,6 +640,9 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"public static $enumClass byId(long id) { return byId.get(id); }")
out.dec
out.puts("}")
+
+ importList.add("java.util.Map")
+ importList.add("java.util.HashMap")
}
override def debugClassSequence(seq: List[AttrSpec]) = {
@@ -483,13 +665,13 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def privateMemberName(id: Identifier): String = s"this.${idToStr(id)}"
override def publicMemberName(id: Identifier) = idToStr(id)
+
+ override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}"
}
object JavaCompiler extends LanguageCompilerStatic
with UpperCamelCaseClasses
with StreamStructNames {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig) = new JavaTranslator(tp)
-
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
@@ -497,8 +679,8 @@ object JavaCompiler extends LanguageCompilerStatic
def kaitaiType2JavaType(attrType: DataType): String = kaitaiType2JavaTypePrim(attrType)
- def kaitaiType2JavaType(attrType: DataType, condSpec: ConditionalSpec): String =
- if (condSpec.ifExpr.nonEmpty) {
+ def kaitaiType2JavaType(attrType: DataType, isNullable: Boolean): String =
+ if (isNullable) {
kaitaiType2JavaTypeBoxed(attrType)
} else {
kaitaiType2JavaTypePrim(attrType)
@@ -544,7 +726,7 @@ object JavaCompiler extends LanguageCompilerStatic
case ArrayType(_) => kaitaiType2JavaTypeBoxed(attrType)
- case SwitchType(_, cases) => kaitaiType2JavaTypePrim(TypeDetector.combineTypes(cases.values))
+ case st: SwitchType => kaitaiType2JavaTypePrim(st.combinedType)
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala
index 3fd99a440..f3d5fb3f7 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala
@@ -1,13 +1,13 @@
package io.kaitai.struct.languages
-import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype.{DataType, FixedEndian, InheritedEndian}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components._
-import io.kaitai.struct.translators.{JavaScriptTranslator, TypeProvider}
-import io.kaitai.struct.{ClassTypeProvider, LanguageOutputWriter, RuntimeConfig, Utils}
+import io.kaitai.struct.translators.JavaScriptTranslator
+import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, Utils}
class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
extends LanguageCompiler(typeProvider, config)
@@ -20,45 +20,45 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
with FixedContentsUsingArrayByteLiteral {
import JavaScriptCompiler._
- override def getStatic = JavaScriptCompiler
+ override val translator = new JavaScriptTranslator(typeProvider)
override def indent: String = " "
override def outFileName(topClassName: String): String = s"${type2class(topClassName)}.js"
+ override def outImports(topClass: ClassSpec) = {
+ val impList = importList.toList
+ val quotedImpList = impList.map((x) => s"'$x'")
+ val defineArgs = quotedImpList.mkString(", ")
+ val moduleArgs = quotedImpList.map((x) => s"require($x)").mkString(", ")
+ val argClasses = impList.map((x) => x.split('/').last)
+ val rootArgs = argClasses.map((x) => s"root.$x").mkString(", ")
+
+ "(function (root, factory) {\n" +
+ indent + "if (typeof define === 'function' && define.amd) {\n" +
+ indent * 2 + s"define([$defineArgs], factory);\n" +
+ indent + "} else if (typeof module === 'object' && module.exports) {\n" +
+ indent * 2 + s"module.exports = factory($moduleArgs);\n" +
+ indent + "} else {\n" +
+ indent * 2 + s"root.${types2class(topClass.name)} = factory($rootArgs);\n" +
+ indent + "}\n" +
+ s"}(this, function (${argClasses.mkString(", ")}) {"
+ }
+
override def fileHeader(topClassName: String): Unit = {
- out.puts(s"// $headerComment")
+ outHeader.puts(s"// $headerComment")
+ outHeader.puts
+
+ importList.add("kaitai-struct/KaitaiStream")
}
override def fileFooter(name: String): Unit = {
- out.puts
- out.puts("// Export for amd environments")
- out.puts("if (typeof define === 'function' && define.amd) {")
- out.inc
- out.puts(s"define('${type2class(name)}', [], function() {")
- out.inc
out.puts(s"return ${type2class(name)};")
- out.dec
- out.puts("});")
- out.dec
- out.puts("}")
-
- out.puts
-
- out.puts("// Export for CommonJS")
- out.puts("if (typeof module === 'object' && module && module.exports) {")
- out.inc
- out.puts(s"module.exports = ${type2class(name)};")
- out.dec
- out.puts("}")
+ out.puts("}));")
}
override def opaqueClassDeclaration(classSpec: ClassSpec): Unit = {
- val typeName = classSpec.name.head
- out.puts
- out.puts("if (typeof require === 'function')")
- out.inc
- out.puts(s"var ${type2class(typeName)} = require('./${outFileName(typeName)}');")
- out.dec
+ val className = type2class(classSpec.name.head)
+ importList.add(s"./$className")
}
override def classHeader(name: List[String]): Unit = {
@@ -82,22 +82,31 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts("})();")
}
- override def classConstructorHeader(name: List[String], parentClassName: List[String], rootClassName: List[String]): Unit = {
- out.puts(s"function ${type2class(name.last)}(_io, _parent, _root) {")
+ override def classConstructorHeader(name: List[String], parentClassName: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ val endianSuffix = if (isHybrid) {
+ ", _is_le"
+ } else {
+ ""
+ }
+
+ val paramsList = Utils.join(params.map((p) => paramName(p.id)), ", ", ", ", "")
+
+ out.puts(s"function ${type2class(name.last)}(_io, _parent, _root$endianSuffix$paramsList) {")
out.inc
out.puts("this._io = _io;")
out.puts("this._parent = _parent;")
out.puts("this._root = _root || this;")
+
+ if (isHybrid)
+ out.puts("this._is_le = _is_le;")
+
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
+
if (debug) {
out.puts("this._debug = {};")
- out.dec
- out.puts("}")
- out.puts
- out.puts(s"${type2class(name.last)}.prototype._read = function() {")
- out.inc
- } else {
- out.puts
}
+ out.puts
}
override def classConstructorFooter: Unit = {
@@ -105,9 +114,44 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts("}")
}
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {}
+ override def runRead(): Unit = {
+ out.puts("this._read();")
+ }
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {}
+ override def runReadCalc(): Unit = {
+ out.puts
+ out.puts(s"if (this._is_le === true) {")
+ out.inc
+ out.puts("this._readLE();")
+ out.dec
+ out.puts("} else if (this._is_le === false) {")
+ out.inc
+ out.puts("this._readBE();")
+ out.dec
+ out.puts("} else {")
+ out.inc
+ out.puts("throw new KaitaiStream.UndecidedEndiannessError();")
+ out.dec
+ out.puts("}")
+ }
+
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean) = {
+ val suffix = endian match {
+ case Some(e) => e.toSuffix.toUpperCase
+ case None => ""
+ }
+ out.puts(s"${type2class(typeProvider.nowClass.name.last)}.prototype._read$suffix = function() {")
+ out.inc
+ }
+
+ override def readFooter() = {
+ out.dec
+ out.puts("}")
+ }
+
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}
+
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}
override def universalDoc(doc: DocSpec): Unit = {
// JSDoc docstring style: http://usejsdoc.org/about-getting-started.html
@@ -128,6 +172,18 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts( " */")
}
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts("if (this._is_le) {")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("} else {")
+ out.inc
+ beProc()
+ out.dec
+ out.puts("}")
+ }
+
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit = {
out.puts(s"${privateMemberName(attrName)} = " +
s"$normalIO.ensureFixedContents($contents);")
@@ -153,6 +209,15 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
s"8 - (${expression(rotValue)})"
}
out.puts(s"$destName = $kstreamName.processRotateLeft($srcName, $expr, 1);")
+ case ProcessCustom(name, args) =>
+ val nameInit = name.init
+ val pkgName = if (nameInit.isEmpty) "" else nameInit.mkString("-") + "/"
+ val procClass = type2class(name.last)
+
+ importList.add(s"$pkgName$procClass")
+
+ out.puts(s"var _process = new $procClass(${args.map(expression).mkString(", ")});")
+ out.puts(s"$destName = _process.decode($srcName);")
}
}
@@ -232,6 +297,7 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"${privateMemberName(id)} = [];")
if (debug)
out.puts(s"this._debug.${idToStr(id)}.arr = [];")
+ out.puts("var i = 0;")
out.puts(s"while (!$io.isEof()) {")
out.inc
}
@@ -241,6 +307,7 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
override def condRepeatEosFooter: Unit = {
+ out.puts("i++;")
out.dec
out.puts("}")
}
@@ -270,6 +337,7 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"${privateMemberName(id)} = []")
if (debug)
out.puts(s"this._debug.${idToStr(id)}.arr = [];")
+ out.puts("var i = 0;")
out.puts("do {")
out.inc
}
@@ -282,6 +350,7 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
typeProvider._currentIteratorType = Some(dataType)
+ out.puts("i++;")
out.dec
out.puts(s"} while (!(${expression(untilExpr)}));")
}
@@ -293,10 +362,10 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def handleAssignmentTempVar(dataType: DataType, id: String, expr: String): Unit =
out.puts(s"var $id = $expr;")
- override def parseExpr(dataType: DataType, io: String): String = {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
dataType match {
case t: ReadableType =>
- s"$io.read${Utils.capitalize(t.apiCall)}()"
+ s"$io.read${Utils.capitalize(t.apiCall(defEndian))}()"
case blt: BytesLimitType =>
s"$io.readBytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -308,8 +377,18 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case BitsType(width: Int) =>
s"$io.readBitsInt($width)"
case t: UserType =>
- val addArgs = if (t.isOpaque) "" else ", this, this._root"
- s"new ${type2class(t.name.last)}($io$addArgs)"
+ val parent = t.forcedParent match {
+ case Some(USER_TYPE_NO_PARENT) => "null"
+ case Some(fp) => translator.translate(fp)
+ case None => "this"
+ }
+ val root = if (t.isOpaque) "null" else "this._root"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => ", this._is_le"
+ case _ => ""
+ }
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), ", ", ", ", "")
+ s"new ${type2class(t.name.last)}($io, $parent, $root$addEndian$addParams)"
}
}
@@ -329,28 +408,88 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"$id._read();")
}
- override def switchStart(id: Identifier, on: Ast.expr): Unit =
- out.puts(s"switch (${expression(on)}) {")
+ /**
+ * Designates switch mode. If false, we're doing real switch-case for this
+ * attribute. If true, we're doing if-based emulation.
+ */
+ var switchIfs = false
+
+ val NAME_SWITCH_ON = Ast.expr.Name(Ast.identifier(Identifier.SWITCH_ON))
+
+ override def switchStart(id: Identifier, on: Ast.expr): Unit = {
+ val onType = translator.detectType(on)
+ typeProvider._currentSwitchType = Some(onType)
+
+ // Determine switching mode for this construct based on type
+ switchIfs = onType match {
+ case _: IntType | _: BooleanType | _: EnumType | _: StrType => false
+ case _ => true
+ }
+
+ if (switchIfs) {
+ out.puts("{")
+ out.inc
+ out.puts(s"var ${expression(NAME_SWITCH_ON)} = ${expression(on)};")
+ } else {
+ out.puts(s"switch (${expression(on)}) {")
+ }
+ }
+
+ def switchCmpExpr(condition: Ast.expr): String =
+ expression(
+ Ast.expr.Compare(
+ NAME_SWITCH_ON,
+ Ast.cmpop.Eq,
+ condition
+ )
+ )
+
+ override def switchCaseFirstStart(condition: Ast.expr): Unit = {
+ if (switchIfs) {
+ out.puts(s"if (${switchCmpExpr(condition)}) {")
+ out.inc
+ } else {
+ switchCaseStart(condition)
+ }
+ }
override def switchCaseStart(condition: Ast.expr): Unit = {
- out.puts(s"case ${expression(condition)}:")
- out.inc
+ if (switchIfs) {
+ out.puts(s"else if (${switchCmpExpr(condition)}) {")
+ out.inc
+ } else {
+ out.puts(s"case ${expression(condition)}:")
+ out.inc
+ }
}
override def switchCaseEnd(): Unit = {
- out.puts("break;")
- out.dec
+ if (switchIfs) {
+ out.dec
+ out.puts("}")
+ } else {
+ out.puts("break;")
+ out.dec
+ }
}
override def switchElseStart(): Unit = {
- out.puts("default:")
- out.inc
+ if (switchIfs) {
+ out.puts("else {")
+ out.inc
+ } else {
+ out.puts("default:")
+ out.inc
+ }
}
- override def switchEnd(): Unit =
+ override def switchEnd(): Unit = {
+ if (switchIfs)
+ out.dec
out.puts("}")
+ }
- override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType): Unit = {
+ override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
out.puts(s"Object.defineProperty(${type2class(className.last)}.prototype, '${publicMemberName(instName)}', {")
out.inc
out.puts("get: function() {")
@@ -375,16 +514,26 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"return ${privateMemberName(instName)};")
}
- override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, String)]): Unit = {
+ override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
out.puts(s"${type2class(curClass.last)}.${type2class(enumName)} = Object.freeze({")
out.inc
+
+ // Name to ID mapping
enumColl.foreach { case (id, label) =>
- out.puts(s"${enumValue(enumName, label)}: $id,")
+ out.puts(s"${enumValue(enumName, label.name)}: $id,")
}
out.puts
+
+ // ID to name mapping
enumColl.foreach { case (id, label) =>
- out.puts(s"""$id: "${enumValue(enumName, label)}",""")
+ val idStr = if (id < 0) {
+ "\"" + id.toString + "\""
+ } else {
+ id.toString
+ }
+ out.puts(s"""$idStr: "${enumValue(enumName, label.name)}",""")
}
+
out.dec
out.puts("});")
out.puts
@@ -416,6 +565,8 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
}
+ override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}"
+
private
def attrDebugNeeded(attrId: Identifier) = attrId match {
case _: NamedIdentifier | _: NumberedIdentifier | _: InstanceIdentifier => true
@@ -436,7 +587,6 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
object JavaScriptCompiler extends LanguageCompilerStatic
with UpperCamelCaseClasses
with StreamStructNames {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig) = new JavaScriptTranslator(tp)
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala
new file mode 100644
index 000000000..ebba1a88c
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala
@@ -0,0 +1,407 @@
+package io.kaitai.struct.languages
+
+import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, Utils}
+import io.kaitai.struct.datatype.{DataType, FixedEndian, InheritedEndian}
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format._
+import io.kaitai.struct.languages.components._
+import io.kaitai.struct.translators.LuaTranslator
+
+class LuaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
+ extends LanguageCompiler(typeProvider, config)
+ with AllocateIOLocalVar
+ with EveryReadIsExpression
+ with FixedContentsUsingArrayByteLiteral
+ with ObjectOrientedLanguage
+ with SingleOutputFile
+ with UniversalDoc
+ with UniversalFooter
+ with UpperCamelCaseClasses {
+
+ import LuaCompiler._
+
+ override val translator = new LuaTranslator(typeProvider, importList)
+
+ override def innerClasses = false
+ override def innerEnums = true
+
+ override def indent: String = " "
+ override def outFileName(topClassName: String): String = s"$topClassName.lua"
+ override def outImports(topClass: ClassSpec) =
+ importList.toList.mkString("", "\n", "\n")
+
+ override def opaqueClassDeclaration(classSpec: ClassSpec): Unit =
+ out.puts("require(\"" + classSpec.name.head + "\")")
+
+ override def fileHeader(topClassName: String): Unit = {
+ outHeader.puts(s"-- $headerComment")
+ outHeader.puts("--")
+ outHeader.puts("-- This file is compatible with Lua 5.3")
+ outHeader.puts
+
+ importList.add("local class = require(\"class\")")
+ importList.add("require(\"kaitaistruct\")")
+
+ out.puts
+ }
+
+ override def universalFooter: Unit =
+ out.puts
+
+ override def universalDoc(doc: DocSpec): Unit = {
+ val docStr = doc.summary match {
+ case Some(summary) =>
+ val lastChar = summary.last
+ if (lastChar == '.' || lastChar == '\n') {
+ summary
+ } else {
+ summary + "."
+ }
+ case None =>
+ ""
+ }
+ val extraNewLine = if (docStr.isEmpty || docStr.last == '\n') "" else "\n"
+ val refStr = doc.ref match {
+ case TextRef(text) =>
+ s"See also: $text"
+ case UrlRef(url, text) =>
+ s"See also: $text ($url)"
+ case NoRef =>
+ ""
+ }
+
+ out.putsLines("-- ", "\n" + docStr + extraNewLine + refStr)
+ }
+
+ override def classHeader(name: List[String]): Unit = {
+ out.puts(s"${types2class(name)} = class.class($kstructName)")
+ out.puts
+ }
+ override def classFooter(name: List[String]): Unit =
+ universalFooter
+ override def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ val endianAdd = if (isHybrid) ", is_le" else ""
+ val paramsList = Utils.join(params.map((p) => paramName(p.id)), "", ", ", ", ")
+
+ out.puts(s"function ${types2class(name)}:_init($paramsList" + s"io, parent, root$endianAdd)")
+ out.inc
+ out.puts(s"$kstructName._init(self, io)")
+ out.puts("self._parent = parent")
+ out.puts("self._root = root or self")
+ if (isHybrid)
+ out.puts("self._is_le = is_le")
+
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
+ }
+ override def classConstructorFooter: Unit = {
+ out.dec
+ out.puts("end")
+ out.puts
+ }
+
+ override def runRead(): Unit =
+ out.puts("self:_read()")
+ override def runReadCalc(): Unit = {
+ out.puts
+ out.puts(s"if self._is_le then")
+ out.inc
+ out.puts("self:_read_le()")
+ out.dec
+ out.puts(s"elseif not self._is_le then")
+ out.inc
+ out.puts("self:_read_be()")
+ out.dec
+ out.puts("else")
+ out.inc
+ out.puts("error(\"unable to decide endianness\")")
+ out.dec
+ out.puts("end")
+ }
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit = {
+ val suffix = endian match {
+ case Some(e) => s"_${e.toSuffix}"
+ case None => ""
+ }
+
+ out.puts(s"function ${types2class(typeProvider.nowClass.name)}:_read$suffix()")
+ out.inc
+ }
+ override def readFooter(): Unit = {
+ out.dec
+ out.puts("end")
+ out.puts
+ }
+
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit =
+ {}
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit =
+ {}
+
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts("if self._is_le then")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("else")
+ out.inc
+ beProc()
+ out.dec
+ out.puts("end")
+ }
+
+ override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit =
+ out.puts(s"${privateMemberName(attrName)} = self._io:ensure_fixed_contents($contents)")
+
+ override def condIfHeader(expr: Ast.expr): Unit = {
+ out.puts(s"if ${expression(expr)} then")
+ out.inc
+ }
+ override def condIfFooter(expr: Ast.expr): Unit = {
+ out.dec
+ out.puts("end")
+ }
+
+ override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean): Unit = {
+ if (needRaw)
+ out.puts(s"${privateMemberName(RawIdentifier(id))} = {}")
+ out.puts(s"${privateMemberName(id)} = {}")
+ out.puts("local i = 1")
+ out.puts(s"while not $io:is_eof() do")
+ out.inc
+ }
+ override def condRepeatEosFooter: Unit = {
+ out.puts("i = i + 1")
+ out.dec
+ out.puts("end")
+ }
+
+ override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: Ast.expr): Unit = {
+ if (needRaw)
+ out.puts(s"${privateMemberName(RawIdentifier(id))} = {}")
+ out.puts(s"${privateMemberName(id)} = {}")
+ out.puts(s"for i = 1, ${expression(repeatExpr)} do")
+ out.inc
+ }
+ override def condRepeatExprFooter: Unit = {
+ out.dec
+ out.puts("end")
+ }
+
+ override def condRepeatUntilHeader(id: Identifier, io: String, datatype: DataType, needRaw: Boolean, repeatExpr: Ast.expr): Unit = {
+ if (needRaw)
+ out.puts(s"${privateMemberName(RawIdentifier(id))} = {}")
+ out.puts(s"${privateMemberName(id)} = {}")
+ out.puts("local i = 1")
+ out.puts("while true do")
+ out.inc
+ }
+ override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: Ast.expr): Unit = {
+ typeProvider._currentIteratorType = Some(dataType)
+ out.puts(s"if ${expression(untilExpr)} then")
+ out.inc
+ out.puts("break")
+ out.dec
+ out.puts("end")
+ out.puts("i = i + 1")
+ out.dec
+ out.puts("end")
+ out.dec
+ }
+
+ override def attrProcess(proc: ProcessExpr, varSrc: Identifier, varDest: Identifier): Unit = {
+ val srcName = privateMemberName(varSrc)
+ val destName = privateMemberName(varDest)
+
+ proc match {
+ case ProcessXor(xorValue) =>
+ val procName = translator.detectType(xorValue) match {
+ case _: IntType => "process_xor_one"
+ case _: BytesType => "process_xor_many"
+ }
+ out.puts(s"$destName = $kstreamName.$procName($srcName, ${expression(xorValue)})")
+ case ProcessZlib =>
+ throw new RuntimeException("Lua zlib not supported")
+ case ProcessRotate(isLeft, rotValue) =>
+ val expr = if (isLeft) {
+ expression(rotValue)
+ } else {
+ s"8 - (${expression(rotValue)})"
+ }
+ out.puts(s"$destName = $kstreamName.process_rotate_left($srcName, $expr, 1)")
+ case ProcessCustom(name, args) =>
+ val procName = s"_process_${idToStr(varSrc)}"
+
+ importList.add("require(\"" + s"${name.last}" + "\")")
+
+ out.puts(s"local $procName = ${types2class(name)}(${args.map(expression).mkString(", ")})")
+ out.puts(s"$destName = $procName:decode($srcName)")
+ }
+ }
+
+ override def useIO(ioEx: Ast.expr): String = {
+ out.puts(s"local _io = ${expression(ioEx)}")
+ "_io"
+ }
+ override def pushPos(io:String): Unit =
+ out.puts(s"local _pos = $io:pos()")
+ override def seek(io: String, pos: Ast.expr): Unit =
+ out.puts(s"$io:seek(${expression(pos)})")
+ override def popPos(io: String): Unit =
+ out.puts(s"$io:seek(_pos)")
+ override def alignToByte(io: String): Unit =
+ out.puts(s"$io:align_to_byte()")
+
+ override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
+ out.puts(s"${types2class(className)}.property.${publicMemberName(instName)} = {}")
+ out.puts(s"function ${types2class(className)}.property.${publicMemberName(instName)}:get()")
+ out.inc
+ }
+ override def instanceFooter: Unit = {
+ out.dec
+ out.puts("end")
+ out.puts
+ }
+ override def instanceCheckCacheAndReturn(instName: InstanceIdentifier): Unit = {
+ out.puts(s"if self.${idToStr(instName)} ~= nil then")
+ out.inc
+ instanceReturn(instName)
+ out.dec
+ out.puts("end")
+ out.puts
+ }
+ override def instanceReturn(instName: InstanceIdentifier): Unit =
+ out.puts(s"return ${privateMemberName(instName)}")
+
+ override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
+ importList.add("local enum = require(\"enum\")")
+
+ out.puts(s"${types2class(curClass)}.${type2class(enumName)} = enum.Enum {")
+ out.inc
+ enumColl.foreach { case (id, label) => out.puts(s"${label.name} = $id,") }
+ out.dec
+ out.puts("}")
+ out.puts
+ }
+
+ override def idToStr(id: Identifier): String = id match {
+ case SpecialIdentifier(name) => name
+ case NamedIdentifier(name) => name
+ case NumberedIdentifier(idx) => s"_${NumberedIdentifier.TEMPLATE}$idx"
+ case InstanceIdentifier(name) => s"_m_$name"
+ case RawIdentifier(innerId) => s"_raw_${idToStr(innerId)}"
+ }
+ override def privateMemberName(id: Identifier): String =
+ s"self.${idToStr(id)}"
+ override def publicMemberName(id: Identifier): String = id match {
+ case SpecialIdentifier(name) => name
+ case NamedIdentifier(name) => name
+ case InstanceIdentifier(name) => name
+ case RawIdentifier(innerId) => s"_raw_${publicMemberName(innerId)}"
+ }
+ override def localTemporaryName(id: Identifier): String =
+ s"_t_${idToStr(id)}"
+
+ override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit =
+ out.puts(s"${privateMemberName(id)}[i] = $expr")
+ override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit =
+ out.puts(s"${privateMemberName(id)}[i] = $expr")
+ override def handleAssignmentRepeatUntil(id: Identifier, expr: String, isRaw: Boolean): Unit = {
+ val tmpName = translator.doName(if (isRaw) Identifier.ITERATOR2 else Identifier.ITERATOR)
+ out.puts(s"$tmpName = $expr")
+ out.puts(s"${privateMemberName(id)}[i] = $tmpName")
+ }
+ override def handleAssignmentSimple(id: Identifier, expr: String): Unit =
+ out.puts(s"${privateMemberName(id)} = $expr")
+
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = dataType match {
+ case t: ReadableType =>
+ s"$io:read_${t.apiCall(defEndian)}()"
+ case blt: BytesLimitType =>
+ s"$io:read_bytes(${expression(blt.size)})"
+ case _: BytesEosType =>
+ s"$io:read_bytes_full()"
+ case BytesTerminatedType(terminator, include, consume, eosError, _) =>
+ s"$io:read_bytes_term($terminator, $include, $consume, $eosError)"
+ case BitsType1 =>
+ s"$io:read_bits_int(1)"
+ case BitsType(width: Int) =>
+ s"$io:read_bits_int($width)"
+ case t: UserType =>
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), "", ", ", ", ")
+ val addArgs = if (t.isOpaque) {
+ ""
+ } else {
+ val parent = t.forcedParent match {
+ case Some(fp) => translator.translate(fp)
+ case None => "self"
+ }
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => ", self._is_le"
+ case _ => ""
+ }
+ s", $parent, self._root$addEndian"
+ }
+ s"${types2class(t.classSpec.get.name)}($addParams$io$addArgs)"
+ }
+ override def bytesPadTermExpr(expr0: String, padRight: Option[Int], terminator: Option[Int], include: Boolean): String = {
+ val expr1 = padRight match {
+ case Some(padByte) => s"$kstreamName.bytes_strip_right($expr0, $padByte)"
+ case None => expr0
+ }
+ val expr2 = terminator match {
+ case Some(term) => s"$kstreamName.bytes_terminate($expr1, $term, $include)"
+ case None => expr1
+ }
+ expr2
+ }
+
+ override def switchStart(id: Identifier, on: Ast.expr): Unit =
+ out.puts(s"local _on = ${expression(on)}")
+ override def switchCaseFirstStart(condition: Ast.expr): Unit = {
+ out.puts(s"if _on == ${expression(condition)} then")
+ out.inc
+ }
+ override def switchCaseStart(condition: Ast.expr): Unit = {
+ out.puts(s"elseif _on == ${expression(condition)} then")
+ out.inc
+ }
+ override def switchCaseEnd(): Unit =
+ out.dec
+ override def switchElseStart(): Unit = {
+ out.puts("else")
+ out.inc
+ }
+ override def switchEnd(): Unit =
+ out.puts("end")
+
+ override def allocateIO(varName: Identifier, rep: RepeatSpec): String = {
+ val varStr = privateMemberName(varName)
+
+ val args = rep match {
+ case RepeatEos | RepeatUntil(_) => s"$varStr[#$varStr]"
+ case RepeatExpr(_) => s"$varStr[i]"
+ case NoRepeat => varStr
+ }
+
+ importList.add("local stringstream = require(\"string_stream\")")
+ out.puts(s"local _io = $kstreamName(stringstream($args))")
+ "_io"
+ }
+}
+
+object LuaCompiler extends LanguageCompilerStatic
+ with UpperCamelCaseClasses
+ with StreamStructNames {
+ override def getCompiler(
+ tp: ClassTypeProvider,
+ config: RuntimeConfig
+ ): LanguageCompiler = new LuaCompiler(tp, config)
+
+ override def kstructName: String = "KaitaiStruct"
+ override def kstreamName: String = "KaitaiStream"
+
+ def types2class(name: List[String]): String =
+ name.map(x => type2class(x)).mkString(".")
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala
index a5894f6ce..2bef260ad 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala
@@ -1,12 +1,12 @@
package io.kaitai.struct.languages
-import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype.{CalcEndian, DataType, FixedEndian, InheritedEndian}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.format.{NoRepeat, RepeatEos, RepeatExpr, RepeatSpec, _}
import io.kaitai.struct.languages.components._
-import io.kaitai.struct.translators.{PHPTranslator, TypeProvider}
-import io.kaitai.struct.{ClassTypeProvider, LanguageOutputWriter, RuntimeConfig, Utils}
+import io.kaitai.struct.translators.PHPTranslator
+import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, Utils}
class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
extends LanguageCompiler(typeProvider, config)
@@ -25,8 +25,6 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def innerEnums = false
- override def getStatic = PHPCompiler
-
override val translator: PHPTranslator = new PHPTranslator(typeProvider, config)
override def universalFooter: Unit = {
@@ -69,25 +67,80 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def classFooter(name: List[String]): Unit = universalFooter
- override def classConstructorHeader(name: List[String], parentClassName: List[String], rootClassName: List[String]): Unit = {
- out.puts
+ override def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ typeProvider.nowClass.meta.endian match {
+ case Some(_: CalcEndian) | Some(InheritedEndian) =>
+ out.puts("protected $_m__is_le;")
+ out.puts
+ case _ =>
+ // no _is_le variable
+ }
+
+ val endianAdd = if (isHybrid) ", $is_le = null" else ""
+
+ val paramsArg = Utils.join(params.map((p) =>
+ s"${kaitaiType2NativeType(p.dataType)} ${paramName(p.id)}"
+ ), "", ", ", ", ")
+
+ // Parameter names
+ val pIo = paramName(IoIdentifier)
+ val pParent = paramName(ParentIdentifier)
+ val pRoot = paramName(RootIdentifier)
+
+ // Types
+ val tIo = kstreamName
+ val tParent = kaitaiType2NativeType(parentType)
+ val tRoot = translator.types2classAbs(rootClassName)
+
out.puts(
- "public function __construct(" +
- kstreamName + " $io, " +
- translator.types2classAbs(parentClassName) + " $parent = null, " +
- translator.types2classAbs(rootClassName) + " $root = null) {"
+ s"public function __construct($paramsArg" +
+ s"$tIo $pIo, " +
+ s"$tParent $pParent = null, " +
+ s"$tRoot $pRoot = null" + endianAdd + ") {"
)
out.inc
- out.puts("parent::__construct($io, $parent, $root);")
- out.puts("$this->_parse();")
+ out.puts(s"parent::__construct($pIo, $pParent, $pRoot);")
+
+ if (isHybrid)
+ handleAssignmentSimple(EndianIdentifier, "$is_le")
+
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
+ }
+
+ override def runRead(): Unit =
+ out.puts("$this->_read();")
+
+ override def runReadCalc(): Unit = {
+ out.puts
+ out.puts("if (is_null($this->_m__is_le)) {")
+ out.inc
+ out.puts("throw new \\RuntimeException(\"Unable to decide on endianness\");")
+ out.dec
+ out.puts("} else if ($this->_m__is_le) {")
+ out.inc
+ out.puts("$this->_readLE();")
+ out.dec
+ out.puts("} else {")
+ out.inc
+ out.puts("$this->_readBE();")
out.dec
out.puts("}")
+ }
- out.puts("private function _parse() {")
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean) = {
+ val suffix = endian match {
+ case Some(e) => s"${e.toSuffix.toUpperCase}"
+ case None => ""
+ }
+ out.puts
+ out.puts(s"private function _read$suffix() {")
out.inc
}
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def readFooter(): Unit = universalFooter
+
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
attrName match {
case ParentIdentifier | RootIdentifier | IoIdentifier =>
// just ignore it for now
@@ -96,7 +149,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
}
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
attrName match {
case ParentIdentifier | RootIdentifier =>
// just ignore it for now
@@ -106,10 +159,24 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
override def universalDoc(doc: DocSpec): Unit = {
- out.puts
- out.puts( "/**")
- doc.summary.foreach((summary) => out.putsLines(" * ", summary))
- out.puts( " */")
+ if (doc.summary.isDefined) {
+ out.puts
+ out.puts("/**")
+ doc.summary.foreach((summary) => out.putsLines(" * ", summary))
+ out.puts(" */")
+ }
+ }
+
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts("if ($this->_m__is_le) {")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("} else {")
+ out.inc
+ beProc()
+ out.dec
+ out.puts("}")
}
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit =
@@ -135,6 +202,13 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
s"8 - (${expression(rotValue)})"
}
out.puts(s"$destName = $kstreamName::processRotateLeft($srcName, $expr, 1);")
+ case ProcessCustom(name, args) =>
+ val isAbsolute = name.length > 1
+ val procClass = name.map((x) => type2class(x)).mkString(
+ if (isAbsolute) "\\" else "", "\\", ""
+ )
+ out.puts(s"$$_process = new $procClass(${args.map(expression).mkString(", ")});")
+ out.puts(s"$destName = $$_process->decode($srcName);")
}
}
@@ -177,6 +251,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = [];")
out.puts(s"${privateMemberName(id)} = [];")
+ out.puts("$i = 0;")
out.puts(s"while (!$io->isEof()) {")
out.inc
}
@@ -185,6 +260,11 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"${privateMemberName(id)}[] = $expr;")
}
+ override def condRepeatEosFooter: Unit = {
+ out.puts("$i++;")
+ super.condRepeatEosFooter
+ }
+
override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: Ast.expr): Unit = {
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = [];")
@@ -202,6 +282,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = [];")
out.puts(s"${privateMemberName(id)} = [];")
+ out.puts("$i = 0;")
out.puts("do {")
out.inc
}
@@ -214,6 +295,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: Ast.expr): Unit = {
typeProvider._currentIteratorType = Some(dataType)
+ out.puts("$i++;")
out.dec
out.puts(s"} while (!(${expression(untilExpr)}));")
}
@@ -222,10 +304,10 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"${privateMemberName(id)} = $expr;")
}
- override def parseExpr(dataType: DataType, io: String): String = {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
dataType match {
case t: ReadableType =>
- s"$io->read${Utils.capitalize(t.apiCall)}()"
+ s"$io->read${Utils.capitalize(t.apiCall(defEndian))}()"
case blt: BytesLimitType =>
s"$io->readBytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -237,6 +319,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case BitsType(width: Int) =>
s"$io->readBitsInt($width)"
case t: UserType =>
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), "", ", ", ", ")
val addArgs = if (t.isOpaque) {
""
} else {
@@ -245,9 +328,13 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case Some(fp) => translator.translate(fp)
case None => "$this"
}
- s", $parent, ${privateMemberName(RootIdentifier)}"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => s", ${privateMemberName(EndianIdentifier)}"
+ case _ => ""
+ }
+ s", $parent, ${privateMemberName(RootIdentifier)}$addEndian"
}
- s"new ${translator.types2classAbs(t.classSpec.get.name)}($io$addArgs)"
+ s"new ${translator.types2classAbs(t.classSpec.get.name)}($addParams$io$addArgs)"
}
}
@@ -287,7 +374,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def switchEnd(): Unit = universalFooter
- override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType): Unit = {
+ override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
out.puts(s"public function ${idToStr(instName)}() {")
out.inc
}
@@ -303,10 +390,11 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"return ${privateMemberName(instName)};")
}
- override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, String)]): Unit = {
+ override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
classHeader(curClass ::: List(enumName), None)
enumColl.foreach { case (id, label) =>
- out.puts(s"const ${value2Const(label)} = $id;")
+ universalDoc(label.doc)
+ out.puts(s"const ${value2Const(label.name)} = $id;")
}
universalFooter
}
@@ -334,6 +422,10 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def publicMemberName(id: Identifier) = idToStr(id)
+ override def localTemporaryName(id: Identifier): String = s"$$_t_${idToStr(id)}"
+
+ override def paramName(id: Identifier): String = s"$$${idToStr(id)}"
+
/**
* Determine PHP data type corresponding to a KS data type. Currently unused due to
* problems with nullable types (which were introduced only in PHP 7.1).
@@ -350,10 +442,16 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case _: StrType | _: BytesType => "string"
- case t: UserType => translator.types2classAbs(t.classSpec.get.name)
+ case t: UserType => translator.types2classAbs(t.classSpec match {
+ case Some(cs) => cs.name
+ case None => t.name
+ })
case t: EnumType => "int"
case ArrayType(_) => "array"
+
+ case KaitaiStructType => kstructName
+ case KaitaiStreamType => kstreamName
}
}
}
@@ -361,7 +459,6 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
object PHPCompiler extends LanguageCompilerStatic
with StreamStructNames
with UpperCamelCaseClasses {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig): PHPTranslator = new PHPTranslator(tp, config)
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala
index 9e9131ed3..fdf020569 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala
@@ -1,6 +1,6 @@
package io.kaitai.struct.languages
-import io.kaitai.struct.datatype.DataType
+import io.kaitai.struct.datatype.{DataType, FixedEndian, InheritedEndian}
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
@@ -20,9 +20,9 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
import PerlCompiler._
- override def innerClasses = false
+ override val translator = new PerlTranslator(typeProvider, importList)
- override def getStatic: LanguageCompilerStatic = PerlCompiler
+ override def innerClasses = false
override def universalFooter: Unit = {
out.dec
@@ -32,14 +32,16 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def indent: String = " "
override def outFileName(topClassName: String): String = s"${type2class(topClassName)}.pm"
+ override def outImports(topClass: ClassSpec) =
+ importList.toList.map((x) => s"use $x;").mkString("", "\n", "\n")
+
override def fileHeader(topClassName: String): Unit = {
- out.puts(s"# $headerComment")
- out.puts
- out.puts("use strict;")
- out.puts("use warnings;")
- out.puts(s"use $packageName ${KSVersion.minimalRuntime.toPerlVersion};")
- out.puts("use Compress::Zlib;")
- out.puts("use Encode;")
+ outHeader.puts(s"# $headerComment")
+ outHeader.puts
+
+ importList.add("strict")
+ importList.add("warnings")
+ importList.add(s"$packageName ${KSVersion.minimalRuntime.toPerlVersion}")
}
override def fileFooter(topClassName: String): Unit = {
@@ -70,16 +72,22 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def classFooter(name: List[String]): Unit = {}
- override def classConstructorHeader(name: List[String], parentClassName: List[String], rootClassName: List[String]): Unit = {
+ override def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ val endianSuffix = if (isHybrid) ", $_is_le" else ""
+
out.puts
out.puts("sub new {")
out.inc
- out.puts("my ($class, $_io, $_parent, $_root) = @_;")
+ out.puts("my ($class, $_io, $_parent, $_root" + endianSuffix + ") = @_;")
out.puts(s"my $$self = $kstructName->new($$_io);")
out.puts
out.puts("bless $self, $class;")
- out.puts(s"${privateMemberName(ParentIdentifier)} = $$_parent;")
- out.puts(s"${privateMemberName(RootIdentifier)} = $$_root || $$self;")
+ handleAssignmentSimple(ParentIdentifier, "$_parent")
+ handleAssignmentSimple(RootIdentifier, "$_root || $self;")
+
+ if (isHybrid)
+ handleAssignmentSimple(EndianIdentifier, "$_is_le")
+
out.puts
}
@@ -89,9 +97,44 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
universalFooter
}
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {}
+ override def runRead(): Unit =
+ out.puts("$self->_read();")
+
+ override def runReadCalc(): Unit = {
+ val isLe = privateMemberName(EndianIdentifier)
+
+ out.puts(s"if (!(defined $isLe)) {")
+ out.inc
+ out.puts("die \"Unable to decide on endianness\";")
+ out.dec
+ out.puts(s"} elsif ($isLe) {")
+ out.inc
+ out.puts("$self->_read_le();")
+ out.dec
+ out.puts("} else {")
+ out.inc
+ out.puts("$self->_read_be();")
+ out.dec
+ out.puts("}")
+ }
+
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit = {
+ val suffix = endian match {
+ case Some(e) => s"_${e.toSuffix}"
+ case None => ""
+ }
+ out.puts
+ out.puts(s"sub _read$suffix {")
+ out.inc
+ out.puts("my ($self) = @_;")
+ out.puts
+ }
+
+ override def readFooter(): Unit = universalFooter
+
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
attrName match {
case RootIdentifier | ParentIdentifier =>
// ignore, they are already defined in KaitaiStruct class
@@ -107,6 +150,18 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
}
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts(s"if (${privateMemberName(EndianIdentifier)}) {")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("} else {")
+ out.inc
+ beProc()
+ out.dec
+ out.puts("}")
+ }
+
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit = {
out.puts(s"${privateMemberName(attrName)} = $normalIO->ensure_fixed_contents($contents);")
}
@@ -123,6 +178,7 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
s"$destName = $kstreamName::$procName($srcName, ${expression(xorValue)});"
case ProcessZlib =>
+ importList.add("Compress::Zlib")
s"$destName = Compress::Zlib::uncompress($srcName);"
case ProcessRotate(isLeft, rotValue) =>
val expr = if (isLeft) {
@@ -223,10 +279,10 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def handleAssignmentSimple(id: Identifier, expr: String): Unit =
out.puts(s"${privateMemberName(id)} = $expr;")
- override def parseExpr(dataType: DataType, io: String): String = {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
dataType match {
case t: ReadableType =>
- s"$io->read_${t.apiCall}()"
+ s"$io->read_${t.apiCall(defEndian)}()"
case blt: BytesLimitType =>
s"$io->read_bytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -245,7 +301,11 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case Some(fp) => translator.translate(fp)
case None => "$self"
}
- s", $parent, ${privateMemberName(RootIdentifier)}"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => s", ${privateMemberName(EndianIdentifier)}"
+ case _ => ""
+ }
+ s", $parent, ${privateMemberName(RootIdentifier)}$addEndian"
}
s"${types2class(t.classSpec.get.name)}->new($io$addArgs)"
}
@@ -296,7 +356,7 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
def onComparisonExpr(condition: Ast.expr) =
Ast.expr.Compare(Ast.expr.Name(Ast.identifier("_on")), Ast.cmpop.Eq, condition)
- override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType): Unit = {
+ override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
out.puts
out.puts(s"sub ${instName.name} {")
out.inc
@@ -311,11 +371,11 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"return ${privateMemberName(instName)};")
}
- override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, String)]): Unit = {
+ override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
out.puts
enumColl.foreach { case (id, label) =>
- out.puts(s"our ${enumValue(enumName, label)} = $id;")
+ out.puts(s"our ${enumValue(enumName, label.name)} = $id;")
}
}
@@ -335,6 +395,8 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def publicMemberName(id: Identifier): String = idToStr(id)
+ override def localTemporaryName(id: Identifier): String = s"$$_t_${idToStr(id)}"
+
def boolLiteral(b: Boolean): String = translator.doBoolLiteral(b)
def types2class(t: List[String]) = t.map(type2class).mkString("::")
@@ -343,7 +405,6 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
object PerlCompiler extends LanguageCompilerStatic
with UpperCamelCaseClasses
with StreamStructNames {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig) = new PerlTranslator(tp)
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala
index e74fdef3f..6026981fe 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala
@@ -1,13 +1,13 @@
package io.kaitai.struct.languages
+import io.kaitai.struct.datatype.{DataType, FixedEndian, InheritedEndian}
+import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
-import io.kaitai.struct.datatype.DataType
-import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components._
-import io.kaitai.struct.translators.{PythonTranslator, TypeProvider}
-import io.kaitai.struct.{ClassTypeProvider, LanguageOutputWriter, RuntimeConfig}
+import io.kaitai.struct.translators.PythonTranslator
+import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, StringLanguageOutputWriter, Utils}
class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
extends LanguageCompiler(typeProvider, config)
@@ -18,11 +18,14 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
with EveryReadIsExpression
with AllocateIOLocalVar
with FixedContentsUsingArrayByteLiteral
+ with UniversalDoc
with NoNeedForFullClassPath {
import PythonCompiler._
- override def getStatic = PythonCompiler
+ override val translator = new PythonTranslator(typeProvider, importList)
+
+ override def innerDocstrings = true
override def universalFooter: Unit = {
out.dec
@@ -32,16 +35,16 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def indent: String = " "
override def outFileName(topClassName: String): String = s"$topClassName.py"
+ override def outImports(topClass: ClassSpec) =
+ importList.toList.mkString("", "\n", "\n")
+
override def fileHeader(topClassName: String): Unit = {
- out.puts(s"# $headerComment")
- out.puts
- out.puts("import array")
- out.puts("import struct")
- out.puts("import zlib")
- out.puts("from enum import Enum")
- out.puts("from pkg_resources import parse_version")
- out.puts
- out.puts(s"from kaitaistruct import __version__ as ks_version, $kstructName, $kstreamName, BytesIO")
+ outHeader.puts(s"# $headerComment")
+ outHeader.puts
+
+ importList.add("from pkg_resources import parse_version")
+ importList.add(s"from kaitaistruct import __version__ as ks_version, $kstructName, $kstreamName, BytesIO")
+
out.puts
out.puts
@@ -63,7 +66,12 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def opaqueClassDeclaration(classSpec: ClassSpec): Unit = {
val name = classSpec.name.head
- out.puts(s"from $name import ${type2class(name)}")
+ val prefix = config.pythonPackage match {
+ case "" => ""
+ case "." => "."
+ case pkg => s"$pkg."
+ }
+ out.puts(s"from $prefix$name import ${type2class(name)}")
}
override def classHeader(name: String): Unit = {
@@ -71,38 +79,140 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.inc
}
- override def classConstructorHeader(name: String, parentClassName: String, rootClassName: String): Unit = {
- out.puts("def __init__(self, _io, _parent=None, _root=None):")
+ override def classConstructorHeader(name: String, parentType: DataType, rootClassName: String, isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ val endianAdd = if (isHybrid) ", _is_le=None" else ""
+ val paramsList = Utils.join(params.map((p) => paramName(p.id)), ", ", ", ", "")
+
+ out.puts(s"def __init__(self$paramsList, _io, _parent=None, _root=None$endianAdd):")
out.inc
out.puts("self._io = _io")
out.puts("self._parent = _parent")
out.puts("self._root = _root if _root else self")
+
+ if (isHybrid)
+ out.puts("self._is_le = _is_le")
+
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
+ }
+
+ override def runRead(): Unit = {
+ out.puts("self._read()")
+ }
+
+ override def runReadCalc(): Unit = {
+ out.puts
+ out.puts(s"if self._is_le == True:")
+ out.inc
+ out.puts("self._read_le()")
+ out.dec
+ out.puts("elif self._is_le == False:")
+ out.inc
+ out.puts("self._read_be()")
+ out.dec
+ out.puts("else:")
+ out.inc
+ //out.puts(s"raise $kstreamName.UndecidedEndiannessError")
+ out.puts("raise Exception(\"Unable to decide endianness\")")
+ out.dec
+ }
+
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit = {
+ val suffix = endian match {
+ case Some(e) => s"_${e.toSuffix}"
+ case None => ""
+ }
+ out.puts(s"def _read$suffix(self):")
+ out.inc
+ if (isEmpty)
+ out.puts("pass")
}
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {}
+ override def readFooter() = universalFooter
+
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}
+
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {}
+ override def universalDoc(doc: DocSpec): Unit = {
+ val docStr = doc.summary match {
+ case Some(summary) =>
+ val lastChar = summary.last
+ if (lastChar == '.' || lastChar == '\n') {
+ summary
+ } else {
+ summary + "."
+ }
+ case None =>
+ ""
+ }
+
+ val extraNewline = if (docStr.isEmpty || docStr.last == '\n') "" else "\n"
+ val refStr = doc.ref match {
+ case TextRef(text) =>
+ val seeAlso = new StringLanguageOutputWriter("")
+ seeAlso.putsLines(" ", text)
+ s"$extraNewline\n.. seealso::\n${seeAlso.result}"
+ case ref: UrlRef =>
+ val seeAlso = new StringLanguageOutputWriter("")
+ seeAlso.putsLines(" ", s"${ref.text} - ${ref.url}")
+ s"$extraNewline\n.. seealso::\n${seeAlso.result}"
+ case NoRef =>
+ ""
+ }
+
+ out.putsLines("", "\"\"\"" + docStr + refStr + "\"\"\"")
+ }
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit =
out.puts(s"${privateMemberName(attrName)} = self._io.ensure_fixed_contents($contents)")
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts("if self._is_le:")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("else:")
+ out.inc
+ beProc()
+ out.dec
+ }
+
override def attrProcess(proc: ProcessExpr, varSrc: Identifier, varDest: Identifier): Unit = {
+ val srcName = privateMemberName(varSrc)
+ val destName = privateMemberName(varDest)
+
proc match {
case ProcessXor(xorValue) =>
val procName = translator.detectType(xorValue) match {
case _: IntType => "process_xor_one"
case _: BytesType => "process_xor_many"
}
- out.puts(s"${privateMemberName(varDest)} = $kstreamName.$procName(${privateMemberName(varSrc)}, ${expression(xorValue)})")
+ out.puts(s"$destName = $kstreamName.$procName($srcName, ${expression(xorValue)})")
case ProcessZlib =>
- out.puts(s"${privateMemberName(varDest)} = zlib.decompress(${privateMemberName(varSrc)})")
+ importList.add("import zlib")
+ out.puts(s"$destName = zlib.decompress($srcName)")
case ProcessRotate(isLeft, rotValue) =>
val expr = if (isLeft) {
expression(rotValue)
} else {
s"8 - (${expression(rotValue)})"
}
- out.puts(s"${privateMemberName(varDest)} = $kstreamName.process_rotate_left(${privateMemberName(varSrc)}, $expr, 1)")
+ out.puts(s"$destName = $kstreamName.process_rotate_left($srcName, $expr, 1)")
+ case ProcessCustom(name, args) =>
+ val procClass = if (name.length == 1) {
+ val onlyName = name.head
+ val className = type2class(onlyName)
+ importList.add(s"from $onlyName import $className")
+ className
+ } else {
+ val pkgName = name.init.mkString(".")
+ importList.add(s"import $pkgName")
+ s"$pkgName.${type2class(name.last)}"
+ }
+
+ out.puts(s"_process = $procClass(${args.map(expression).mkString(", ")})")
+ out.puts(s"$destName = _process.decode($srcName)")
}
}
@@ -147,11 +257,16 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = []")
out.puts(s"${privateMemberName(id)} = []")
+ out.puts("i = 0")
out.puts(s"while not $io.is_eof():")
out.inc
}
override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit =
out.puts(s"${privateMemberName(id)}.append($expr)")
+ override def condRepeatEosFooter: Unit = {
+ out.puts("i += 1")
+ universalFooter
+ }
override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: expr): Unit = {
if (needRaw)
@@ -167,6 +282,7 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = []")
out.puts(s"${privateMemberName(id)} = []")
+ out.puts("i = 0")
out.puts("while True:")
out.inc
}
@@ -183,16 +299,17 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.inc
out.puts("break")
out.dec
+ out.puts("i += 1")
out.dec
}
override def handleAssignmentSimple(id: Identifier, expr: String): Unit =
out.puts(s"${privateMemberName(id)} = $expr")
- override def parseExpr(dataType: DataType, io: String): String = {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
dataType match {
case t: ReadableType =>
- s"$io.read_${t.apiCall}()"
+ s"$io.read_${t.apiCall(defEndian)}()"
case blt: BytesLimitType =>
s"$io.read_bytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -204,6 +321,7 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case BitsType(width: Int) =>
s"$io.read_bits_int($width)"
case t: UserType =>
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), "", ", ", ", ")
val addArgs = if (t.isOpaque) {
""
} else {
@@ -211,9 +329,13 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case Some(fp) => translator.translate(fp)
case None => "self"
}
- s", $parent, self._root"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => ", self._is_le"
+ case _ => ""
+ }
+ s", $parent, self._root$addEndian"
}
- s"${types2class(t.classSpec.get.name)}($io$addArgs)"
+ s"${types2class(t.classSpec.get.name)}($addParams$io$addArgs)"
}
}
@@ -253,7 +375,7 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def switchEnd(): Unit = {}
- override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType): Unit = {
+ override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
out.puts("@property")
out.puts(s"def ${publicMemberName(instName)}(self):")
out.inc
@@ -274,6 +396,8 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = {
+ importList.add("from enum import Enum")
+
out.puts
out.puts(s"class ${type2class(enumName)}(Enum):")
out.inc
@@ -303,12 +427,13 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case RawIdentifier(innerId) => s"_raw_${publicMemberName(innerId)}"
}
}
+
+ override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}"
}
object PythonCompiler extends LanguageCompilerStatic
with UpperCamelCaseClasses
with StreamStructNames {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig) = new PythonTranslator(tp)
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala
index 05bac2789..1e588698a 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala
@@ -1,13 +1,13 @@
package io.kaitai.struct.languages
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
-import io.kaitai.struct.datatype.DataType
-import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components._
-import io.kaitai.struct.translators.{RubyTranslator, TypeProvider}
-import io.kaitai.struct.{ClassTypeProvider, LanguageOutputWriter, RuntimeConfig}
+import io.kaitai.struct.translators.RubyTranslator
+import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, Utils}
class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
extends LanguageCompiler(typeProvider, config)
@@ -23,7 +23,7 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
import RubyCompiler._
- override def getStatic = RubyCompiler
+ val translator = new RubyTranslator(typeProvider)
override def universalFooter: Unit = {
out.dec
@@ -33,11 +33,15 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def outFileName(topClassName: String): String = s"$topClassName.rb"
override def indent: String = " "
+ override def outImports(topClass: ClassSpec) =
+ importList.toList.map((x) => s"require '$x'").mkString("\n") + "\n"
+
override def fileHeader(topClassName: String): Unit = {
- out.puts(s"# $headerComment")
- out.puts
- out.puts("require 'kaitai/struct/struct'")
- out.puts("require 'zlib'") // TODO: add only if actually used
+ outHeader.puts(s"# $headerComment")
+ outHeader.puts
+
+ importList.add("kaitai/struct/struct")
+
out.puts
// API compatibility check
@@ -64,33 +68,78 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts("attr_reader :_debug")
}
- override def classConstructorHeader(name: String, parentClassName: String, rootClassName: String): Unit = {
- out.puts("def initialize(_io, _parent = nil, _root = self)")
+ override def classConstructorHeader(name: String, parentType: DataType, rootClassName: String, isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
+ val endianSuffix = if (isHybrid) {
+ ", _is_le = nil"
+ } else {
+ ""
+ }
+
+ val paramsList = Utils.join(params.map((p) => paramName(p.id)), ", ", ", ", "")
+
+ out.puts(s"def initialize(_io, _parent = nil, _root = self$endianSuffix$paramsList)")
out.inc
out.puts("super(_io, _parent, _root)")
+
+ if (isHybrid) {
+ out.puts("@_is_le = _is_le")
+ }
+
+ // Store parameters passed to us
+ params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))
+
if (debug) {
out.puts("@_debug = {}")
- out.dec
- out.puts("end")
- out.puts
- out.puts("def _read")
- out.inc
}
}
- override def classConstructorFooter: Unit = {
- if (debug) {
- // Actually, it's not constructor in debug mode, but a "_read" method. Make sure it returns an instance of the
- // class, just as normal Foo.new call does.
- out.puts
- out.puts("self")
+ override def runRead(): Unit = {
+ out.puts("_read")
+ }
+
+ override def runReadCalc(): Unit = {
+ out.puts
+ out.puts(s"if @_is_le == true")
+ out.inc
+ out.puts("_read_le")
+ out.dec
+ out.puts("elsif @_is_le == false")
+ out.inc
+ out.puts("_read_be")
+ out.dec
+ out.puts("else")
+ out.inc
+ out.puts("raise Kaitai::Struct::Stream::UndecidedEndiannessError")
+ out.dec
+ out.puts("end")
+ }
+
+ override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean) = {
+ val suffix = endian match {
+ case Some(e) => s"_${e.toSuffix}"
+ case None => ""
}
+ out.puts
+ out.puts(s"def _read$suffix")
+ out.inc
+ }
+
+ override def readFooter() = {
+ // This is required for debug mode to be able to do stuff like:
+ //
+ // obj = Obj.new(...)._read
+ //
+ // i.e. drop-in replacement of non-debug mode invocation:
+ //
+ // obj = Obj.new(...)
+ out.puts("self")
+
universalFooter
}
- override def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {}
+ override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}
- override def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit = {
+ override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
attrName match {
case RootIdentifier | ParentIdentifier =>
// ignore, they are already added in Kaitai::Struct::Struct
@@ -115,6 +164,18 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
}
+ override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
+ out.puts("if @_is_le")
+ out.inc
+ leProc()
+ out.dec
+ out.puts("else")
+ out.inc
+ beProc()
+ out.dec
+ out.puts("end")
+ }
+
override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit =
out.puts(s"${privateMemberName(attrName)} = $normalIO.ensure_fixed_contents($contents)")
@@ -130,6 +191,7 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
}
s"$destName = $kstreamName::$procName($srcName, ${expression(xorValue)})"
case ProcessZlib =>
+ importList.add("zlib")
s"$destName = Zlib::Inflate.inflate($srcName)"
case ProcessRotate(isLeft, rotValue) =>
val expr = if (isLeft) {
@@ -138,6 +200,10 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
s"8 - (${expression(rotValue)})"
}
s"$destName = $kstreamName::process_rotate_left($srcName, $expr, 1)"
+ case ProcessCustom(name, args) =>
+ val procClass = name.map((x) => type2class(x)).mkString("::")
+ out.puts(s"_process = $procClass.new(${args.map(expression).mkString(", ")})")
+ s"$destName = _process.decode($srcName)"
})
}
@@ -213,11 +279,16 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts(s"${privateMemberName(RawIdentifier(id))} = []")
out.puts(s"${privateMemberName(id)} = []")
+ out.puts("i = 0")
out.puts(s"while not $io.eof?")
out.inc
}
override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit =
out.puts(s"${privateMemberName(id)} << $expr")
+ override def condRepeatEosFooter: Unit = {
+ out.puts("i += 1")
+ super.condRepeatEosFooter
+ }
override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: expr): Unit = {
if (needRaw)
@@ -237,6 +308,7 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
if (needRaw)
out.puts(s"${privateMemberName(RawIdentifier(id))} = []")
out.puts(s"${privateMemberName(id)} = []")
+ out.puts("i = 0")
out.puts("begin")
out.inc
}
@@ -249,6 +321,7 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
typeProvider._currentIteratorType = Some(dataType)
+ out.puts("i += 1")
out.dec
out.puts(s"end until ${expression(untilExpr)}")
}
@@ -259,10 +332,10 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def handleAssignmentTempVar(dataType: DataType, id: String, expr: String): Unit =
out.puts(s"$id = $expr")
- override def parseExpr(dataType: DataType, io: String): String = {
+ override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
dataType match {
case t: ReadableType =>
- s"$io.read_${t.apiCall}"
+ s"$io.read_${t.apiCall(defEndian)}"
case blt: BytesLimitType =>
s"$io.read_bytes(${expression(blt.size)})"
case _: BytesEosType =>
@@ -274,6 +347,7 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case BitsType(width: Int) =>
s"$io.read_bits_int($width)"
case t: UserType =>
+ val addParams = Utils.join(t.args.map((a) => translator.translate(a)), ", ", ", ", "")
val addArgs = if (t.isOpaque) {
""
} else {
@@ -281,9 +355,13 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
case Some(fp) => translator.translate(fp)
case None => "self"
}
- s", $parent, @_root"
+ val addEndian = t.classSpec.get.meta.endian match {
+ case Some(InheritedEndian) => ", @_is_le"
+ case _ => ""
+ }
+ s", $parent, @_root$addEndian"
}
- s"${type2class(t.name.last)}.new($io$addArgs)"
+ s"${type2class(t.name.last)}.new($io$addArgs$addParams)"
}
}
@@ -321,7 +399,7 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def switchEnd(): Unit =
out.puts("end")
- override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType): Unit = {
+ override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
out.puts(s"def ${instName.name}")
out.inc
}
@@ -372,11 +450,12 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def privateMemberName(id: Identifier): String = s"@${idToStr(id)}"
override def publicMemberName(id: Identifier): String = idToStr(id)
+
+ override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}"
}
object RubyCompiler extends LanguageCompilerStatic
with StreamStructNames {
- override def getTranslator(tp: TypeProvider, config: RuntimeConfig) = new RubyTranslator(tp)
override def getCompiler(
tp: ClassTypeProvider,
config: RuntimeConfig
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/AllocateAndStoreIO.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/AllocateAndStoreIO.scala
index bb216d6c5..cac6a7fdc 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/AllocateAndStoreIO.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/AllocateAndStoreIO.scala
@@ -1,6 +1,8 @@
package io.kaitai.struct.languages.components
-import io.kaitai.struct.format.{Identifier, IoStorageIdentifier, RepeatSpec}
+import io.kaitai.struct.format.{AttrSpec, Identifier, RepeatSpec}
+
+import scala.collection.mutable.ListBuffer
/**
* Allocates new IO and returns attribute identifier that it will be stored
@@ -8,5 +10,5 @@ import io.kaitai.struct.format.{Identifier, IoStorageIdentifier, RepeatSpec}
* keep track of allocated IOs.
*/
trait AllocateAndStoreIO {
- def allocateIO(varName: Identifier, rep: RepeatSpec): IoStorageIdentifier
+ def allocateIO(id: Identifier, rep: RepeatSpec, extraAttrs: ListBuffer[AttrSpec]): String
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/CommonReads.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/CommonReads.scala
new file mode 100644
index 000000000..350d1c091
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/CommonReads.scala
@@ -0,0 +1,91 @@
+package io.kaitai.struct.languages.components
+
+import io.kaitai.struct.datatype._
+import io.kaitai.struct.datatype.DataType.{SwitchType, UserTypeFromBytes}
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format._
+
+import scala.collection.mutable.ListBuffer
+
+trait CommonReads extends LanguageCompiler {
+ override def attrParse(attr: AttrLikeSpec, id: Identifier, extraAttrs: ListBuffer[AttrSpec], defEndian: Option[Endianness]): Unit = {
+ attrParseIfHeader(id, attr.cond.ifExpr)
+
+ // Manage IO & seeking for ParseInstances
+ val io = attr match {
+ case pis: ParseInstanceSpec =>
+ val io = pis.io match {
+ case None => normalIO
+ case Some(ex) => useIO(ex)
+ }
+ pis.pos.foreach { pos =>
+ pushPos(io)
+ seek(io, pos)
+ }
+ io
+ case _ =>
+ // no seeking required for sequence attributes
+ normalIO
+ }
+
+ if (debug)
+ attrDebugStart(id, attr.dataType, Some(io), NoRepeat)
+
+ defEndian match {
+ case Some(_: CalcEndian) | Some(InheritedEndian) =>
+ attrParseHybrid(
+ () => attrParse0(id, attr, io, extraAttrs, Some(LittleEndian)),
+ () => attrParse0(id, attr, io, extraAttrs, Some(BigEndian))
+ )
+ case None =>
+ attrParse0(id, attr, io, extraAttrs, None)
+ case Some(fe: FixedEndian) =>
+ attrParse0(id, attr, io, extraAttrs, Some(fe))
+ }
+
+ if (debug)
+ attrDebugEnd(id, attr.dataType, io, NoRepeat)
+
+ // More position management after parsing for ParseInstanceSpecs
+ attr match {
+ case pis: ParseInstanceSpec =>
+ if (pis.pos.isDefined)
+ popPos(io)
+ case _ => // no seeking required for sequence attributes
+ }
+
+ attrParseIfFooter(attr.cond.ifExpr)
+ }
+
+ def attrParse0(id: Identifier, attr: AttrLikeSpec, io: String, extraAttrs: ListBuffer[AttrSpec], defEndian: Option[FixedEndian]): Unit = {
+ attr.cond.repeat match {
+ case RepeatEos =>
+ condRepeatEosHeader(id, io, attr.dataType, needRaw(attr.dataType))
+ attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false, defEndian)
+ condRepeatEosFooter
+ case RepeatExpr(repeatExpr: Ast.expr) =>
+ condRepeatExprHeader(id, io, attr.dataType, needRaw(attr.dataType), repeatExpr)
+ attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false, defEndian)
+ condRepeatExprFooter
+ case RepeatUntil(untilExpr: Ast.expr) =>
+ condRepeatUntilHeader(id, io, attr.dataType, needRaw(attr.dataType), untilExpr)
+ attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false, defEndian)
+ condRepeatUntilFooter(id, io, attr.dataType, needRaw(attr.dataType), untilExpr)
+ case NoRepeat =>
+ attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false, defEndian)
+ }
+ }
+
+ def attrParse2(id: Identifier, dataType: DataType, io: String, extraAttrs: ListBuffer[AttrSpec], rep: RepeatSpec, isRaw: Boolean, defEndian: Option[FixedEndian], assignType: Option[DataType] = None): Unit
+
+ def needRaw(dataType: DataType): Boolean = {
+ dataType match {
+ case _: UserTypeFromBytes => true
+ case st: SwitchType => st.hasSize
+ case _ => false
+ }
+ }
+
+ def attrDebugStart(attrName: Identifier, attrType: DataType, io: Option[String], repeat: RepeatSpec): Unit = {}
+ def attrDebugEnd(attrName: Identifier, attrType: DataType, io: String, repeat: RepeatSpec): Unit = {}
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/EveryReadIsExpression.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/EveryReadIsExpression.scala
index 9ad3f56cd..fcee72026 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/EveryReadIsExpression.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/EveryReadIsExpression.scala
@@ -1,10 +1,11 @@
package io.kaitai.struct.languages.components
import io.kaitai.struct.Utils
-import io.kaitai.struct.exprlang.Ast
-import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.datatype.{DataType, FixedEndian}
+import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.format._
+import io.kaitai.struct.translators.BaseTranslator
import scala.collection.mutable.ListBuffer
@@ -13,69 +14,25 @@ import scala.collection.mutable.ListBuffer
* rvalue. In these languages, "attrStdTypeParse" is replaced with higher-level API: "stdTypeParseExpr" and
* "handleAssignment".
*/
-trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage {
- override def attrParse(attr: AttrLikeSpec, id: Identifier, extraAttrs: ListBuffer[AttrSpec]): Unit = {
- attrParseIfHeader(id, attr.cond.ifExpr)
-
- // Manage IO & seeking for ParseInstances
- val io = attr match {
- case pis: ParseInstanceSpec =>
- val io = pis.io match {
- case None => normalIO
- case Some(ex) => useIO(ex)
- }
- pis.pos.foreach { pos =>
- pushPos(io)
- seek(io, pos)
- }
- io
- case _ =>
- // no seeking required for sequence attributes
- normalIO
- }
-
- if (debug)
- attrDebugStart(id, attr.dataType, Some(io), NoRepeat)
-
- attr.cond.repeat match {
- case RepeatEos =>
- condRepeatEosHeader(id, io, attr.dataType, needRaw(attr.dataType))
- attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false)
- condRepeatEosFooter
- case RepeatExpr(repeatExpr: Ast.expr) =>
- condRepeatExprHeader(id, io, attr.dataType, needRaw(attr.dataType), repeatExpr)
- attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false)
- condRepeatExprFooter
- case RepeatUntil(untilExpr: Ast.expr) =>
- condRepeatUntilHeader(id, io, attr.dataType, needRaw(attr.dataType), untilExpr)
- attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false)
- condRepeatUntilFooter(id, io, attr.dataType, needRaw(attr.dataType), untilExpr)
- case NoRepeat =>
- attrParse2(id, attr.dataType, io, extraAttrs, attr.cond.repeat, false)
- }
-
- if (debug)
- attrDebugEnd(id, attr.dataType, io, NoRepeat)
-
- // More position management after parsing for ParseInstanceSpecs
- attr match {
- case pis: ParseInstanceSpec =>
- if (pis.pos.isDefined)
- popPos(io)
- case _ => // no seeking required for sequence attributes
- }
-
- attrParseIfFooter(attr.cond.ifExpr)
- }
-
- def attrParse2(
+trait EveryReadIsExpression
+ extends LanguageCompiler
+ with ObjectOrientedLanguage
+ with CommonReads
+ with SwitchOps {
+ val translator: BaseTranslator
+
+ override def attrParse2(
id: Identifier,
dataType: DataType,
io: String,
extraAttrs: ListBuffer[AttrSpec],
rep: RepeatSpec,
- isRaw: Boolean
+ isRaw: Boolean,
+ defEndian: Option[FixedEndian],
+ assignTypeOpt: Option[DataType] = None
): Unit = {
+ val assignType = assignTypeOpt.getOrElse(dataType)
+
if (debug && rep != NoRepeat)
attrDebugStart(id, dataType, Some(io), rep)
@@ -83,19 +40,25 @@ trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage
case FixedBytesType(c, _) =>
attrFixedContentsParse(id, c)
case t: UserType =>
- attrUserTypeParse(id, t, io, extraAttrs, rep)
+ attrUserTypeParse(id, t, io, extraAttrs, rep, defEndian)
case t: BytesType =>
attrBytesTypeParse(id, t, io, extraAttrs, rep, isRaw)
- case SwitchType(on, cases) =>
- attrSwitchTypeParse(id, on, cases, io, extraAttrs, rep)
+ case st: SwitchType =>
+ val isNullable = if (switchBytesOnlyAsRaw) {
+ st.isNullableSwitchRaw
+ } else {
+ st.isNullable
+ }
+
+ attrSwitchTypeParse(id, st.on, st.cases, io, extraAttrs, rep, defEndian, isNullable, st.combinedType)
case t: StrFromBytesType =>
val expr = translator.bytesToStr(parseExprBytes(t.bytes, io), Ast.expr.Str(t.encoding))
handleAssignment(id, expr, rep, isRaw)
case t: EnumType =>
- val expr = translator.doEnumById(t.enumSpec.get.name, parseExpr(t.basedOn, io))
+ val expr = translator.doEnumById(t.enumSpec.get.name, parseExpr(t.basedOn, t.basedOn, io, defEndian))
handleAssignment(id, expr, rep, isRaw)
case _ =>
- val expr = parseExpr(dataType, io)
+ val expr = parseExpr(dataType, assignType, io, defEndian)
handleAssignment(id, expr, rep, isRaw)
}
@@ -128,7 +91,7 @@ trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage
}
def parseExprBytes(dataType: BytesType, io: String): String = {
- val expr = parseExpr(dataType, io)
+ val expr = parseExpr(dataType, dataType, io, None)
// apply pad stripping and termination
dataType match {
@@ -141,14 +104,14 @@ trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage
}
}
- def attrUserTypeParse(id: Identifier, dataType: UserType, io: String, extraAttrs: ListBuffer[AttrSpec], rep: RepeatSpec): Unit = {
+ def attrUserTypeParse(id: Identifier, dataType: UserType, io: String, extraAttrs: ListBuffer[AttrSpec], rep: RepeatSpec, defEndian: Option[FixedEndian]): Unit = {
val newIO = dataType match {
case knownSizeType: UserTypeFromBytes =>
// we have a fixed buffer, thus we shall create separate IO for it
val rawId = RawIdentifier(id)
val byteType = knownSizeType.bytes
- attrParse2(rawId, byteType, io, extraAttrs, rep, true)
+ attrParse2(rawId, byteType, io, extraAttrs, rep, true, defEndian)
val extraType = rep match {
case NoRepeat => byteType
@@ -159,9 +122,7 @@ trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage
this match {
case thisStore: AllocateAndStoreIO =>
- val ourIO = thisStore.allocateIO(rawId, rep)
- Utils.addUniqueAttr(extraAttrs, AttrSpec(List(), ourIO, KaitaiStreamType))
- privateMemberName(ourIO)
+ thisStore.allocateIO(rawId, rep, extraAttrs)
case thisLocal: AllocateIOLocalVar =>
thisLocal.allocateIO(rawId, rep)
}
@@ -169,7 +130,7 @@ trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage
// no fixed buffer, just use regular IO
io
}
- val expr = parseExpr(dataType, newIO)
+ val expr = parseExpr(dataType, dataType, newIO, defEndian)
if (!debug) {
handleAssignment(id, expr, rep, false)
} else {
@@ -182,65 +143,45 @@ trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage
handleAssignmentSimple(id, expr)
userTypeDebugRead(privateMemberName(id))
case _ =>
- val tempVarName = s"_t_${idToStr(id)}"
+ val tempVarName = localTemporaryName(id)
handleAssignmentTempVar(dataType, tempVarName, expr)
- handleAssignment(id, tempVarName, rep, false)
userTypeDebugRead(tempVarName)
+ handleAssignment(id, tempVarName, rep, false)
}
}
}
- def needRaw(dataType: DataType): Boolean = {
- dataType match {
- case t: UserTypeFromBytes => true
- case _ => false
- }
- }
-
- def attrSwitchTypeParse(id: Identifier, on: Ast.expr, cases: Map[Ast.expr, DataType], io: String, extraAttrs: ListBuffer[AttrSpec], rep: RepeatSpec): Unit = {
- switchStart(id, on)
-
- // Pass 1: only normal case clauses
- var first = true
-
- cases.foreach { case (condition, dataType) =>
- condition match {
- case SwitchType.ELSE_CONST =>
- // skip for now
- case _ =>
- if (first) {
- switchCaseFirstStart(condition)
- first = false
- } else {
- switchCaseStart(condition)
- }
- attrParse2(id, dataType, io, extraAttrs, rep, false)
- switchCaseEnd()
- }
- }
-
- // Pass 2: else clause, if it is there
- cases.foreach { case (condition, dataType) =>
- condition match {
- case SwitchType.ELSE_CONST =>
- switchElseStart()
- if (switchBytesOnlyAsRaw) {
- dataType match {
- case t: BytesType =>
- attrParse2(RawIdentifier(id), dataType, io, extraAttrs, rep, false)
- case _ =>
- attrParse2(id, dataType, io, extraAttrs, rep, false)
- }
- } else {
- attrParse2(id, dataType, io, extraAttrs, rep, false)
- }
- switchElseEnd()
- case _ =>
- // ignore normal case clauses
+ def attrSwitchTypeParse(
+ id: Identifier,
+ on: Ast.expr,
+ cases: Map[Ast.expr, DataType],
+ io: String,
+ extraAttrs: ListBuffer[AttrSpec],
+ rep: RepeatSpec,
+ defEndian: Option[FixedEndian],
+ isNullable: Boolean,
+ assignType: DataType
+ ): Unit = {
+ if (isNullable)
+ condIfSetNull(id)
+
+ switchCases[DataType](id, on, cases,
+ (dataType) => {
+ if (isNullable)
+ condIfSetNonNull(id)
+ attrParse2(id, dataType, io, extraAttrs, rep, false, defEndian, Some(assignType))
+ },
+ (dataType) => if (switchBytesOnlyAsRaw) {
+ dataType match {
+ case t: BytesType =>
+ attrParse2(RawIdentifier(id), dataType, io, extraAttrs, rep, false, defEndian, Some(assignType))
+ case _ =>
+ attrParse2(id, dataType, io, extraAttrs, rep, false, defEndian, Some(assignType))
+ }
+ } else {
+ attrParse2(id, dataType, io, extraAttrs, rep, false, defEndian, Some(assignType))
}
- }
-
- switchEnd()
+ )
}
def handleAssignment(id: Identifier, expr: String, rep: RepeatSpec, isRaw: Boolean): Unit = {
@@ -252,40 +193,19 @@ trait EveryReadIsExpression extends LanguageCompiler with ObjectOrientedLanguage
}
}
- def attrDebugStart(attrName: Identifier, attrType: DataType, io: Option[String], repeat: RepeatSpec): Unit = {}
- def attrDebugEnd(attrName: Identifier, attrType: DataType, io: String, repeat: RepeatSpec): Unit = {}
-
def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit
def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit
def handleAssignmentRepeatUntil(id: Identifier, expr: String, isRaw: Boolean): Unit
def handleAssignmentSimple(id: Identifier, expr: String): Unit
def handleAssignmentTempVar(dataType: DataType, id: String, expr: String): Unit = ???
- def parseExpr(dataType: DataType, io: String): String
+ def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String
def bytesPadTermExpr(expr0: String, padRight: Option[Int], terminator: Option[Int], include: Boolean): String
def userTypeDebugRead(id: String): Unit = {}
- def instanceCalculate(instName: InstanceIdentifier, dataType: DataType, value: Ast.expr): Unit = {
+ def instanceCalculate(instName: Identifier, dataType: DataType, value: Ast.expr): Unit = {
if (debug)
attrDebugStart(instName, dataType, None, NoRepeat)
handleAssignmentSimple(instName, expression(value))
}
-
- def switchStart(id: Identifier, on: Ast.expr): Unit
- def switchCaseFirstStart(condition: Ast.expr): Unit = switchCaseStart(condition)
- def switchCaseStart(condition: Ast.expr): Unit
- def switchCaseEnd(): Unit
- def switchElseStart(): Unit
- def switchElseEnd(): Unit = switchCaseEnd()
- def switchEnd(): Unit
-
- /**
- * Controls parsing of typeless (BytesType) alternative in switch case. If true,
- * then target language does not support storing both bytes array and true object
- * in the same variable, so we'll use workaround: bytes array will be read as
- * _raw_ variable (which would be used anyway for all other cases as well). If
- * false (which is default), we'll store *both* true objects and bytes array in
- * the same variable.
- */
- def switchBytesOnlyAsRaw = false
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/ExtraAttrs.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/ExtraAttrs.scala
new file mode 100644
index 000000000..a1f9e6f5c
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/ExtraAttrs.scala
@@ -0,0 +1,35 @@
+package io.kaitai.struct.languages.components
+
+import io.kaitai.struct.datatype.DataType
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.format._
+
+/**
+ * Generates list of extra attributes required to store intermediate /
+ * virtual stuff for every attribute like:
+ *
+ * * buffered raw value byte arrays
+ * * IO objects (?)
+ * * unprocessed / postprocessed byte arrays
+ */
+object ExtraAttrs {
+ def forAttr(attr: AttrLikeSpec): List[AttrSpec] =
+ forAttr(attr.id, attr.dataType)
+
+ def forAttr(id: Identifier, dataType: DataType): List[AttrSpec] = {
+ dataType match {
+ case bt: BytesType =>
+ bt.process match {
+ case None => List()
+ case Some(_) =>
+ val rawId = RawIdentifier(id)
+ List(AttrSpec(List(), rawId, bt))
+ }
+ case utb: UserTypeFromBytes =>
+ val rawId = RawIdentifier(id)
+ List(AttrSpec(List(), rawId, utb.bytes)) ++ forAttr(rawId, utb.bytes)
+ case _ =>
+ List()
+ }
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/FixedContentsUsingArrayByteLiteral.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/FixedContentsUsingArrayByteLiteral.scala
index b2ea21b50..c5d4abd9b 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/FixedContentsUsingArrayByteLiteral.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/FixedContentsUsingArrayByteLiteral.scala
@@ -1,5 +1,6 @@
package io.kaitai.struct.languages.components
+import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.format.Identifier
/**
@@ -8,6 +9,13 @@ import io.kaitai.struct.format.Identifier
*/
trait FixedContentsUsingArrayByteLiteral extends LanguageCompiler {
def attrFixedContentsParse(attrName: Identifier, contents: Array[Byte]) =
- attrFixedContentsParse(attrName, translator.doByteArrayLiteral(contents))
+ attrFixedContentsParse(
+ attrName,
+ translator.translate(
+ Ast.expr.List(
+ contents.map(x => Ast.expr.IntNum(BigInt(x & 0xff)))
+ )
+ )
+ )
def attrFixedContentsParse(attrName: Identifier, contents: String): Unit
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/GoReads.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/GoReads.scala
new file mode 100644
index 000000000..876515960
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/GoReads.scala
@@ -0,0 +1,109 @@
+package io.kaitai.struct.languages.components
+
+import io.kaitai.struct.Utils
+import io.kaitai.struct.datatype.{BigEndian, DataType, FixedEndian}
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format._
+import io.kaitai.struct.translators.{GoTranslator, TranslatorResult}
+
+import scala.collection.mutable.ListBuffer
+
+trait GoReads extends CommonReads with ObjectOrientedLanguage with SwitchOps {
+ val translator: GoTranslator
+
+ override def attrParse2(
+ id: Identifier,
+ dataType: DataType,
+ io: String,
+ extraAttrs: ListBuffer[AttrSpec],
+ rep: RepeatSpec,
+ isRaw: Boolean,
+ defEndian: Option[FixedEndian],
+ assignType: Option[DataType] = None
+ ): Unit = {
+ dataType match {
+ case FixedBytesType(c, _) =>
+ attrFixedContentsParse(id, c)
+ case t: UserType =>
+ attrUserTypeParse(id, t, io, extraAttrs, rep, defEndian)
+// case t: BytesType =>
+// attrBytesTypeParse(id, t, io, extraAttrs, rep, isRaw)
+// case SwitchType(on, cases) =>
+// attrSwitchTypeParse(id, on, cases, io, extraAttrs, rep)
+ case t: StrFromBytesType =>
+ val r1 = translator.outVarCheckRes(parseExprBytes(t.bytes, io))
+ val expr = translator.bytesToStr(translator.resToStr(r1), Ast.expr.Str(t.encoding))
+ handleAssignment(id, expr, rep, isRaw)
+// case t: EnumType =>
+// val expr = translator.doEnumById(t.enumSpec.get.name, parseExpr(t.basedOn, io))
+// handleAssignment(id, expr, rep, isRaw)
+ case _ =>
+ val expr = parseExpr(dataType, io, defEndian)
+ val r = translator.outVarCheckRes(expr)
+ handleAssignment(id, r, rep, isRaw)
+ }
+ }
+
+ def parseExprBytes(dataType: BytesType, io: String): String = {
+ val expr = parseExpr(dataType, io, None) // FIXME
+/*
+ // apply pad stripping and termination
+ dataType match {
+ case BytesEosType(terminator, include, padRight, _) =>
+ bytesPadTermExpr(expr, padRight, terminator, include)
+ case BytesLimitType(_, terminator, include, padRight, _) =>
+ bytesPadTermExpr(expr, padRight, terminator, include)
+ case _ =>
+ expr
+ }*/
+ expr
+ }
+
+ def attrUserTypeParse(id: Identifier, dataType: UserType, io: String, extraAttrs: ListBuffer[AttrSpec], rep: RepeatSpec, defEndian: Option[FixedEndian]): Unit = {
+ val newIO = dataType match {
+ case knownSizeType: UserTypeFromBytes =>
+ // we have a fixed buffer, thus we shall create separate IO for it
+ val rawId = RawIdentifier(id)
+ val byteType = knownSizeType.bytes
+
+ attrParse2(rawId, byteType, io, extraAttrs, rep, true, defEndian)
+
+ val extraType = rep match {
+ case NoRepeat => byteType
+ case _ => ArrayType(byteType)
+ }
+
+ Utils.addUniqueAttr(extraAttrs, AttrSpec(List(), rawId, extraType))
+
+ this match {
+ case thisStore: AllocateAndStoreIO =>
+ thisStore.allocateIO(rawId, rep, extraAttrs)
+ case thisLocal: AllocateIOLocalVar =>
+ thisLocal.allocateIO(rawId, rep)
+ }
+ case _: UserTypeInstream =>
+ // no fixed buffer, just use regular IO
+ io
+ }
+
+ val expr = translator.userType(dataType, newIO)
+ handleAssignment(id, expr, rep, false)
+ }
+
+ def handleAssignment(id: Identifier, expr: TranslatorResult, rep: RepeatSpec, isRaw: Boolean): Unit = {
+ rep match {
+ case RepeatEos => handleAssignmentRepeatEos(id, expr)
+ case RepeatExpr(_) => handleAssignmentRepeatExpr(id, expr)
+ case RepeatUntil(_) => handleAssignmentRepeatUntil(id, expr, isRaw)
+ case NoRepeat => handleAssignmentSimple(id, expr)
+ }
+ }
+
+ def handleAssignmentRepeatEos(id: Identifier, expr: TranslatorResult): Unit
+ def handleAssignmentRepeatExpr(id: Identifier, expr: TranslatorResult): Unit
+ def handleAssignmentRepeatUntil(id: Identifier, expr: TranslatorResult, isRaw: Boolean): Unit
+ def handleAssignmentSimple(id: Identifier, expr: TranslatorResult): Unit
+
+ def parseExpr(dataType: DataType, io: String, defEndian: Option[FixedEndian]): String
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala
index c2dff3f6a..77ec48c19 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala
@@ -1,9 +1,9 @@
package io.kaitai.struct.languages.components
-import io.kaitai.struct.datatype.DataType
+import io.kaitai.struct.datatype.{DataType, Endianness, FixedEndian, InheritedEndian}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.format._
-import io.kaitai.struct.translators.BaseTranslator
+import io.kaitai.struct.translators.AbstractTranslator
import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig}
import scala.collection.mutable.ListBuffer
@@ -11,8 +11,9 @@ import scala.collection.mutable.ListBuffer
abstract class LanguageCompiler(
typeProvider: ClassTypeProvider,
config: RuntimeConfig
-) {
- val translator: BaseTranslator = getStatic.getTranslator(typeProvider, config)
+) extends SwitchOps {
+
+ val translator: AbstractTranslator
/**
* @return compilation results as a map: keys are file names, values are
@@ -39,7 +40,16 @@ abstract class LanguageCompiler(
*/
def innerEnums: Boolean = true
- def getStatic: LanguageCompilerStatic
+ /**
+ * Determines whether the language needs docstrings to be generated
+ * inside classes and methods (true, Python-style) or outside them
+ * (false, JavaDoc-style, majority of other languages). Affects calling
+ * sequence of rendering methods.
+ *
+ * @return true if language needs docstrings to be generated
+ * inside classes and methods, false otherwise
+ */
+ def innerDocstrings: Boolean = false
def debug = config.debug
@@ -63,17 +73,23 @@ abstract class LanguageCompiler(
def classFooter(name: List[String]): Unit
def classForwardDeclaration(name: List[String]): Unit = {}
- def classConstructorHeader(name: List[String], parentClassName: List[String], rootClassName: List[String]): Unit
+ def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit
def classConstructorFooter: Unit
- def classDestructorHeader(name: List[String], parentTypeName: List[String], topClassName: List[String]): Unit = {}
+ def classDestructorHeader(name: List[String], parentType: DataType, topClassName: List[String]): Unit = {}
def classDestructorFooter: Unit = {}
- def attributeDeclaration(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit
- def attributeReader(attrName: Identifier, attrType: DataType, condSpec: ConditionalSpec): Unit
+ def runRead(): Unit
+ def runReadCalc(): Unit
+ def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit
+ def readFooter(): Unit
+
+ def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit
+ def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit
def attributeDoc(id: Identifier, doc: DocSpec): Unit = {}
- def attrParse(attr: AttrLikeSpec, id: Identifier, extraAttrs: ListBuffer[AttrSpec]): Unit
+ def attrParse(attr: AttrLikeSpec, id: Identifier, extraAttrs: ListBuffer[AttrSpec], defEndian: Option[Endianness]): Unit
+ def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit
def attrDestructor(attr: AttrLikeSpec, id: Identifier): Unit = {}
def attrFixedContentsParse(attrName: Identifier, contents: Array[Byte]): Unit
@@ -103,14 +119,14 @@ abstract class LanguageCompiler(
def instanceClear(instName: InstanceIdentifier): Unit = {}
def instanceSetCalculated(instName: InstanceIdentifier): Unit = {}
- def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, condSpec: ConditionalSpec) = attributeDeclaration(attrName, attrType, condSpec)
- def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType): Unit
+ def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = attributeDeclaration(attrName, attrType, isNullable)
+ def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit
def instanceFooter: Unit
def instanceCheckCacheAndReturn(instName: InstanceIdentifier): Unit
def instanceReturn(instName: InstanceIdentifier): Unit
- def instanceCalculate(instName: InstanceIdentifier, dataType: DataType, value: Ast.expr)
+ def instanceCalculate(instName: Identifier, dataType: DataType, value: Ast.expr)
- def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, String)]): Unit
+ def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit
/**
* Outputs class' attributes sequence identifiers as some sort of an ordered sequence,
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala
index 9ec6e474d..93d0dc023 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala
@@ -6,7 +6,6 @@ import io.kaitai.struct.translators.{BaseTranslator, TypeProvider}
trait LanguageCompilerStatic {
def getCompiler(tp: ClassTypeProvider, config: RuntimeConfig): LanguageCompiler
- def getTranslator(tp: TypeProvider, config: RuntimeConfig): BaseTranslator
}
object LanguageCompilerStatic {
@@ -14,13 +13,17 @@ object LanguageCompilerStatic {
"cpp_stl" -> CppCompiler,
"csharp" -> CSharpCompiler,
"graphviz" -> GraphvizClassCompiler,
+ "go" -> GoCompiler,
"java" -> JavaCompiler,
"javascript" -> JavaScriptCompiler,
+ "lua" -> LuaCompiler,
"perl" -> PerlCompiler,
"php" -> PHPCompiler,
"python" -> PythonCompiler,
"ruby" -> RubyCompiler
)
+ val CLASS_TO_NAME: Map[LanguageCompilerStatic, String] = NAME_TO_CLASS.map(_.swap)
+
def byString(langName: String): LanguageCompilerStatic = NAME_TO_CLASS(langName)
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/NoNeedForFullClassPath.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/NoNeedForFullClassPath.scala
index 963f47a20..63be66119 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/NoNeedForFullClassPath.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/NoNeedForFullClassPath.scala
@@ -1,7 +1,7 @@
package io.kaitai.struct.languages.components
import io.kaitai.struct.datatype.DataType
-import io.kaitai.struct.format.InstanceIdentifier
+import io.kaitai.struct.format._
trait NoNeedForFullClassPath {
def classHeader(name: List[String]): Unit =
@@ -12,15 +12,15 @@ trait NoNeedForFullClassPath {
classFooter(name.last)
def classFooter(name: String): Unit
- def classConstructorHeader(name: List[String], parentClassName: List[String], rootClassName: List[String]): Unit =
- classConstructorHeader(name.last, parentClassName.last, rootClassName.last)
- def classConstructorHeader(name: String, parentClassName: String, rootClassName: String): Unit
+ def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit =
+ classConstructorHeader(name.last, parentType, rootClassName.last, isHybrid, params)
+ def classConstructorHeader(name: String, parentType: DataType, rootClassName: String, isHybrid: Boolean, params: List[ParamDefSpec]): Unit
- def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType): Unit =
- instanceHeader(className.last, instName, dataType)
- def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType): Unit
+ def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit =
+ instanceHeader(className.last, instName, dataType, isNullable)
+ def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit
- def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, String)]): Unit =
- enumDeclaration(curClass.last, enumName, enumColl)
+ def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit =
+ enumDeclaration(curClass.last, enumName, enumColl.map((x) => (x._1, x._2.name)))
def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/ObjectOrientedLanguage.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/ObjectOrientedLanguage.scala
index 30dcf2da4..02d00c943 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/ObjectOrientedLanguage.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/ObjectOrientedLanguage.scala
@@ -39,5 +39,23 @@ trait ObjectOrientedLanguage extends LanguageCompiler {
*/
def publicMemberName(id: Identifier): String
+ /**
+ * Renders identifier as a proper reference to a local temporary
+ * variable appropriately named to hold a temporary reference to
+ * this field.
+ *
+ * @param id identifier to render
+ * @return identifier as string
+ */
+ def localTemporaryName(id: Identifier): String
+
+ /**
+ * Renders identifier as a parameter (method argument) name.
+ * Default implementation just calls [[idToStr]].
+ * @param id
+ * @return
+ */
+ def paramName(id: Identifier): String = idToStr(id)
+
override def normalIO: String = privateMemberName(IoIdentifier)
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/SingleOutputFile.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/SingleOutputFile.scala
index cf154f286..11d4c77ae 100644
--- a/shared/src/main/scala/io/kaitai/struct/languages/components/SingleOutputFile.scala
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/SingleOutputFile.scala
@@ -1,14 +1,32 @@
package io.kaitai.struct.languages.components
-import io.kaitai.struct.StringLanguageOutputWriter
+import io.kaitai.struct.{ImportList, StringLanguageOutputWriter, Utils}
import io.kaitai.struct.format.ClassSpec
+import scala.collection.mutable.ListBuffer
+
/**
* Common trait for languages that have one output file per ClassSpec.
+ * This file is considered to be composed of:
+ *
+ * * a header
+ * * imports list
+ * * output body
*/
trait SingleOutputFile extends LanguageCompiler {
+ val outHeader = new StringLanguageOutputWriter(indent)
val out = new StringLanguageOutputWriter(indent)
override def results(topClass: ClassSpec) =
- Map(outFileName(topClass.nameAsStr) -> out.result)
+ Map(outFileName(topClass.nameAsStr) ->
+ (outHeader.result + outImports(topClass) + out.result)
+ )
+
+ val importList = new ImportList
+
+ /**
+ * Generates imports clauses in target language format
+ * @return import
+ */
+ def outImports(topClass: ClassSpec) = ""
}
diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/SwitchOps.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/SwitchOps.scala
new file mode 100644
index 000000000..df9ce90fa
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/languages/components/SwitchOps.scala
@@ -0,0 +1,80 @@
+package io.kaitai.struct.languages.components
+
+import io.kaitai.struct.datatype.DataType.SwitchType
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format.Identifier
+
+/**
+ * An interface for switching operations.
+ */
+trait SwitchOps {
+ def switchStart(id: Identifier, on: Ast.expr): Unit
+ def switchCaseFirstStart(condition: Ast.expr): Unit = switchCaseStart(condition)
+ def switchCaseStart(condition: Ast.expr): Unit
+ def switchCaseEnd(): Unit
+ def switchElseStart(): Unit
+ def switchElseEnd(): Unit = switchCaseEnd()
+ def switchEnd(): Unit
+
+ /**
+ * Controls parsing of typeless (BytesType) alternative in switch case. If true,
+ * then target language does not support storing both bytes array and true object
+ * in the same variable, so we'll use workaround: bytes array will be read as
+ * _raw_ variable (which would be used anyway for all other cases as well). If
+ * false (which is default), we'll store *both* true objects and bytes array in
+ * the same variable.
+ */
+ def switchBytesOnlyAsRaw = false
+
+ /**
+ * Generate switch cases by calling case procedures. Suitable for a wide variety of
+ * target languages that something remotely resembling C-like `switch`-`case` statement.
+ * Thanks to customizable argument type for case procedures, can be used for switch type
+ * handling and a variety of other cases (i.e. switching between customizable endianness,
+ * etc).
+ * @param id attribute identifier
+ * @param on on expression to decide upon
+ * @param cases cases map: keys should be expressions, values are arbitrary typed objects
+ * that will be passed to case procedures
+ * @param normalCaseProc procedure that would handle "normal" (i.e. non-else case)
+ * @param elseCaseProc procedure that would handle "else" case
+ * @tparam T type of object to pass to procedures
+ */
+ def switchCases[T](
+ id: Identifier,
+ on: Ast.expr,
+ cases: Map[Ast.expr, T],
+ normalCaseProc: (T) => Unit,
+ elseCaseProc: (T) => Unit
+ ): Unit = {
+ switchStart(id, on)
+
+ // Pass 1: only normal case clauses
+ var first = true
+
+ cases.foreach { case (condition, result) =>
+ condition match {
+ case SwitchType.ELSE_CONST =>
+ // skip for now
+ case _ =>
+ if (first) {
+ switchCaseFirstStart(condition)
+ first = false
+ } else {
+ switchCaseStart(condition)
+ }
+ normalCaseProc(result)
+ switchCaseEnd()
+ }
+ }
+
+ // Pass 2: else clause, if it is there
+ cases.get(SwitchType.ELSE_CONST).foreach { (result) =>
+ switchElseStart()
+ elseCaseProc(result)
+ switchElseEnd()
+ }
+
+ switchEnd()
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/CalculateSeqSizes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/CalculateSeqSizes.scala
new file mode 100644
index 000000000..40c586d97
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/CalculateSeqSizes.scala
@@ -0,0 +1,134 @@
+package io.kaitai.struct.precompile
+
+import io.kaitai.struct.Log
+import io.kaitai.struct.datatype.DataType
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format._
+
+class CalculateSeqSizes(specs: ClassSpecs) {
+ def run(): Unit = specs.forEachRec(CalculateSeqSizes.getSeqSize)
+}
+
+object CalculateSeqSizes {
+ def sizeMultiply(sizeElement: Sized, repeat: RepeatSpec) = {
+ sizeElement match {
+ case FixedSized(elementSize) =>
+ repeat match {
+ case NoRepeat =>
+ sizeElement
+ case RepeatExpr(expr) =>
+ evaluateIntLiteral(expr) match {
+ case Some(count) => FixedSized(elementSize * count)
+ case None => DynamicSized
+ }
+ case _: RepeatUntil | RepeatEos =>
+ DynamicSized
+ }
+ case _ => sizeElement
+ }
+ }
+
+ def getSeqSize(curClass: ClassSpec): Sized = {
+ curClass.seqSize match {
+ case DynamicSized | _: FixedSized =>
+ // do nothing, it's already calculated
+ case StartedCalculationSized =>
+ // recursive size dependency encountered => we won't be able to determine
+ // let's break the infinite loop
+ curClass.seqSize = DynamicSized
+ case NotCalculatedSized =>
+ // launch the calculation
+ curClass.seqSize = StartedCalculationSized
+ val seqSize = forEachSeqAttr(curClass, (attr, seqPos, sizeElement, sizeContainer) => {})
+ curClass.seqSize = seqSize match {
+ case Some(size) => FixedSized(size)
+ case None => DynamicSized
+ }
+ }
+
+ Log.seqSizes.info(() => s"sizeof(${curClass.nameAsStr}) = ${curClass.seqSize}")
+ curClass.seqSize
+ }
+
+ /**
+ * Traverses type's sequence of attributes, calling operation for every attribute.
+ * Operation is called with arguments (attr, seqPos, sizeElement, sizeContainer)
+ * @param curClass type specification to traverse
+ * @param op operation to apply to every sequence attribute
+ * @return total size of sequence, if possible (i.e. it's fixed size)
+ */
+ def forEachSeqAttr(curClass: ClassSpec, op: (AttrSpec, Option[Int], Sized, Sized) => Unit): Option[Int] = {
+ var seqPos: Option[Int] = Some(0)
+ curClass.seq.foreach { attr =>
+ val sizeElement = dataTypeBitsSize(attr.dataType)
+ val sizeContainer = sizeMultiply(sizeElement, attr.cond.repeat)
+
+ op(attr, seqPos, sizeElement, sizeContainer)
+
+ seqPos = (seqPos, sizeContainer) match {
+ case (Some(pos), FixedSized(siz)) => Some(pos + siz)
+ case _ => None
+ }
+ }
+ seqPos
+ }
+
+ /**
+ * Determines how many bits occupies given data type.
+ *
+ * @param dataType data type to analyze
+ * @return number of bits or None, if it's impossible to determine a priori
+ */
+ def dataTypeBitsSize(dataType: DataType): Sized = {
+ dataType match {
+ case BitsType1 => FixedSized(1)
+ case BitsType(width) => FixedSized(width)
+ case EnumType(_, basedOn) => dataTypeBitsSize(basedOn)
+ case ut: UserTypeInstream => getSeqSize(ut.classSpec.get)
+ case _ =>
+ dataTypeByteSize(dataType) match {
+ case FixedSized(x) => FixedSized(x * 8)
+ case otherSize => otherSize
+ }
+ }
+ }
+
+ /**
+ * Determines how many bytes occupies a given data type.
+ *
+ * @param dataType data type to analyze
+ * @return number of bytes or None, if it's impossible to determine a priori
+ */
+ def dataTypeByteSize(dataType: DataType): Sized = {
+ dataType match {
+ case _: Int1Type => FixedSized(1)
+ case IntMultiType(_, width, _) => FixedSized(width.width)
+ case FixedBytesType(contents, _) => FixedSized(contents.length)
+ case FloatMultiType(width, _) => FixedSized(width.width)
+ case _: BytesEosType => DynamicSized
+ case blt: BytesLimitType => evaluateIntLiteral(blt.size) match {
+ case Some(x) => FixedSized(x)
+ case None => DynamicSized
+ }
+ case _: BytesTerminatedType => DynamicSized
+ case StrFromBytesType(basedOn, _) => dataTypeByteSize(basedOn)
+ case utb: UserTypeFromBytes => dataTypeByteSize(utb.bytes)
+ case st: SwitchType => DynamicSized // FIXME: it's really possible get size if st.hasSize
+ }
+ }
+
+ /**
+ * Evaluates the expression, if possible to get the result without introduction
+ * of any variables or anything.
+ *
+ * @param expr expression to evaluate
+ * @return integer result or None
+ */
+ def evaluateIntLiteral(expr: Ast.expr): Option[Int] = {
+ expr match {
+ case Ast.expr.IntNum(x) => Some(x.toInt)
+ case _ => None
+ }
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala
index a251b2fe1..8ecdcbaba 100644
--- a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala
@@ -9,12 +9,18 @@ import io.kaitai.struct.format.ClassSpec
* @param path YAML path components in file
* @param file file to report as erroneous, None means "main compilation unit"
*/
-class ErrorInInput(err: Throwable, path: List[String] = List(), file: Option[String] = None)
- extends RuntimeException(ErrorInInput.message(err, path, file))
+case class ErrorInInput(err: Throwable, val path: List[String] = List(), val file: Option[String] = None)
+ extends RuntimeException(ErrorInInput.message(err, path, file), err)
object ErrorInInput {
- private def message(err: Throwable, path: List[String], file: Option[String]) =
- s"${file.getOrElse("(main)")}: /${path.mkString("/")}: ${err.getMessage}"
+ private def message(err: Throwable, path: List[String], file: Option[String]) = {
+ val fileStr = file match {
+ case Some(x) => x.replace('\\', '/')
+ case None => "(main)"
+ }
+ val msg = Option(err.getMessage).getOrElse(err.toString)
+ s"$fileStr: /${path.mkString("/")}: $msg"
+ }
}
/**
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala
index 1a7649da0..1cb0c0304 100644
--- a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala
@@ -13,6 +13,8 @@ import scala.concurrent.Future
* @param specs collection of [[ClassSpec]] entries to work on
*/
class LoadImports(specs: ClassSpecs) {
+ import LoadImports._
+
/**
* Recursively loads and processes all .ksy files referenced in
* `meta/import` section of given class spec and all nested classes.
@@ -22,30 +24,37 @@ class LoadImports(specs: ClassSpecs) {
*
* @param curClass class spec to start recursive import from
*/
- def processClass(curClass: ClassSpec): Future[List[ClassSpec]] = {
- Log.importOps.info(() => s".. LoadImports: processing class ${curClass.nameAsStr}")
+ def processClass(curClass: ClassSpec, workDir: ImportPath): Future[List[ClassSpec]] = {
+ Log.importOps.info(() => s".. LoadImports: processing class ${curClass.nameAsStr} (workDir = $workDir)")
- val thisMetaFuture: Future[List[ClassSpec]] = curClass.meta match {
- case Some(meta) =>
- Future.sequence(meta.imports.zipWithIndex.map { case (name, idx) =>
- loadImport(name, meta.path ++ List("imports", idx.toString), Some(curClass.nameAsStr))
- }).map((x) => x.flatten)
- case None =>
- Future { List() }
- }
+ val thisMetaFuture: Future[List[ClassSpec]] =
+ Future.sequence(curClass.meta.imports.zipWithIndex.map { case (name, idx) =>
+ loadImport(
+ name,
+ curClass.meta.path ++ List("imports", idx.toString),
+ Some(curClass.nameAsStr),
+ workDir
+ )
+ }).map((x) => x.flatten)
val nestedFuture: Future[Iterable[ClassSpec]] = Future.sequence(curClass.types.map({
- case (_, nestedClass) => processClass(nestedClass)
+ case (_, nestedClass) => processClass(nestedClass, workDir)
})).map((listOfLists) => listOfLists.flatten)
Future.sequence(List(thisMetaFuture, nestedFuture)).map((x) => x.flatten)
}
- private def loadImport(name: String, path: List[String], inFile: Option[String]): Future[List[ClassSpec]] = {
- val futureSpec = if (name.startsWith("/")) {
- specs.importAbsolute(name.substring(1), path, inFile)
- } else {
- specs.importRelative(name, path, inFile)
+ private def loadImport(name: String, path: List[String], inFile: Option[String], workDir: ImportPath): Future[List[ClassSpec]] = {
+ Log.importOps.info(() => s".. LoadImports: loadImport($name, workDir = $workDir)")
+
+ val impPath = ImportPath.fromString(name)
+ val fullPath = ImportPath.add(workDir, impPath)
+
+ val futureSpec = fullPath match {
+ case RelativeImportPath(p) =>
+ specs.importRelative(p.mkString("/"), path, inFile)
+ case AbsoluteImportPath(p) =>
+ specs.importAbsolute(p.mkString("/"), path, inFile)
}
futureSpec.flatMap { case optSpec =>
@@ -60,7 +69,7 @@ class LoadImports(specs: ClassSpecs) {
// import* methods due to caching, but we won't rely on it here.
if (!specs.contains(specName)) {
specs(specName) = spec
- processClass(spec)
+ processClass(spec, ImportPath.updateWorkDir(workDir, impPath))
} else {
Future { List() }
}
@@ -70,3 +79,38 @@ class LoadImports(specs: ClassSpecs) {
}
}
}
+
+object LoadImports {
+ sealed trait ImportPath {
+ def baseDir: ImportPath
+ }
+ case class RelativeImportPath(path: List[String]) extends ImportPath {
+ override def baseDir: ImportPath = RelativeImportPath(path.init)
+ }
+ case class AbsoluteImportPath(path: List[String]) extends ImportPath {
+ override def baseDir: ImportPath = AbsoluteImportPath(path.init)
+ }
+ val BasePath = RelativeImportPath(List())
+
+ object ImportPath {
+ def fromString(s: String): ImportPath = if (s.startsWith("/")) {
+ AbsoluteImportPath(s.substring(1).split("/", -1).toList)
+ } else {
+ RelativeImportPath(s.split("/", -1).toList)
+ }
+
+ def add(curWorkDir: ImportPath, newPath: ImportPath): ImportPath = {
+ (curWorkDir, newPath) match {
+ case (_, AbsoluteImportPath(newPathAbs)) =>
+ AbsoluteImportPath(newPathAbs)
+ case (RelativeImportPath(curDir), RelativeImportPath(newPathRel)) =>
+ RelativeImportPath(curDir ++ newPathRel)
+ case (AbsoluteImportPath(curDir), RelativeImportPath(newPathRel)) =>
+ AbsoluteImportPath(curDir ++ newPathRel)
+ }
+ }
+
+ def updateWorkDir(curWorkDir: ImportPath, newPath: ImportPath): ImportPath =
+ add(curWorkDir, newPath).baseDir
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala
index 0a0beebf5..63f242196 100644
--- a/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/ParentTypes.scala
@@ -3,11 +3,14 @@ package io.kaitai.struct.precompile
import io.kaitai.struct.{ClassTypeProvider, Log}
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType.{ArrayType, SwitchType, UserType}
-import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.format._
import io.kaitai.struct.translators.TypeDetector
-object ParentTypes {
+class ParentTypes(classSpecs: ClassSpecs) {
+ def run(): Unit = {
+ classSpecs.foreach { case (_, curClass) => markup(curClass) }
+ }
+
def markup(curClass: ClassSpec): Unit = {
Log.typeProcParent.info(() => s"markupParentTypes(${curClass.nameAsStr})")
@@ -40,7 +43,7 @@ object ParentTypes {
Log.typeProcParent.info(() => s"..... no parent type added")
None
case Some(parent) =>
- val provider = new ClassTypeProvider(curClass)
+ val provider = new ClassTypeProvider(classSpecs, curClass)
val detector = new TypeDetector(provider)
val parentType = detector.detectType(parent)
Log.typeProcParent.info(() => s"..... enforced parent type = $parentType")
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala
index 37ba4d487..1fe9eb0c6 100644
--- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala
@@ -10,11 +10,7 @@ import io.kaitai.struct.format._
* converts names into ClassSpec / EnumSpec references.
*/
class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) {
- def run(): Unit =
- specs.foreach { case (_, spec) =>
- // FIXME: grab exception and rethrow more localized one, with a specName?
- resolveUserTypes(spec)
- }
+ def run(): Unit = specs.forEachRec(resolveUserTypes)
/**
* Resolves user types and enum types recursively starting from a certain
@@ -32,10 +28,6 @@ class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) {
// ignore all other types of instances
}
}
-
- curClass.types.foreach { case (_, nestedClass) =>
- resolveUserTypes(nestedClass)
- }
}
def resolveUserTypeForAttr(curClass: ClassSpec, attr: AttrLikeSpec): Unit =
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/SpecsValueTypeDerive.scala b/shared/src/main/scala/io/kaitai/struct/precompile/SpecsValueTypeDerive.scala
index 5d10845a5..74686d1a1 100644
--- a/shared/src/main/scala/io/kaitai/struct/precompile/SpecsValueTypeDerive.scala
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/SpecsValueTypeDerive.scala
@@ -12,7 +12,7 @@ class SpecsValueTypeDerive(specs: ClassSpecs) {
Log.typeProcValue.info(() => s"### SpecsValueTypeDerive: iteration #$iterNum")
specs.foreach { case (specName, spec) =>
Log.typeProcValue.info(() => s"#### $specName")
- val thisChanged = new ValueTypesDeriver(spec).run()
+ val thisChanged = new ValueTypesDeriver(specs, spec).run()
Log.typeProcValue.info(() => ".... => " + (if (thisChanged) "changed" else "no changes"))
hasChanged |= thisChanged
}
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala b/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala
index d6f37d613..6bd2b1210 100644
--- a/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala
@@ -1,6 +1,6 @@
package io.kaitai.struct.precompile
-import io.kaitai.struct.ClassTypeProvider
+import io.kaitai.struct.{ClassTypeProvider, Log}
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
@@ -11,17 +11,21 @@ import scala.reflect.ClassTag
/**
* Validates all expressions used inside the given ClassSpec to use expected types.
+ * @param specs bundle of class specifications (used only to find external references)
* @param topClass class to start check with
*/
-class TypeValidator(topClass: ClassSpec) {
- val provider = new ClassTypeProvider(topClass)
+class TypeValidator(specs: ClassSpecs, topClass: ClassSpec) {
+ val provider = new ClassTypeProvider(specs, topClass)
val detector = new TypeDetector(provider)
/**
* Starts the check from top-level class.
*/
- def run(): Unit =
- validateClass(topClass)
+ def run(): Unit = specs.forEachTopLevel { (specName, curClass) =>
+ Log.typeValid.info(() => s"validating top level class '$specName'")
+ provider.topClass = curClass
+ curClass.forEachRec(validateClass)
+ }
/**
* Performs validation of a single ClassSpec: would validate
@@ -31,6 +35,7 @@ class TypeValidator(topClass: ClassSpec) {
* @param curClass class to check
*/
def validateClass(curClass: ClassSpec): Unit = {
+ Log.typeValid.info(() => s"validateClass(${curClass.nameAsStr})")
provider.nowClass = curClass
curClass.seq.foreach(validateAttr)
@@ -43,10 +48,6 @@ class TypeValidator(topClass: ClassSpec) {
// TODO
}
}
-
- curClass.types.foreach { case (_, nestedClass) =>
- validateClass(nestedClass)
- }
}
/**
@@ -55,6 +56,8 @@ class TypeValidator(topClass: ClassSpec) {
* @param attr attribute to check
*/
def validateAttr(attr: AttrLikeSpec) {
+ Log.typeValid.info(() => s"validateAttr(${attr.id.humanReadable})")
+
val path = attr.path
attr.cond.ifExpr.foreach((ifExpr) =>
@@ -77,10 +80,21 @@ class TypeValidator(topClass: ClassSpec) {
/**
* Validates single non-composite data type, checking all expressions
* inside data type definition.
+ *
* @param dataType data type to check
* @param path original .ksy path to make error messages more meaningful
*/
def validateDataType(dataType: DataType, path: List[String]) {
+ // validate args vs params
+ dataType match {
+ case ut: UserType =>
+ // we only validate non-opaque types, opaque are unverifiable by definition
+ if (!ut.isOpaque)
+ validateArgsVsParams(ut.args, ut.classSpec.get.params, path ++ List("type"))
+ case _ =>
+ // no args or params in non-user types
+ }
+
dataType match {
case blt: BytesLimitType =>
checkAssert[IntType](blt.size, "integer", path, "size")
@@ -105,12 +119,38 @@ class TypeValidator(topClass: ClassSpec) {
} catch {
case tme: TypeMismatchError =>
throw new YAMLParseException(tme.getMessage, casePath)
+ case err: Throwable =>
+ throw new ErrorInInput(err, casePath)
}
}
validateDataType(caseType, casePath)
}
}
+ /**
+ * Validates that arguments given for a certain type match list of parameters
+ * declared for that type.
+ * @param args arguments given in invocation
+ * @param params parameters declared in a user type
+ * @param path path where invocation happens
+ * @return
+ */
+ def validateArgsVsParams(args: Seq[Ast.expr], params: List[ParamDefSpec], path: List[String]): Unit = {
+ if (args.size != params.size)
+ throw YAMLParseException.invalidParamCount(params.size, args.size, path)
+
+ args.indices.foreach { (i) =>
+ val arg = args(i)
+ val param = params(i)
+ val tArg = detector.detectType(arg)
+ val tParam = param.dataType
+
+ if (!TypeDetector.canAssign(tArg, tParam)) {
+ throw YAMLParseException.paramMismatch(i, tArg, param.id.humanReadable, tParam, path)
+ }
+ }
+ }
+
/**
* Checks that expression's type conforms to a given datatype, otherwise
* throw a human-readable exception, with some pointers that would help
@@ -134,11 +174,19 @@ class TypeValidator(topClass: ClassSpec) {
try {
detector.detectType(expr) match {
case _: T => // good
+ case st: SwitchType =>
+ st.combinedType match {
+ case _: T => // good
+ case actual =>
+ throw YAMLParseException.exprType(expectStr, actual, path ++ List(pathKey))
+ }
case actual => throw YAMLParseException.exprType(expectStr, actual, path ++ List(pathKey))
}
} catch {
+ case err: InvalidIdentifier =>
+ throw new ErrorInInput(err, path ++ List(pathKey))
case err: ExpressionError =>
- throw new YAMLParseException(err.getMessage, path ++ List(pathKey))
+ throw new ErrorInInput(err, path ++ List(pathKey))
}
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ValueTypesDeriver.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ValueTypesDeriver.scala
index 6f3e6195c..50cf82c30 100644
--- a/shared/src/main/scala/io/kaitai/struct/precompile/ValueTypesDeriver.scala
+++ b/shared/src/main/scala/io/kaitai/struct/precompile/ValueTypesDeriver.scala
@@ -1,11 +1,11 @@
package io.kaitai.struct.precompile
-import io.kaitai.struct.format.{ClassSpec, ValueInstanceSpec, YAMLParseException}
+import io.kaitai.struct.format.{ClassSpec, ClassSpecs, ValueInstanceSpec, YAMLParseException}
import io.kaitai.struct.translators.TypeDetector
import io.kaitai.struct.{ClassTypeProvider, Log}
-class ValueTypesDeriver(topClass: ClassSpec) {
- val provider = new ClassTypeProvider(topClass)
+class ValueTypesDeriver(specs: ClassSpecs, topClass: ClassSpec) {
+ val provider = new ClassTypeProvider(specs, topClass)
val detector = new TypeDetector(provider)
def run(): Boolean =
@@ -34,10 +34,7 @@ class ValueTypesDeriver(topClass: ClassSpec) {
hasUndecided = true
// just ignore, we're not there yet, probably we'll get it on next iteration
case err: ExpressionError =>
- throw new YAMLParseException(
- err.getMessage,
- vi.path ++ List("value")
- )
+ throw new ErrorInInput(err, vi.path ++ List("value"))
}
case Some(_) =>
// already derived, do nothing
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/AbstractTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/AbstractTranslator.scala
new file mode 100644
index 000000000..a96140ea7
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/translators/AbstractTranslator.scala
@@ -0,0 +1,20 @@
+package io.kaitai.struct.translators
+
+import io.kaitai.struct.exprlang.Ast
+
+/**
+ * Translators are per-target language classes which implement translation
+ * of Kaitai Struct expression language into expression in target language.
+ *
+ * This simplest, most abstract form of translator provides only a single
+ * `translate` method, which takes KS expression and returns string in
+ * target language.
+ */
+trait AbstractTranslator {
+ /**
+ * Translates KS expression into an expression in some target language.
+ * @param v KS expression to translate
+ * @return expression in target language as string
+ */
+ def translate(v: Ast.expr): String
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala
index 73e411f10..2c2c808cb 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala
@@ -5,7 +5,43 @@ import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.precompile.TypeMismatchError
-abstract class BaseTranslator(val provider: TypeProvider) extends TypeDetector(provider) {
+/**
+ * BaseTranslator is a common semi-abstract implementation of a translator
+ * API (i.e. [[AbstractTranslator]]), which fits target languages that
+ * follow "every KS expression is translatable into expression" paradigm.
+ * Main [[AbstractTranslator.translate]] method is implemented as a huge
+ * case matching, which usually just calls relevant abstract methods for
+ * every particular piece of KS expression, i.e. literals, operations,
+ * method calls, etc.
+ *
+ * Given that there are many of these abstract methods, to make it more
+ * maintainable, they are grouped into several abstract traits:
+ * [[CommonLiterals]], [[CommonOps]].
+ *
+ * This translator implementation also handles user-defined types and
+ * fields properly - it uses given [[TypeProvider]] to resolve these.
+ *
+ * @param provider TypeProvider that will answer queries on user types
+ */
+abstract class BaseTranslator(val provider: TypeProvider)
+ extends TypeDetector(provider)
+ with AbstractTranslator
+ with CommonLiterals
+ with CommonOps
+ with CommonMethods[String] {
+
+ /**
+ * Translates KS expression into an expression in some target language.
+ * Note that this implementation may throw errors subclassed off the
+ * [[io.kaitai.struct.precompile.ExpressionError]] when encountering
+ * some sort of logical error in expression (i.e. invalid usage of
+ * operator, type mismatch, etc). Typically, one's supposed to catch
+ * and rethrow it, wrapped in [[io.kaitai.struct.precompile.ErrorInInput]]
+ * to assist error reporting in KSC.
+ *
+ * @param v KS expression to translate
+ * @return expression in target language as string
+ */
def translate(v: Ast.expr): String = {
v match {
case Ast.expr.IntNum(n) =>
@@ -24,8 +60,13 @@ abstract class BaseTranslator(val provider: TypeProvider) extends TypeDetector(p
doEnumByLabel(enumSpec.name, label.name)
case Ast.expr.Name(name: Ast.identifier) =>
doLocalName(name.name)
- case Ast.expr.UnaryOp(op: Ast.unaryop, v: Ast.expr) =>
- s"${unaryOp(op)}${translate(v)}"
+ case Ast.expr.UnaryOp(op: Ast.unaryop, inner: Ast.expr) =>
+ unaryOp(op) + (inner match {
+ case Ast.expr.IntNum(_) | Ast.expr.FloatNum(_) =>
+ translate(inner)
+ case _ =>
+ s"(${translate(inner)})"
+ })
case Ast.expr.Compare(left: Ast.expr, op: Ast.cmpop, right: Ast.expr) =>
(detectType(left), detectType(right)) match {
case (_: NumericType, _: NumericType) =>
@@ -40,6 +81,8 @@ abstract class BaseTranslator(val provider: TypeProvider) extends TypeDetector(p
}
case (_: StrType, _: StrType) =>
doStrCompareOp(left, op, right)
+ case (_: BytesType, _: BytesType) =>
+ doBytesCompareOp(left, op, right)
case (EnumType(ltype, _), EnumType(rtype, _)) =>
if (ltype != rtype) {
throw new TypeMismatchError(s"can't compare enums type $ltype and $rtype")
@@ -69,56 +112,10 @@ abstract class BaseTranslator(val provider: TypeProvider) extends TypeDetector(p
case idxType =>
throw new TypeMismatchError(s"can't use $idx as array index (need int, got $idxType)")
}
- case Ast.expr.Attribute(value: Ast.expr, attr: Ast.identifier) =>
- val valType = detectType(value)
- valType match {
- case _: UserType =>
- userTypeField(value, attr.name)
- case _: StrType =>
- attr.name match {
- case "length" => strLength(value)
- case "reverse" => strReverse(value)
- case "to_i" => strToInt(value, Ast.expr.IntNum(10))
- }
- case _: IntType =>
- attr.name match {
- case "to_s" => intToStr(value, Ast.expr.IntNum(10))
- }
- case ArrayType(inType) =>
- attr.name match {
- case "first" => arrayFirst(value)
- case "last" => arrayLast(value)
- case "size" => arraySize(value)
- }
- case KaitaiStreamType =>
- attr.name match {
- case "size" => kaitaiStreamSize(value)
- case "eof" => kaitaiStreamEof(value)
- case "pos" => kaitaiStreamPos(value)
- }
- case et: EnumType =>
- attr.name match {
- case "to_i" => enumToInt(value, et)
- case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
- }
- case _: BooleanType =>
- attr.name match {
- case "to_i" => boolToInt(value)
- case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
- }
- }
- case Ast.expr.Call(func: Ast.expr, args: Seq[Ast.expr]) =>
- func match {
- case Ast.expr.Attribute(obj: Ast.expr, methodName: Ast.identifier) =>
- val objType = detectType(obj)
- (objType, methodName.name) match {
- // TODO: check argument quantity
- case (_: StrType, "substring") => strSubstring(obj, args(0), args(1))
- case (_: StrType, "to_i") => strToInt(obj, args(0))
- case (_: BytesType, "to_s") => bytesToStr(translate(obj), args(0))
- case _ => throw new TypeMismatchError(s"don't know how to call method '$methodName' of object type '$objType'")
- }
- }
+ case call: Ast.expr.Attribute =>
+ translateAttribute(call)
+ case call: Ast.expr.Call =>
+ translateCall(call)
case Ast.expr.List(values: Seq[Ast.expr]) =>
val t = detectArrayType(values)
t match {
@@ -142,169 +139,37 @@ abstract class BaseTranslator(val provider: TypeProvider) extends TypeDetector(p
}
}
- def numericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) = {
- s"(${translate(left)} ${binOp(op)} ${translate(right)})"
- }
-
- def binOp(op: Ast.operator): String = {
- op match {
- case Ast.operator.Add => "+"
- case Ast.operator.Sub => "-"
- case Ast.operator.Mult => "*"
- case Ast.operator.Div => "/"
- case Ast.operator.Mod => "%"
- case Ast.operator.BitAnd => "&"
- case Ast.operator.BitOr => "|"
- case Ast.operator.BitXor => "^"
- case Ast.operator.LShift => "<<"
- case Ast.operator.RShift => ">>"
- }
- }
-
- def doNumericCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
- s"${translate(left)} ${cmpOp(op)} ${translate(right)}"
-
- def doStrCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
- s"${translate(left)} ${cmpOp(op)} ${translate(right)}"
-
- def doEnumCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
- s"${translate(left)} ${cmpOp(op)} ${translate(right)}"
-
- def cmpOp(op: Ast.cmpop): String = {
- op match {
- case Ast.cmpop.Lt => "<"
- case Ast.cmpop.LtE => "<="
- case Ast.cmpop.Gt => ">"
- case Ast.cmpop.GtE => ">="
- case Ast.cmpop.Eq => "=="
- case Ast.cmpop.NotEq => "!="
- }
- }
-
- def doBooleanOp(op: Ast.boolop, values: Seq[Ast.expr]): String = {
- val opStr = s"${booleanOp(op)}"
- val dividerStr = s") $opStr ("
- val valuesStr = values.map(translate).mkString("(", dividerStr, ")")
-
- // Improve compatibility for statements like: ( ... && ... || ... ) ? ... : ...
- s" ($valuesStr) "
- }
-
- def booleanOp(op: Ast.boolop): String = op match {
- case Ast.boolop.Or => "||"
- case Ast.boolop.And => "&&"
- }
-
- def unaryOp(op: Ast.unaryop): String = op match {
- case Ast.unaryop.Invert => "~"
- case Ast.unaryop.Minus => "-"
- case Ast.unaryop.Not => "!"
- }
-
def doSubscript(container: Ast.expr, idx: Ast.expr): String
def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String
def doCast(value: Ast.expr, typeName: String): String = translate(value)
- // Literals
- def doIntLiteral(n: BigInt): String = n.toString
- def doFloatLiteral(n: Any): String = n.toString
-
- def doStringLiteral(s: String): String = {
- val encoded = s.toCharArray.map((code) =>
- if (code <= 0xff) {
- strLiteralAsciiChar(code)
- } else {
- strLiteralUnicode(code)
- }
- ).mkString
- "\"" + encoded + "\""
- }
- def doBoolLiteral(n: Boolean): String = n.toString
def doArrayLiteral(t: DataType, value: Seq[Ast.expr]): String = "[" + value.map((v) => translate(v)).mkString(", ") + "]"
def doByteArrayLiteral(arr: Seq[Byte]): String = "[" + arr.map(_ & 0xff).mkString(", ") + "]"
- /**
- * Handle ASCII character conversion for inlining into string literals.
- * Default implementation consults [[asciiCharQuoteMap]] first, then
- * just dumps it as is if it's a printable ASCII charcter, or calls
- * [[strLiteralGenericCC]] if it's a control character.
- * @param code character code to convert into string for inclusion in
- * a string literal
- */
- def strLiteralAsciiChar(code: Char): String = {
- asciiCharQuoteMap.get(code) match {
- case Some(encoded) => encoded
- case None =>
- if (code >= 0x20 && code < 0x80) {
- Character.toString(code)
- } else {
- strLiteralGenericCC(code)
- }
- }
- }
-
- /**
- * Converts generic control character code into something that's allowed
- * inside a string literal. Default implementation uses octal encoding,
- * which is ok for most C-derived languages.
- *
- * Note that we use strictly 3 octal digits to work around potential
- * problems with following decimal digits, i.e. "\0" + "2" that would be
- * parsed as single character "\02" = "\x02", instead of two characters
- * "\x00\x32".
- * @param code character code to represent
- * @return string literal representation of given code
- */
- def strLiteralGenericCC(code: Char): String =
- "\\%03o".format(code.toInt)
-
- /**
- * Converts Unicode (typically, non-ASCII) character code into something
- * that's allowed inside a string literal. Default implementation uses
- * Unicode 4-digit hex encoding, which is ok for most C-derived languages.
- * @param code character code to represent
- * @return string literal representation of given code
- */
- def strLiteralUnicode(code: Char): String =
- "\\u%04x".format(code.toInt)
-
- /**
- * Character quotation map for inclusion in string literals.
- * Default implementation includes bare minimum that seems
- * to be available in all languages.
- */
- val asciiCharQuoteMap: Map[Char, String] = Map(
- '\t' -> "\\t",
- '\n' -> "\\n",
- '\r' -> "\\r",
- '"' -> "\\\"",
- '\\' -> "\\\\"
- )
-
def doLocalName(s: String): String = doName(s)
def doName(s: String): String
- def userTypeField(value: Ast.expr, attrName: String): String =
- s"${translate(value)}.${doName(attrName)}"
+ def userTypeField(userType: UserType, value: Ast.expr, attrName: String): String =
+ anyField(value, attrName)
+
def doEnumByLabel(enumTypeAbs: List[String], label: String): String
def doEnumById(enumTypeAbs: List[String], id: String): String
// Predefined methods of various types
def strConcat(left: Ast.expr, right: Ast.expr): String = s"${translate(left)} + ${translate(right)}"
- def strToInt(s: Ast.expr, base: Ast.expr): String
- def enumToInt(value: Ast.expr, et: EnumType): String
def boolToInt(value: Ast.expr): String =
doIfExp(value, Ast.expr.IntNum(1), Ast.expr.IntNum(0))
- def intToStr(i: Ast.expr, base: Ast.expr): String
- def bytesToStr(bytesExpr: String, encoding: Ast.expr): String
- def strLength(s: Ast.expr): String
- def strReverse(s: Ast.expr): String
- def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): String
- def arrayFirst(a: Ast.expr): String
- def arrayLast(a: Ast.expr): String
- def arraySize(a: Ast.expr): String
+ def kaitaiStreamSize(value: Ast.expr): String = anyField(value, "size")
+ def kaitaiStreamEof(value: Ast.expr): String = anyField(value, "is_eof")
+ def kaitaiStreamPos(value: Ast.expr): String = anyField(value, "pos")
+
+ // Special convenience definition method + helper
+ override def bytesToStr(value: Ast.expr, expr: Ast.expr): String =
+ bytesToStr(translate(value), expr)
+ def bytesToStr(value: String, expr: Ast.expr): String
- def kaitaiStreamSize(value: Ast.expr): String = userTypeField(value, "size")
- def kaitaiStreamEof(value: Ast.expr): String = userTypeField(value, "is_eof")
- def kaitaiStreamPos(value: Ast.expr): String = userTypeField(value, "pos")
+ // Helper that does simple "one size fits all" attribute calling, if it is useful
+ // for the language
+ def anyField(value: Ast.expr, attrName: String): String =
+ s"${translate(value)}.${doName(attrName)}"
}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/CSharpTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/CSharpTranslator.scala
index 7e7737a59..26a030b89 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/CSharpTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/CSharpTranslator.scala
@@ -1,13 +1,14 @@
package io.kaitai.struct.translators
-import io.kaitai.struct.Utils
+import io.kaitai.struct.{ImportList, Utils}
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast._
+import io.kaitai.struct.format.Identifier
import io.kaitai.struct.languages.CSharpCompiler
-class CSharpTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
+class CSharpTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
override def doArrayLiteral(t: DataType, value: Seq[expr]): String = {
val nativeType = CSharpCompiler.kaitaiType2NativeType(t)
val commaStr = value.map((v) => translate(v)).mkString(", ")
@@ -43,10 +44,15 @@ class CSharpTranslator(provider: TypeProvider) extends BaseTranslator(provider)
}
override def doName(s: String) =
- if (s.startsWith("_"))
- s"M${Utils.upperCamelCase(s)}"
- else
+ if (s.startsWith("_")) {
+ s match {
+ case Identifier.SWITCH_ON => "on"
+ case Identifier.INDEX => "i"
+ case _ => s"M${Utils.upperCamelCase(s)}"
+ }
+ } else {
s"${Utils.upperCamelCase(s)}"
+ }
override def doEnumByLabel(enumTypeAbs: List[String], label: String): String =
s"${enumClass(enumTypeAbs)}.${Utils.upperCamelCase(label)}"
@@ -68,6 +74,9 @@ class CSharpTranslator(provider: TypeProvider) extends BaseTranslator(provider)
}
}
+ override def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
+ s"(${CSharpCompiler.kstreamName}.ByteArrayCompare(${translate(left)}, ${translate(right)}) ${cmpOp(op)} 0)"
+
override def doSubscript(container: expr, idx: expr): String =
s"${translate(container)}[${translate(idx)}]"
override def doIfExp(condition: expr, ifTrue: expr, ifFalse: expr): String =
@@ -76,12 +85,18 @@ class CSharpTranslator(provider: TypeProvider) extends BaseTranslator(provider)
s"((${Utils.upperCamelCase(typeName)}) (${translate(value)}))"
// Predefined methods of various types
- override def strToInt(s: expr, base: expr): String =
+ override def strToInt(s: expr, base: expr): String = {
+ importList.add("System")
s"Convert.ToInt64(${translate(s)}, ${translate(base)})"
+ }
override def enumToInt(v: expr, et: EnumType): String =
translate(v)
- override def intToStr(i: expr, base: expr): String =
- s"Convert.ToString(${translate(i)}, ${translate(base)})"
+ override def floatToInt(v: expr): String =
+ s"(long) (${translate(v)})"
+ override def intToStr(i: expr, base: expr): String = {
+ importList.add("System")
+ s"Convert.ToString((long) (${translate(i)}), ${translate(base)})"
+ }
override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String =
s"System.Text.Encoding.GetEncoding(${translate(encoding)}).GetString($bytesExpr)"
override def strLength(s: expr): String =
@@ -99,8 +114,16 @@ class CSharpTranslator(provider: TypeProvider) extends BaseTranslator(provider)
s"${translate(a)}[0]"
override def arrayLast(a: expr): String = {
val v = translate(a)
- s"$v[$v.Length - 1]"
+ s"$v[$v.Count - 1]"
}
override def arraySize(a: expr): String =
s"${translate(a)}.Count"
+ override def arrayMin(a: Ast.expr): String = {
+ importList.add("System.Linq")
+ s"${translate(a)}.Min()"
+ }
+ override def arrayMax(a: Ast.expr): String = {
+ importList.add("System.Linq")
+ s"${translate(a)}.Max()"
+ }
}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/CommonLiterals.scala b/shared/src/main/scala/io/kaitai/struct/translators/CommonLiterals.scala
new file mode 100644
index 000000000..29ee8ee4a
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/translators/CommonLiterals.scala
@@ -0,0 +1,80 @@
+package io.kaitai.struct.translators
+
+/**
+ * Implementations of translations of literals in C-style, common to many
+ * languages.
+ */
+trait CommonLiterals {
+ def doIntLiteral(n: BigInt): String = n.toString
+ def doFloatLiteral(n: Any): String = n.toString
+
+ def doStringLiteral(s: String): String = {
+ val encoded = s.toCharArray.map((code) =>
+ if (code <= 0xff) {
+ strLiteralAsciiChar(code)
+ } else {
+ strLiteralUnicode(code)
+ }
+ ).mkString
+ "\"" + encoded + "\""
+ }
+ def doBoolLiteral(n: Boolean): String = n.toString
+
+ /**
+ * Handle ASCII character conversion for inlining into string literals.
+ * Default implementation consults [[asciiCharQuoteMap]] first, then
+ * just dumps it as is if it's a printable ASCII charcter, or calls
+ * [[strLiteralGenericCC]] if it's a control character.
+ * @param code character code to convert into string for inclusion in
+ * a string literal
+ */
+ def strLiteralAsciiChar(code: Char): String = {
+ asciiCharQuoteMap.get(code) match {
+ case Some(encoded) => encoded
+ case None =>
+ if (code >= 0x20 && code < 0x80) {
+ Character.toString(code)
+ } else {
+ strLiteralGenericCC(code)
+ }
+ }
+ }
+
+ /**
+ * Converts generic control character code into something that's allowed
+ * inside a string literal. Default implementation uses octal encoding,
+ * which is ok for most C-derived languages.
+ *
+ * Note that we use strictly 3 octal digits to work around potential
+ * problems with following decimal digits, i.e. "\0" + "2" that would be
+ * parsed as single character "\02" = "\x02", instead of two characters
+ * "\x00\x32".
+ * @param code character code to represent
+ * @return string literal representation of given code
+ */
+ def strLiteralGenericCC(code: Char): String =
+ "\\%03o".format(code.toInt)
+
+ /**
+ * Converts Unicode (typically, non-ASCII) character code into something
+ * that's allowed inside a string literal. Default implementation uses
+ * Unicode 4-digit hex encoding, which is ok for most C-derived languages.
+ * @param code character code to represent
+ * @return string literal representation of given code
+ */
+ def strLiteralUnicode(code: Char): String =
+ "\\u%04x".format(code.toInt)
+
+ /**
+ * Character quotation map for inclusion in string literals.
+ * Default implementation includes bare minimum that seems
+ * to be available in all languages.
+ */
+ val asciiCharQuoteMap: Map[Char, String] = Map(
+ '\t' -> "\\t",
+ '\n' -> "\\n",
+ '\r' -> "\\r",
+ '"' -> "\\\"",
+ '\\' -> "\\\\"
+ )
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala b/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala
new file mode 100644
index 000000000..b71faa868
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/translators/CommonMethods.scala
@@ -0,0 +1,99 @@
+package io.kaitai.struct.translators
+
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.precompile.TypeMismatchError
+
+abstract trait CommonMethods[T] extends TypeDetector {
+ def translateAttribute(call: Ast.expr.Attribute): T = {
+ val attr = call.attr
+ val value = call.value
+ val valType = detectType(value)
+ valType match {
+ case ut: UserType =>
+ userTypeField(ut, value, attr.name)
+ case _: StrType =>
+ attr.name match {
+ case "length" => strLength(value)
+ case "reverse" => strReverse(value)
+ case "to_i" => strToInt(value, Ast.expr.IntNum(10))
+ }
+ case _: IntType =>
+ attr.name match {
+ case "to_s" => intToStr(value, Ast.expr.IntNum(10))
+ }
+ case _: FloatType =>
+ attr.name match {
+ case "to_i" => floatToInt(value)
+ }
+ case ArrayType(inType) =>
+ attr.name match {
+ case "first" => arrayFirst(value)
+ case "last" => arrayLast(value)
+ case "size" => arraySize(value)
+ case "min" => arrayMin(value)
+ case "max" => arrayMax(value)
+ }
+ case KaitaiStreamType =>
+ attr.name match {
+ case "size" => kaitaiStreamSize(value)
+ case "eof" => kaitaiStreamEof(value)
+ case "pos" => kaitaiStreamPos(value)
+ }
+ case et: EnumType =>
+ attr.name match {
+ case "to_i" => enumToInt(value, et)
+ case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
+ }
+ case _: BooleanType =>
+ attr.name match {
+ case "to_i" => boolToInt(value)
+ case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
+ }
+ }
+ }
+
+ def translateCall(call: Ast.expr.Call): T = {
+ val func = call.func
+ val args = call.args
+
+ func match {
+ case Ast.expr.Attribute(obj: Ast.expr, methodName: Ast.identifier) =>
+ val objType = detectType(obj)
+ (objType, methodName.name) match {
+ // TODO: check argument quantity
+ case (_: StrType, "substring") => strSubstring(obj, args(0), args(1))
+ case (_: StrType, "to_i") => strToInt(obj, args(0))
+ case (_: BytesType, "to_s") => bytesToStr(obj, args(0))
+ case _ => throw new TypeMismatchError(s"don't know how to call method '$methodName' of object type '$objType'")
+ }
+ }
+ }
+
+ def userTypeField(ut: UserType, value: Ast.expr, name: String): T
+
+ def strLength(s: Ast.expr): T
+ def strReverse(s: Ast.expr): T
+ def strToInt(s: Ast.expr, base: Ast.expr): T
+ def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): T
+
+ def bytesToStr(value: Ast.expr, expr: Ast.expr): T
+
+ def intToStr(value: Ast.expr, num: Ast.expr): T
+
+ def floatToInt(value: Ast.expr): T
+
+ def kaitaiStreamSize(value: Ast.expr): T
+ def kaitaiStreamEof(value: Ast.expr): T
+ def kaitaiStreamPos(value: Ast.expr): T
+
+ def arrayFirst(a: Ast.expr): T
+ def arrayLast(a: Ast.expr): T
+ def arraySize(a: Ast.expr): T
+ def arrayMin(a: Ast.expr): T
+ def arrayMax(a: Ast.expr): T
+
+ def enumToInt(value: Ast.expr, et: EnumType): T
+
+ def boolToInt(value: Ast.expr): T
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/CommonOps.scala b/shared/src/main/scala/io/kaitai/struct/translators/CommonOps.scala
new file mode 100644
index 000000000..a55019e55
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/translators/CommonOps.scala
@@ -0,0 +1,67 @@
+package io.kaitai.struct.translators
+
+import io.kaitai.struct.exprlang.Ast
+
+trait CommonOps extends AbstractTranslator {
+ def numericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) = {
+ s"(${translate(left)} ${binOp(op)} ${translate(right)})"
+ }
+
+ def binOp(op: Ast.operator): String = {
+ op match {
+ case Ast.operator.Add => "+"
+ case Ast.operator.Sub => "-"
+ case Ast.operator.Mult => "*"
+ case Ast.operator.Div => "/"
+ case Ast.operator.Mod => "%"
+ case Ast.operator.BitAnd => "&"
+ case Ast.operator.BitOr => "|"
+ case Ast.operator.BitXor => "^"
+ case Ast.operator.LShift => "<<"
+ case Ast.operator.RShift => ">>"
+ }
+ }
+
+ def doNumericCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
+ s"${translate(left)} ${cmpOp(op)} ${translate(right)}"
+
+ def doStrCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
+ s"${translate(left)} ${cmpOp(op)} ${translate(right)}"
+
+ def doEnumCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
+ s"${translate(left)} ${cmpOp(op)} ${translate(right)}"
+
+ def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
+ s"${translate(left)} ${cmpOp(op)} ${translate(right)}"
+
+ def cmpOp(op: Ast.cmpop): String = {
+ op match {
+ case Ast.cmpop.Lt => "<"
+ case Ast.cmpop.LtE => "<="
+ case Ast.cmpop.Gt => ">"
+ case Ast.cmpop.GtE => ">="
+ case Ast.cmpop.Eq => "=="
+ case Ast.cmpop.NotEq => "!="
+ }
+ }
+
+ def doBooleanOp(op: Ast.boolop, values: Seq[Ast.expr]): String = {
+ val opStr = s"${booleanOp(op)}"
+ val dividerStr = s") $opStr ("
+ val valuesStr = values.map(translate).mkString("(", dividerStr, ")")
+
+ // Improve compatibility for statements like: ( ... && ... || ... ) ? ... : ...
+ s" ($valuesStr) "
+ }
+
+ def booleanOp(op: Ast.boolop): String = op match {
+ case Ast.boolop.Or => "||"
+ case Ast.boolop.And => "&&"
+ }
+
+ def unaryOp(op: Ast.unaryop): String = op match {
+ case Ast.unaryop.Invert => "~"
+ case Ast.unaryop.Minus => "-"
+ case Ast.unaryop.Not => "!"
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/CppTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/CppTranslator.scala
index 0378829dc..2ee150df3 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/CppTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/CppTranslator.scala
@@ -2,7 +2,7 @@ package io.kaitai.struct.translators
import java.nio.charset.Charset
-import io.kaitai.struct.Utils
+import io.kaitai.struct.{ImportList, Utils}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.datatype.DataType
@@ -10,7 +10,7 @@ import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.format.Identifier
import io.kaitai.struct.languages.CppCompiler
-class CppTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
+class CppTranslator(provider: TypeProvider, importListSrc: ImportList) extends BaseTranslator(provider) {
val CHARSET_UTF8 = Charset.forName("UTF-8")
/**
@@ -66,12 +66,13 @@ class CppTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
}
}
- override def userTypeField(value: expr, attrName: String): String =
+ override def anyField(value: expr, attrName: String): String =
s"${translate(value)}->${doName(attrName)}"
override def doName(s: String) = s match {
case Identifier.ITERATOR => "_"
case Identifier.ITERATOR2 => "_buf"
+ case Identifier.INDEX => "i"
case _ => s"$s()"
}
@@ -108,7 +109,9 @@ class CppTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
override def enumToInt(v: expr, et: EnumType): String =
translate(v)
override def boolToInt(v: expr): String =
- translate(v)
+ s"((${translate(v)}) ? 1 : 0)"
+ override def floatToInt(v: expr): String =
+ s"static_cast(${translate(v)})"
override def intToStr(i: expr, base: expr): String = {
val baseStr = translate(base)
baseStr match {
@@ -134,4 +137,14 @@ class CppTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
s"${translate(a)}->back()"
override def arraySize(a: expr): String =
s"${translate(a)}->size()"
+ override def arrayMin(a: expr): String = {
+ importListSrc.add("algorithm")
+ val v = translate(a)
+ s"*std::min_element($v->begin(), $v->end())"
+ }
+ override def arrayMax(a: expr): String = {
+ importListSrc.add("algorithm")
+ val v = translate(a)
+ s"*std::max_element($v->begin(), $v->end())"
+ }
}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala
new file mode 100644
index 000000000..b4be487eb
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala
@@ -0,0 +1,322 @@
+package io.kaitai.struct.translators
+
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format.{ClassSpec, Identifier}
+import io.kaitai.struct.languages.GoCompiler
+import io.kaitai.struct.precompile.TypeMismatchError
+import io.kaitai.struct.{ImportList, StringLanguageOutputWriter, Utils}
+
+sealed trait TranslatorResult
+case class ResultString(s: String) extends TranslatorResult
+case class ResultLocalVar(n: Int) extends TranslatorResult
+
+class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, importList: ImportList)
+ extends TypeDetector(provider)
+ with AbstractTranslator
+ with CommonLiterals
+ with CommonOps
+ with CommonMethods[TranslatorResult] {
+
+ var returnRes: Option[String] = None
+
+ override def translate(v: Ast.expr): String = resToStr(translateExpr(v))
+
+ def resToStr(r: TranslatorResult): String = r match {
+ case ResultString(s) => s
+ case ResultLocalVar(n) => localVarName(n)
+ }
+
+ def translateExpr(v: Ast.expr): TranslatorResult = {
+ v match {
+ case Ast.expr.IntNum(n) =>
+ trIntLiteral(n)
+ case Ast.expr.FloatNum(n) =>
+ trFloatLiteral(n)
+ case Ast.expr.Str(s) =>
+ trStringLiteral(s)
+ case Ast.expr.Bool(n) =>
+ trBoolLiteral(n)
+
+// case Ast.expr.BoolOp(op, values) =>
+ case Ast.expr.BinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) =>
+ (detectType(left), detectType(right), op) match {
+ case (_: NumericType, _: NumericType, _) =>
+ trNumericBinOp(left, op, right)
+ case (_: StrType, _: StrType, Ast.operator.Add) =>
+ trStrConcat(left, right)
+ case (ltype, rtype, _) =>
+ throw new TypeMismatchError(s"can't do $ltype $op $rtype")
+ }
+// case Ast.expr.UnaryOp(op, operand) =>
+// case Ast.expr.IfExp(condition, ifTrue, ifFalse) =>
+// case Ast.expr.Compare(left, ops, right) =>
+// case Ast.expr.EnumByLabel(enumName, label) =>
+// case Ast.expr.EnumById(enumName, id) =>
+// case Ast.expr.CastToType(value, typeName) =>
+// case Ast.expr.Subscript(value, idx) =>
+ case Ast.expr.Name(name: Ast.identifier) =>
+ trLocalName(name.name)
+// case Ast.expr.List(elts) =>
+ case call: Ast.expr.Attribute =>
+ translateAttribute(call)
+ case call: Ast.expr.Call =>
+ translateCall(call)
+ }
+ }
+
+ def trIntLiteral(n: BigInt): TranslatorResult = ResultString(doIntLiteral(n))
+ def trFloatLiteral(n: BigDecimal): TranslatorResult = ResultString(doFloatLiteral(n))
+ def trStringLiteral(s: String): TranslatorResult = ResultString(doStringLiteral(s))
+ def trBoolLiteral(n: Boolean): TranslatorResult = ResultString(doBoolLiteral(n))
+
+ def trNumericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) =
+ ResultString(numericBinOp(left, op, right))
+
+ def trStrConcat(left: Ast.expr, right: Ast.expr): TranslatorResult =
+ ResultString(translate(left) + " + " + translate(right))
+
+// override def doArrayLiteral(t: DataType, value: Seq[Ast.expr]): String = {
+// val javaType = JavaCompiler.kaitaiType2JavaTypeBoxed(t)
+// val commaStr = value.map((v) => translate(v)).mkString(", ")
+// s"new ArrayList<$javaType>(Arrays.asList($commaStr))"
+// }
+//
+// override def doByteArrayLiteral(arr: Seq[Byte]): String =
+// s"new byte[] { ${arr.mkString(", ")} }"
+
+ override def numericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) = {
+ (detectType(left), detectType(right), op) match {
+ case (_: IntType, _: IntType, Ast.operator.Mod) =>
+ s"${GoCompiler.kstreamName}.mod(${translate(left)}, ${translate(right)})"
+ case _ =>
+ super.numericBinOp(left, op, right)
+ }
+ }
+
+ def trLocalName(s: String): TranslatorResult = {
+ s match {
+ case Identifier.ROOT |
+ Identifier.PARENT |
+ Identifier.IO =>
+ ResultString(s"this.${specialName(s)}")
+
+ // These can be local only
+ case Identifier.ITERATOR |
+ Identifier.ITERATOR2 =>
+ ResultString(specialName(s))
+
+ case _ =>
+ if (provider.isLazy(s)) {
+ outVarCheckRes(s"this.${Utils.upperCamelCase(s)}()")
+ } else {
+ ResultString(s"this.${Utils.upperCamelCase(s)}")
+ }
+ }
+ }
+
+ def specialName(id: String): String = id match {
+ case Identifier.ROOT | Identifier.PARENT | Identifier.IO =>
+ id
+ case Identifier.ITERATOR =>
+ "_it"
+ case Identifier.ITERATOR2 =>
+ "_buf"
+ }
+
+// override def doEnumByLabel(enumTypeAbs: List[String], label: String): String =
+// s"${enumClass(enumTypeAbs)}.${label.toUpperCase}"
+// override def doEnumById(enumTypeAbs: List[String], id: String): String =
+// s"${enumClass(enumTypeAbs)}.byId($id)"
+
+ def enumClass(enumTypeAbs: List[String]): String = {
+ val enumTypeRel = Utils.relClass(enumTypeAbs, provider.nowClass.name)
+ enumTypeRel.map((x) => Utils.upperCamelCase(x)).mkString(".")
+ }
+
+ override def doStrCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr) = {
+ if (op == Ast.cmpop.Eq) {
+ s"${translate(left)}.equals(${translate(right)})"
+ } else if (op == Ast.cmpop.NotEq) {
+ s"!(${translate(left)}).equals(${translate(right)})"
+ } else {
+ s"(${translate(left)}.compareTo(${translate(right)}) ${cmpOp(op)} 0)"
+ }
+ }
+
+ override def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String = {
+ op match {
+ case Ast.cmpop.Eq =>
+ s"Arrays.equals(${translate(left)}, ${translate(right)})"
+ case Ast.cmpop.NotEq =>
+ s"!Arrays.equals(${translate(left)}, ${translate(right)})"
+ case _ =>
+ s"(${GoCompiler.kstreamName}.byteArrayCompare(${translate(left)}, ${translate(right)}) ${cmpOp(op)} 0)"
+ }
+ }
+
+// override def doSubscript(container: Ast.expr, idx: Ast.expr): String =
+// s"${translate(container)}.get((int) ${translate(idx)})"
+// override def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String =
+// s"(${translate(condition)} ? ${translate(ifTrue)} : ${translate(ifFalse)})"
+// override def doCast(value: Ast.expr, typeName: String): String =
+// s"((${Utils.upperCamelCase(typeName)}) (${translate(value)}))"
+
+ // Predefined methods of various types
+// override def strToInt(s: Ast.expr, base: Ast.expr): String =
+// s"Long.parseLong(${translate(s)}, ${translate(base)})"
+// override def enumToInt(v: Ast.expr, et: EnumType): String =
+// s"${translate(v)}.id()"
+// override def intToStr(i: Ast.expr, base: Ast.expr): String =
+// s"Long.toString(${translate(i)}, ${translate(base)})"
+
+ val IMPORT_CHARMAP = "golang.org/x/text/encoding/charmap"
+
+ val ENCODINGS = Map(
+ "cp437" -> ("charmap.CodePage437", IMPORT_CHARMAP),
+ "iso8859-1" -> ("charmap.ISO8859_1", IMPORT_CHARMAP),
+ "iso8859-2" -> ("charmap.ISO8859_2", IMPORT_CHARMAP),
+ "iso8859-3" -> ("charmap.ISO8859_3", IMPORT_CHARMAP),
+ "iso8859-4" -> ("charmap.ISO8859_4", IMPORT_CHARMAP),
+ "sjis" -> ("japanese.ShiftJIS", "golang.org/x/text/encoding/japanese"),
+ "big5" -> ("traditionalchinese.Big5", "golang.org/x/text/encoding/traditionalchinese")
+ )
+
+ def bytesToStr(bytesExpr: String, encoding: Ast.expr): TranslatorResult = {
+ val enc = encoding match {
+ case Ast.expr.Str(s) => s
+ case _ => throw new RuntimeException("Variable encodings are not supported in Go yet")
+ }
+
+ enc.toLowerCase match {
+ case "ascii" | "utf-8" | "utf8" =>
+ // no conversion
+ // FIXME: may be add some checks for valid ASCII/UTF-8
+ ResultString(s"string($bytesExpr)")
+ case encStr =>
+ ENCODINGS.get(encStr) match {
+ case Some((decoderSrc, importName)) =>
+ importList.add(importName)
+ outVarCheckRes(s"kaitai.BytesToStr($bytesExpr, $decoderSrc.NewDecoder())")
+ case None =>
+ throw new RuntimeException(s"encoding '$encStr' in not supported in Go")
+ }
+ }
+ }
+
+// override def strLength(s: Ast.expr): String =
+// s"${translate(s)}.length()"
+// override def strReverse(s: Ast.expr): String =
+// s"new StringBuilder(${translate(s)}).reverse().toString()"
+// override def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): String =
+// s"${translate(s)}.substring(${translate(from)}, ${translate(to)})"
+
+// override def arrayFirst(a: Ast.expr): String =
+// s"${translate(a)}.get(0)"
+// override def arrayLast(a: Ast.expr): String = {
+// val v = translate(a)
+// s"$v.get($v.size() - 1)"
+// }
+// override def arraySize(a: Ast.expr): String =
+// s"${translate(a)}.size()"
+// override def arrayMin(a: Ast.expr): String =
+// s"Collections.min(${translate(a)})"
+// override def arrayMax(a: Ast.expr): String =
+// s"Collections.max(${translate(a)})"
+
+ override def userTypeField(ut: UserType, value: Ast.expr, name: String): TranslatorResult = {
+ val valueStr = translate(value)
+
+ val (call, twoOuts) = name match {
+ case Identifier.ROOT |
+ Identifier.PARENT |
+ Identifier.IO =>
+ (specialName(name), false)
+ case _ =>
+ (Utils.upperCamelCase(name), provider.isLazy(ut.classSpec.get, name))
+ }
+
+ if (twoOuts) {
+ outVarCheckRes(s"$valueStr.$call()")
+ } else {
+ ResultString(s"$valueStr.$call")
+ }
+ }
+
+ override def strLength(s: Ast.expr): TranslatorResult = {
+ importList.add("unicode/utf8")
+ ResultString(s"utf8.RuneCountInString(${translate(s)})")
+ }
+
+ override def strReverse(s: Ast.expr): TranslatorResult = ???
+
+ override def strToInt(s: Ast.expr, base: Ast.expr): TranslatorResult = ???
+
+ override def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): TranslatorResult = ???
+
+ override def bytesToStr(value: Ast.expr, expr: Ast.expr): TranslatorResult = ???
+
+ override def intToStr(value: Ast.expr, num: Ast.expr): TranslatorResult = ???
+
+ override def floatToInt(value: Ast.expr): TranslatorResult =
+ ResultString(s"int(${translate(value)})")
+
+ override def kaitaiStreamSize(value: Ast.expr): TranslatorResult = ???
+
+ override def kaitaiStreamEof(value: Ast.expr): TranslatorResult = ???
+
+ override def kaitaiStreamPos(value: Ast.expr): TranslatorResult = ???
+
+ override def arrayFirst(a: Ast.expr): TranslatorResult = ???
+
+ override def arrayLast(a: Ast.expr): TranslatorResult = ???
+
+ override def arraySize(a: Ast.expr): TranslatorResult = ???
+
+ override def arrayMin(a: Ast.expr): TranslatorResult = ???
+
+ override def arrayMax(a: Ast.expr): TranslatorResult = ???
+
+ override def enumToInt(value: Ast.expr, et: EnumType): TranslatorResult = ???
+
+ override def boolToInt(value: Ast.expr): TranslatorResult = ???
+
+ def userType(dataType: UserType, io: String) = {
+ val v = allocateLocalVar()
+ out.puts(s"${localVarName(v)} := new(${GoCompiler.types2class(dataType.classSpec.get.name)})")
+ out.puts(s"err = ${localVarName(v)}.Read($io, this, this._root)")
+ outAddErrCheck()
+ ResultLocalVar(v)
+ }
+
+ def outVarCheckRes(expr: String): ResultLocalVar = {
+ val v1 = allocateLocalVar()
+ out.puts(s"${localVarName(v1)}, err := $expr")
+ outAddErrCheck()
+ ResultLocalVar(v1)
+ }
+
+ private
+ var localVarNum = 0
+
+ def allocateLocalVar(): Int = {
+ localVarNum += 1
+ localVarNum
+ }
+
+ def localVarName(n: Int) = s"tmp$n"
+
+ def outAddErrCheck() {
+ out.puts("if err != nil {")
+ out.inc
+
+ val noValueAndErr = returnRes match {
+ case None => "err"
+ case Some(r) => s"$r, err"
+ }
+
+ out.puts(s"return $noValueAndErr")
+ out.dec
+ out.puts("}")
+ }
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/JavaScriptTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/JavaScriptTranslator.scala
index 35c611e85..3605adea4 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/JavaScriptTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/JavaScriptTranslator.scala
@@ -4,15 +4,31 @@ import io.kaitai.struct.Utils
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
+import io.kaitai.struct.format.Identifier
import io.kaitai.struct.languages.JavaScriptCompiler
class JavaScriptTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
+ /**
+ * JavaScript rendition of common control character that would use hex form,
+ * not octal. "Octal" control character string literals might be accepted
+ * in non-strict JS mode, but in strict mode only hex or unicode are ok.
+ * Here we'll use hex, as they are shorter.
+ *
+ * @see https://github.com/kaitai-io/kaitai_struct/issues/279
+ * @param code character code to represent
+ * @return string literal representation of given code
+ */
+ override def strLiteralGenericCC(code: Char): String =
+ "\\x%02x".format(code.toInt)
+
override def numericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) = {
(detectType(left), detectType(right), op) match {
case (_: IntType, _: IntType, Ast.operator.Div) =>
s"Math.floor(${translate(left)} / ${translate(right)})"
case (_: IntType, _: IntType, Ast.operator.Mod) =>
s"${JavaScriptCompiler.kstreamName}.mod(${translate(left)}, ${translate(right)})"
+ case (_: IntType, _: IntType, Ast.operator.RShift) =>
+ s"(${translate(left)} >>> ${translate(right)})"
case _ =>
super.numericBinOp(left, op, right)
}
@@ -21,6 +37,8 @@ class JavaScriptTranslator(provider: TypeProvider) extends BaseTranslator(provid
override def doLocalName(s: String) = {
s match {
case "_" => s
+ case Identifier.SWITCH_ON => "on"
+ case Identifier.INDEX => "i"
case _ => s"this.${doName(s)}"
}
}
@@ -38,6 +56,9 @@ class JavaScriptTranslator(provider: TypeProvider) extends BaseTranslator(provid
// Just an integer, without any casts / resolutions - one would have to look up constants manually
label
+ override def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
+ s"(${JavaScriptCompiler.kstreamName}.byteArrayCompare(${translate(left)}, ${translate(right)}) ${cmpOp(op)} 0)"
+
override def doSubscript(container: expr, idx: expr): String =
s"${translate(container)}[${translate(idx)}]"
override def doIfExp(condition: expr, ifTrue: expr, ifFalse: expr): String =
@@ -63,6 +84,20 @@ class JavaScriptTranslator(provider: TypeProvider) extends BaseTranslator(provid
override def boolToInt(v: expr): String =
s"(${translate(v)} | 0)"
+ /**
+ * Converts a float to an integer in JavaScript. There are many methods to
+ * do so, here we use the fastest one, but it requires ES6+. OTOH, it is
+ * relatively easy to add compatibility polyfill for non-supporting environments
+ * (see MDN page).
+ *
+ * @see http://stackoverflow.com/a/596503/487064
+ * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc
+ * @param v float expression to convert
+ * @return string rendition of conversion
+ */
+ override def floatToInt(v: expr): String =
+ s"Math.trunc(${translate(v)})"
+
override def intToStr(i: expr, base: expr): String =
s"(${translate(i)}).toString(${translate(base)})"
@@ -87,6 +122,10 @@ class JavaScriptTranslator(provider: TypeProvider) extends BaseTranslator(provid
}
override def arraySize(a: expr): String =
s"${translate(a)}.length"
+ override def arrayMin(a: expr): String =
+ s"${JavaScriptCompiler.kstreamName}.arrayMin(${translate(a)})"
+ override def arrayMax(a: expr): String =
+ s"${JavaScriptCompiler.kstreamName}.arrayMax(${translate(a)})"
override def kaitaiStreamEof(value: Ast.expr): String =
s"${translate(value)}.isEof()"
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala
index 2ba560c6b..9d7dea40e 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala
@@ -1,6 +1,6 @@
package io.kaitai.struct.translators
-import io.kaitai.struct.Utils
+import io.kaitai.struct.{ImportList, Utils}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast._
import io.kaitai.struct.datatype.DataType
@@ -8,7 +8,7 @@ import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.format.Identifier
import io.kaitai.struct.languages.JavaCompiler
-class JavaTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
+class JavaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
override def doIntLiteral(n: BigInt): String = {
val literal = n.toString
val suffix = if (n > Int.MaxValue) "L" else ""
@@ -67,6 +67,8 @@ class JavaTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
case Identifier.IO => "_io()"
case Identifier.ITERATOR => "_it"
case Identifier.ITERATOR2 => "_buf"
+ case Identifier.SWITCH_ON => "on"
+ case Identifier.INDEX => "i"
case _ => s"${Utils.lowerCamelCase(s)}()"
}
@@ -90,6 +92,19 @@ class JavaTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
}
}
+ override def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String = {
+ op match {
+ case Ast.cmpop.Eq =>
+ importList.add("java.util.Arrays")
+ s"Arrays.equals(${translate(left)}, ${translate(right)})"
+ case Ast.cmpop.NotEq =>
+ importList.add("java.util.Arrays")
+ s"!Arrays.equals(${translate(left)}, ${translate(right)})"
+ case _ =>
+ s"(${JavaCompiler.kstreamName}.byteArrayCompare(${translate(left)}, ${translate(right)}) ${cmpOp(op)} 0)"
+ }
+ }
+
override def doSubscript(container: expr, idx: expr): String = {
val idxStr = translate(idx);
val contStr = translate(container);
@@ -100,6 +115,7 @@ class JavaTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
s"${contStr}.get(${idxArgStr})"
}
+
override def doIfExp(condition: expr, ifTrue: expr, ifFalse: expr): String =
s"(${translate(condition)} ? ${translate(ifTrue)} : ${translate(ifFalse)})"
override def doCast(value: Ast.expr, typeName: String): String =
@@ -110,10 +126,14 @@ class JavaTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
s"Long.parseLong(${translate(s)}, ${translate(base)})"
override def enumToInt(v: expr, et: EnumType): String =
s"${translate(v)}.id()"
+ override def floatToInt(v: expr): String =
+ s"(int) (${translate(v)} + 0)"
override def intToStr(i: expr, base: expr): String =
s"Long.toString(${translate(i)}, ${translate(base)})"
- override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String =
+ override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String = {
+ importList.add("java.nio.charset.Charset")
s"new String($bytesExpr, Charset.forName(${translate(encoding)}))"
+ }
override def strLength(s: expr): String =
s"${translate(s)}.length()"
override def strReverse(s: expr): String =
@@ -129,4 +149,12 @@ class JavaTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
}
override def arraySize(a: expr): String =
s"${translate(a)}.size()"
+ override def arrayMin(a: Ast.expr): String = {
+ importList.add("java.util.Collections")
+ s"Collections.min(${translate(a)})"
+ }
+ override def arrayMax(a: Ast.expr): String = {
+ importList.add("java.util.Collections")
+ s"Collections.max(${translate(a)})"
+ }
}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala
new file mode 100644
index 000000000..f854a534b
--- /dev/null
+++ b/shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala
@@ -0,0 +1,157 @@
+package io.kaitai.struct.translators
+
+import io.kaitai.struct.ImportList
+import io.kaitai.struct.datatype.DataType
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.format.Identifier
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.languages.LuaCompiler
+
+class LuaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
+ override val asciiCharQuoteMap: Map[Char, String] = Map(
+ '\t' -> "\\t",
+ '\n' -> "\\n",
+ '\r' -> "\\r",
+ '"' -> "\\\"",
+ '\\' -> "\\\\",
+
+ '\7' -> "\\a",
+ '\b' -> "\\b",
+ '\13' -> "\\v",
+ '\f' -> "\\f",
+ '\33' -> "\\027"
+ )
+
+ override def strLiteralUnicode(code: Char): String =
+ "\\u{%04x}".format(code.toInt)
+
+ override def doSubscript(container: Ast.expr, idx: Ast.expr): String = {
+ // Lua indexes start at 1, so we need to offset them
+ val fixedIdx = idx match {
+ case Ast.expr.IntNum(n) => Ast.expr.IntNum(n + 1)
+ case _ => idx
+ }
+
+ s"${translate(container)}[${translate(fixedIdx)}]"
+ }
+ override def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String =
+ s"(((${translate(condition)}) and (${translate(ifTrue)})) or (${translate(ifFalse)}))"
+
+ override def doBoolLiteral(n: Boolean): String =
+ if (n) "true" else "false"
+ override def doArrayLiteral(t: DataType, value: Seq[Ast.expr]): String =
+ "{" + value.map((v) => translate(v)).mkString(", ") + "}"
+ override def doByteArrayLiteral(arr: Seq[Byte]): String =
+ "\"" + decEscapeByteArray(arr) + "\""
+
+ override def doLocalName(s: String) = s match {
+ case Identifier.ITERATOR => "_"
+ case Identifier.INDEX => "i"
+ case _ => s"self.${doName(s)}"
+ }
+ override def doName(s: String): String =
+ s
+ override def doEnumByLabel(enumTypeAbs: List[String], label: String): String =
+ s"${LuaCompiler.types2class(enumTypeAbs)}.$label"
+ override def doEnumById(enumTypeAbs: List[String], id: String): String =
+ s"${LuaCompiler.types2class(enumTypeAbs)}($id)"
+
+ // This is very hacky because integers and booleans cannot be compared
+ override def doNumericCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String = {
+ val bool2Int = (n: Boolean) => { if (n) "1" else "0" }
+ (left, right) match {
+ case (Ast.expr.Bool(l), Ast.expr.Bool(r)) => s"${bool2Int(l)} ${cmpOp(op)} ${bool2Int(r)}"
+ case (Ast.expr.Bool(l), r) => s"${bool2Int(l)} ${cmpOp(op)} ${translate(r)}"
+ case (l, Ast.expr.Bool(r)) => s"${translate(l)} ${cmpOp(op)} ${bool2Int(r)}"
+ case _ => super.doNumericCompareOp(left, op, right)
+ }
+ }
+
+ override def strConcat(left: Ast.expr, right: Ast.expr): String =
+ s"${translate(left)} .. ${translate(right)}"
+ override def strToInt(s: Ast.expr, base: Ast.expr): String = {
+ val baseStr = translate(base)
+ val add = baseStr match {
+ case "10" => ""
+ case _ => s", $baseStr"
+ }
+ s"tonumber(${translate(s)}$add)"
+ }
+ override def enumToInt(v: Ast.expr, et: EnumType): String =
+ s"${translate(v)}.value"
+ override def boolToInt(v: Ast.expr): String =
+ s"(${translate(v)} and 1 or 0)"
+ override def floatToInt(v: Ast.expr): String =
+ s"(${translate(v)} > 0) and math.floor(${translate(v)}) or math.ceil(${translate(v)})"
+ override def intToStr(i: Ast.expr, base: Ast.expr): String = {
+ val baseStr = translate(base)
+ baseStr match {
+ case "10" => s"tostring(${translate(i)})"
+ case _ => throw new UnsupportedOperationException(baseStr)
+ }
+ }
+ override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String = {
+ importList.add("local str_decode = require(\"string_decode\")")
+
+ s"str_decode.decode($bytesExpr, ${translate(encoding)})"
+ }
+ override def strLength(s: Ast.expr): String =
+ s"string.len(${translate(s)})"
+ override def strReverse(s: Ast.expr): String =
+ s"string.reverse(${translate(s)})"
+ override def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): String =
+ s"string.sub(${translate(s)}, ${translate(from)}, ${translate(to)})"
+
+ override def arrayFirst(a: Ast.expr): String =
+ s"${translate(a)}[1]"
+ override def arrayLast(a: Ast.expr): String = {
+ val table = translate(a)
+ s"${table}[#${table}]"
+ }
+ override def arraySize(a: Ast.expr): String =
+ s"#${translate(a)}"
+ override def arrayMin(a: Ast.expr): String = {
+ importList.add("local utils = require(\"utils\")")
+
+ s"utils.array_min(${translate(a)})"
+ }
+ override def arrayMax(a: Ast.expr): String ={
+ importList.add("local utils = require(\"utils\")")
+
+ s"utils.array_max(${translate(a)})"
+ }
+
+ override def kaitaiStreamSize(value: Ast.expr): String =
+ s"${translate(value)}:size()"
+ override def kaitaiStreamEof(value: Ast.expr): String =
+ s"${translate(value)}:is_eof()"
+ override def kaitaiStreamPos(value: Ast.expr): String =
+ s"${translate(value)}:pos()"
+
+ override def binOp(op: Ast.operator): String = op match {
+ case Ast.operator.BitXor => "~"
+ case _ => super.binOp(op)
+ }
+ override def cmpOp(op: Ast.cmpop): String = op match {
+ case Ast.cmpop.NotEq => "~="
+ case _ => super.cmpOp(op)
+ }
+ override def booleanOp(op: Ast.boolop): String = op match {
+ case Ast.boolop.Or => "or"
+ case Ast.boolop.And => "and"
+ }
+ override def unaryOp(op: Ast.unaryop): String = op match {
+ case Ast.unaryop.Not => "not"
+ case _ => super.unaryOp(op)
+ }
+
+ /**
+ * Converts byte array (Seq[Byte]) into decimal-escaped Lua-style literal
+ * characters (i.e. like \255).
+ *
+ * @param arr byte array to escape
+ * @return array contents decimal-escaped as string
+ */
+ private def decEscapeByteArray(arr: Seq[Byte]): String =
+ arr.map((x) => "\\%03d".format(x & 0xff)).mkString
+}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala
index 404131a26..4b0aa9699 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala
@@ -41,13 +41,14 @@ class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseT
}
}
- override def userTypeField(value: expr, attrName: String): String =
+ override def anyField(value: expr, attrName: String): String =
s"${translate(value)}->${doName(attrName)}"
override def doLocalName(s: String) = {
s match {
case Identifier.ITERATOR => "$_"
case Identifier.ITERATOR2 => "$_buf"
+ case Identifier.INDEX => "$i"
case _ => s"$$this->${doName(s)}"
}
}
@@ -80,6 +81,9 @@ class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseT
override def boolToInt(v: expr): String =
s"intval(${translate(v)})"
+ override def floatToInt(v: expr): String =
+ s"intval(${translate(v)})"
+
override def intToStr(i: expr, base: expr): String = {
val baseStr = translate(base)
baseStr match {
@@ -101,11 +105,17 @@ class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseT
override def arrayFirst(a: expr): String =
s"${translate(a)}[0]"
override def arrayLast(a: expr): String = {
+ // For huge debate on efficiency of PHP last element of array methods, see:
+ // http://stackoverflow.com/a/41795859/487064
val v = translate(a)
- s"$v[$v.length - 1]"
+ s"$v[count($v) - 1]"
}
override def arraySize(a: expr): String =
s"count(${translate(a)})"
+ override def arrayMin(a: Ast.expr): String =
+ s"min(${translate(a)})"
+ override def arrayMax(a: Ast.expr): String =
+ s"max(${translate(a)})"
val namespaceRef = if (config.phpNamespace.isEmpty) {
""
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/PerlTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/PerlTranslator.scala
index a7f95d06d..cc6c30ef7 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/PerlTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/PerlTranslator.scala
@@ -1,12 +1,12 @@
package io.kaitai.struct.translators
+import io.kaitai.struct.ImportList
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
-import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.format.Identifier
-class PerlTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
+class PerlTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
// http://perldoc.perl.org/perlrebackslash.html#Character-Escapes
override val asciiCharQuoteMap: Map[Char, String] = Map(
'\t' -> "\\t",
@@ -44,18 +44,19 @@ class PerlTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
override def doBoolLiteral(n: Boolean): String = if (n) "1" else "0"
- override def doArrayLiteral(t: DataType, value: Seq[expr]): String =
+ override def doArrayLiteral(t: DataType, value: Seq[Ast.expr]): String =
"(" + value.map((v) => translate(v)).mkString(", ") + ")"
override def doByteArrayLiteral(arr: Seq[Byte]): String =
s"pack('C*', (${arr.map(_ & 0xff).mkString(", ")}))"
- override def userTypeField(value: expr, attrName: String): String =
+ override def anyField(value: Ast.expr, attrName: String): String =
s"${translate(value)}->${doName(attrName)}"
override def doLocalName(s: String) = {
s match {
case "_" | "_on" => "$" + s
+ case Identifier.INDEX => doName(s)
case _ => s"$$self->${doName(s)}"
}
}
@@ -64,6 +65,7 @@ class PerlTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
s match {
case Identifier.ITERATOR => "$_"
case Identifier.ITERATOR2 => "$_buf"
+ case Identifier.INDEX => "$i"
case _ => s"$s()"
}
}
@@ -86,9 +88,12 @@ class PerlTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
s"${translate(left)} $opStr ${translate(right)}"
}
- override def doSubscript(container: expr, idx: expr): String =
- s"${translate(container)}[${translate(idx)}]"
- override def doIfExp(condition: expr, ifTrue: expr, ifFalse: expr): String =
+ override def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
+ doStrCompareOp(left, op, right)
+
+ override def doSubscript(container: Ast.expr, idx: Ast.expr): String =
+ s"@{${translate(container)}}[${translate(idx)}]"
+ override def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String =
s"(${translate(condition)} ? ${translate(ifTrue)} : ${translate(ifFalse)})"
// Predefined methods of various types
@@ -106,10 +111,12 @@ class PerlTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
case _ => throw new UnsupportedOperationException(baseStr)
}
}
- override def enumToInt(v: expr, et: EnumType): String =
+ override def enumToInt(v: Ast.expr, et: EnumType): String =
translate(v)
- override def boolToInt(v: expr): String =
+ override def boolToInt(v: Ast.expr): String =
translate(v)
+ override def floatToInt(v: Ast.expr): String =
+ s"int(${translate(v)})"
override def intToStr(i: Ast.expr, base: Ast.expr): String = {
val baseStr = translate(base)
val format = baseStr match {
@@ -124,8 +131,10 @@ class PerlTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
s"sprintf('$format', ${translate(i)})"
}
- override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String =
+ override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String = {
+ importList.add("Encode")
s"Encode::decode(${translate(encoding)}, $bytesExpr)"
+ }
override def strLength(value: Ast.expr): String =
s"length(${translate(value)})"
override def strReverse(value: Ast.expr): String =
@@ -133,12 +142,28 @@ class PerlTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
override def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): String =
s"${translate(s)}[${translate(from)}:${translate(to)}]"
- override def arrayFirst(a: expr): String =
- s"${translate(a)}[0]"
- override def arrayLast(a: expr): String =
- s"${translate(a)}[-1]"
- override def arraySize(a: expr): String =
- s"scalar(${translate(a)})"
+ override def arrayFirst(a: Ast.expr): String =
+ s"@{${translate(a)}}[0]"
+ override def arrayLast(a: Ast.expr): String =
+ s"@{${translate(a)}}[-1]"
+ override def arraySize(a: Ast.expr): String =
+ s"scalar(@{${translate(a)}})"
+ override def arrayMin(a: Ast.expr): String = {
+ val funcName = detectType(a).asInstanceOf[ArrayType].elType match {
+ case _: StrType => "minstr"
+ case _ => "min"
+ }
+ importList.add("List::Util")
+ s"List::Util::$funcName(@{${translate(a)}})"
+ }
+ override def arrayMax(a: Ast.expr): String = {
+ val funcName = detectType(a).asInstanceOf[ArrayType].elType match {
+ case _: StrType => "maxstr"
+ case _ => "max"
+ }
+ importList.add("List::Util")
+ s"List::Util::$funcName(@{${translate(a)}})"
+ }
override def kaitaiStreamSize(value: Ast.expr): String =
s"${translate(value)}->size()"
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/PythonTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/PythonTranslator.scala
index 90f183323..ef6fa6b63 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/PythonTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/PythonTranslator.scala
@@ -1,10 +1,12 @@
package io.kaitai.struct.translators
+import io.kaitai.struct.{ImportList, Utils}
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.format.Identifier
import io.kaitai.struct.languages.PythonCompiler
-class PythonTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
+class PythonTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
override def numericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) = {
(detectType(left), detectType(right), op) match {
case (_: IntType, _: IntType, Ast.operator.Div) =>
@@ -34,12 +36,14 @@ class PythonTranslator(provider: TypeProvider) extends BaseTranslator(provider)
'\b' -> "\\b"
)
- override def doByteArrayLiteral(arr: Seq[Byte]): String =
- s"struct.pack('${arr.length}b', ${arr.mkString(", ")})"
+ override def doByteArrayLiteral(arr: Seq[Byte]): String = {
+ "b\"" + Utils.hexEscapeByteArray(arr) + "\""
+ }
override def doLocalName(s: String) = {
s match {
- case "_" => s
+ case Identifier.ITERATOR => "_"
+ case Identifier.INDEX => "i"
case _ => s"self.${doName(s)}"
}
}
@@ -78,6 +82,8 @@ class PythonTranslator(provider: TypeProvider) extends BaseTranslator(provider)
s"${translate(v)}.value"
override def boolToInt(v: Ast.expr): String =
s"int(${translate(v)})"
+ override def floatToInt(v: Ast.expr): String =
+ s"int(${translate(v)})"
override def intToStr(i: Ast.expr, base: Ast.expr): String = {
val baseStr = translate(base)
val func = baseStr match {
@@ -105,6 +111,10 @@ class PythonTranslator(provider: TypeProvider) extends BaseTranslator(provider)
s"${translate(a)}[-1]"
override def arraySize(a: Ast.expr): String =
s"len(${translate(a)})"
+ override def arrayMin(a: Ast.expr): String =
+ s"min(${translate(a)})"
+ override def arrayMax(a: Ast.expr): String =
+ s"max(${translate(a)})"
override def kaitaiStreamSize(value: Ast.expr): String =
s"${translate(value)}.size()"
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/RubyTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/RubyTranslator.scala
index d8c4c46ca..52975cee5 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/RubyTranslator.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/RubyTranslator.scala
@@ -2,6 +2,8 @@ package io.kaitai.struct.translators
import io.kaitai.struct.datatype.DataType.EnumType
import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.exprlang.Ast.expr
+import io.kaitai.struct.format.Identifier
import io.kaitai.struct.languages.RubyCompiler
class RubyTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
@@ -25,7 +27,12 @@ class RubyTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
'\b' -> "\\b"
)
- override def doName(s: String) = s
+ override def doName(s: String) = {
+ s match {
+ case Identifier.INDEX => "i" // FIXME: probably would clash with attribute named "i"
+ case _ => s
+ }
+ }
override def doEnumByLabel(enumTypeAbs: List[String], label: String): String =
s":${enumTypeAbs.last}_$label"
@@ -47,6 +54,8 @@ class RubyTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
}
override def enumToInt(v: Ast.expr, et: EnumType): String =
s"${RubyCompiler.inverseEnumName(et.name.last.toUpperCase)}[${translate(v)}]"
+ override def floatToInt(v: Ast.expr): String =
+ s"(${translate(v)}).to_i"
override def intToStr(i: Ast.expr, base: Ast.expr): String =
translate(i) + s".to_s(${translate(base)})"
override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String =
@@ -64,6 +73,10 @@ class RubyTranslator(provider: TypeProvider) extends BaseTranslator(provider) {
s"${translate(a)}.last"
override def arraySize(a: Ast.expr): String =
s"${translate(a)}.length"
+ override def arrayMin(a: expr): String =
+ s"${translate(a)}.min"
+ override def arrayMax(a: expr): String =
+ s"${translate(a)}.max"
override def kaitaiStreamEof(value: Ast.expr): String =
s"${translate(value)}.eof?"
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala
index c3ec8caa5..89be684ba 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala
@@ -15,7 +15,26 @@ import io.kaitai.struct.precompile.{TypeMismatchError, TypeUndecidedError}
class TypeDetector(provider: TypeProvider) {
import TypeDetector._
+ /**
+ * Detects type of a given expression. If it returns a SwitchType, it
+ * effectively flattens it to a resulting combined type.
+ * @param v expression
+ * @return data type
+ */
def detectType(v: Ast.expr): DataType = {
+ detectTypeRaw(v) match {
+ case st: SwitchType => st.combinedType
+ case other => other
+ }
+ }
+
+ /**
+ * Detects type of a given expression, raw, without any switch type
+ * flattening.
+ * @param v expression
+ * @return data type
+ */
+ def detectTypeRaw(v: Ast.expr): DataType = {
v match {
case Ast.expr.IntNum(x) =>
if (x < 0 || x > 255) {
@@ -110,9 +129,14 @@ class TypeDetector(provider: TypeProvider) {
case "to_s" => CalcStrType
case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
}
+ case _: FloatType =>
+ attr.name match {
+ case "to_i" => CalcIntType
+ case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
+ }
case ArrayType(inType) =>
attr.name match {
- case "first" | "last" => inType
+ case "first" | "last" | "min" | "max" => inType
case "size" => CalcIntType
case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
}
@@ -195,6 +219,7 @@ object TypeDetector {
case (_: StrType, _: StrType) => // ok
case (_: NumericType, _: NumericType) => // ok
case (_: BooleanType, _: BooleanType) => // ok
+ case (_: BytesType, _: BytesType) => // ok
case (EnumType(name1, _), EnumType(name2, _)) =>
if (name1 != name2) {
throw new TypeMismatchError(s"can't compare different enums '$name1' and '$name2'")
@@ -242,6 +267,7 @@ object TypeDetector {
}
case (_: IntType, _: IntType) => CalcIntType
case (_: NumericType, _: NumericType) => CalcFloatType
+ case (_: BytesType, _: BytesType) => CalcBytesType
case (t1: UserType, t2: UserType) =>
// Two user types can differ in reserved size and/or processing, but that doesn't matter in case of
// type combining - we treat them the same as long as they result in same class spec or have same
@@ -285,10 +311,49 @@ object TypeDetector {
* @return type that can accommodate values of both source types without any data loss
*/
def combineTypesAndFail(t1: DataType, t2: DataType): DataType = {
- combineTypes(t1, t2) match {
- case AnyType =>
- throw new TypeMismatchError(s"can't combine output types: $t1 vs $t2")
- case ct => ct
+ if (t1 == AnyType || t2 == AnyType) {
+ // combining existing AnyTypes is not a crime :)
+ AnyType
+ } else {
+ combineTypes(t1, t2) match {
+ case AnyType =>
+ throw new TypeMismatchError(s"can't combine output types: $t1 vs $t2")
+ case ct => ct
+ }
+ }
+ }
+
+ /**
+ * Returns true if one can assign value of type `src` into a variable / parameter of type `dst`.
+ * @param src data type of source value to be assigned
+ * @param dst destination data type to be assigned into
+ * @return true if assign if possible
+ */
+ def canAssign(src: DataType, dst: DataType): Boolean = {
+ if (src == dst) {
+ // Obviously, if types are equal, they'll fit into one another
+ true
+ } else {
+ (src, dst) match {
+ case (_, AnyType) => true
+ case (_: IntType, _: IntType) => true
+ case (_: FloatType, _: FloatType) => true
+ case (_: BooleanType, _: BooleanType) => true
+ case (_: StrType, _: StrType) => true
+ case (_: UserType, KaitaiStructType) => true
+ case (t1: UserType, t2: UserType) =>
+ (t1.classSpec, t2.classSpec) match {
+ case (None, None) =>
+ // opaque classes are assignable if their names match
+ t1.name == t2.name
+ case (Some(cs1), Some(cs2)) =>
+ // normal user types are assignable if their class specs match
+ cs1 == cs2
+ case (_, _) =>
+ false
+ }
+ case (_, _) => false
+ }
}
}
}
diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala
index 3a9135b4d..74f9207d5 100644
--- a/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala
+++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala
@@ -14,4 +14,6 @@ trait TypeProvider {
def determineType(inClass: ClassSpec, attrName: String): DataType
def resolveEnum(enumName: String): EnumSpec
def resolveType(typeName: String): DataType
+ def isLazy(attrName: String): Boolean
+ def isLazy(inClass: ClassSpec, attrName: String): Boolean
}